1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_colors;
33mod lsp_ext;
34mod mouse_context_menu;
35pub mod movement;
36mod persistence;
37mod proposed_changes_editor;
38mod rust_analyzer_ext;
39pub mod scroll;
40mod selections_collection;
41pub mod tasks;
42
43#[cfg(test)]
44mod code_completion_tests;
45#[cfg(test)]
46mod edit_prediction_tests;
47#[cfg(test)]
48mod editor_tests;
49mod signature_help;
50#[cfg(any(test, feature = "test-support"))]
51pub mod test;
52
53pub(crate) use actions::*;
54pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
55pub use edit_prediction::Direction;
56pub use editor_settings::{
57 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
58 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
59};
60pub use editor_settings_controls::*;
61pub use element::{
62 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
63};
64pub use git::blame::BlameRenderer;
65pub use hover_popover::hover_markdown_style;
66pub use items::MAX_TAB_TITLE_LEN;
67pub use lsp::CompletionContext;
68pub use lsp_ext::lsp_tasks;
69pub use multi_buffer::{
70 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
71 RowInfo, ToOffset, ToPoint,
72};
73pub use proposed_changes_editor::{
74 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
75};
76pub use text::Bias;
77
78use ::git::{
79 Restore,
80 blame::{BlameEntry, ParsedCommitMessage},
81};
82use aho_corasick::AhoCorasick;
83use anyhow::{Context as _, Result, anyhow};
84use blink_manager::BlinkManager;
85use buffer_diff::DiffHunkStatus;
86use client::{Collaborator, ParticipantIndex};
87use clock::{AGENT_REPLICA_ID, ReplicaId};
88use code_context_menus::{
89 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
90 CompletionsMenu, ContextMenuOrigin,
91};
92use collections::{BTreeMap, HashMap, HashSet, VecDeque};
93use convert_case::{Case, Casing};
94use dap::TelemetrySpawnLocation;
95use display_map::*;
96use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
97use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
98use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
99use futures::{
100 FutureExt, StreamExt as _,
101 future::{self, Shared, join},
102 stream::FuturesUnordered,
103};
104use fuzzy::{StringMatch, StringMatchCandidate};
105use git::blame::{GitBlame, GlobalBlameRenderer};
106use gpui::{
107 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
108 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
109 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
110 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
111 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
112 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
113 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
114 div, point, prelude::*, pulsating_between, px, relative, size,
115};
116use highlight_matching_bracket::refresh_matching_bracket_highlights;
117use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
118use hover_popover::{HoverState, hide_hover};
119use indent_guides::ActiveIndentGuidesState;
120use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
121use itertools::{Either, Itertools};
122use language::{
123 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
124 BufferSnapshot, Capability, CharClassifier, CharKind, 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::Peekable,
181 mem,
182 num::NonZeroU32,
183 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
184 path::{Path, PathBuf},
185 rc::Rc,
186 sync::Arc,
187 time::{Duration, Instant},
188};
189use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
190use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
191use theme::{
192 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
193 observe_buffer_font_size_adjustment,
194};
195use ui::{
196 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
197 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
198};
199use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
200use workspace::{
201 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
202 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
203 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
204 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
205 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
206 searchable::SearchEvent,
207};
208
209use crate::{
210 code_context_menus::CompletionsMenuSource,
211 editor_settings::MultiCursorModifier,
212 hover_links::{find_url, find_url_from_range},
213 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
214};
215
216pub const FILE_HEADER_HEIGHT: u32 = 2;
217pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
218const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
219const MAX_LINE_LEN: usize = 1024;
220const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
221const MAX_SELECTION_HISTORY_LEN: usize = 1024;
222pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
223#[doc(hidden)]
224pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
225pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
226
227pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
228pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
229pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
230
231pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
232pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
233pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
234
235pub type RenderDiffHunkControlsFn = Arc<
236 dyn Fn(
237 u32,
238 &DiffHunkStatus,
239 Range<Anchor>,
240 bool,
241 Pixels,
242 &Entity<Editor>,
243 &mut Window,
244 &mut App,
245 ) -> AnyElement,
246>;
247
248enum ReportEditorEvent {
249 Saved { auto_saved: bool },
250 EditorOpened,
251 Closed,
252}
253
254impl ReportEditorEvent {
255 pub fn event_type(&self) -> &'static str {
256 match self {
257 Self::Saved { .. } => "Editor Saved",
258 Self::EditorOpened => "Editor Opened",
259 Self::Closed => "Editor Closed",
260 }
261 }
262}
263
264struct InlineValueCache {
265 enabled: bool,
266 inlays: Vec<InlayId>,
267 refresh_task: Task<Option<()>>,
268}
269
270impl InlineValueCache {
271 fn new(enabled: bool) -> Self {
272 Self {
273 enabled,
274 inlays: Vec::new(),
275 refresh_task: Task::ready(None),
276 }
277 }
278}
279
280#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
281pub enum InlayId {
282 EditPrediction(usize),
283 DebuggerValue(usize),
284 // LSP
285 Hint(usize),
286 Color(usize),
287}
288
289impl InlayId {
290 fn id(&self) -> usize {
291 match self {
292 Self::EditPrediction(id) => *id,
293 Self::DebuggerValue(id) => *id,
294 Self::Hint(id) => *id,
295 Self::Color(id) => *id,
296 }
297 }
298}
299
300pub enum ActiveDebugLine {}
301pub enum DebugStackFrameLine {}
302enum DocumentHighlightRead {}
303enum DocumentHighlightWrite {}
304enum InputComposition {}
305pub enum PendingInput {}
306enum SelectedTextHighlight {}
307
308pub enum ConflictsOuter {}
309pub enum ConflictsOurs {}
310pub enum ConflictsTheirs {}
311pub enum ConflictsOursMarker {}
312pub enum ConflictsTheirsMarker {}
313
314#[derive(Debug, Copy, Clone, PartialEq, Eq)]
315pub enum Navigated {
316 Yes,
317 No,
318}
319
320impl Navigated {
321 pub fn from_bool(yes: bool) -> Navigated {
322 if yes { Navigated::Yes } else { Navigated::No }
323 }
324}
325
326#[derive(Debug, Clone, PartialEq, Eq)]
327enum DisplayDiffHunk {
328 Folded {
329 display_row: DisplayRow,
330 },
331 Unfolded {
332 is_created_file: bool,
333 diff_base_byte_range: Range<usize>,
334 display_row_range: Range<DisplayRow>,
335 multi_buffer_range: Range<Anchor>,
336 status: DiffHunkStatus,
337 },
338}
339
340pub enum HideMouseCursorOrigin {
341 TypingAction,
342 MovementAction,
343}
344
345pub fn init_settings(cx: &mut App) {
346 EditorSettings::register(cx);
347}
348
349pub fn init(cx: &mut App) {
350 init_settings(cx);
351
352 cx.set_global(GlobalBlameRenderer(Arc::new(())));
353
354 workspace::register_project_item::<Editor>(cx);
355 workspace::FollowableViewRegistry::register::<Editor>(cx);
356 workspace::register_serializable_item::<Editor>(cx);
357
358 cx.observe_new(
359 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
360 workspace.register_action(Editor::new_file);
361 workspace.register_action(Editor::new_file_vertical);
362 workspace.register_action(Editor::new_file_horizontal);
363 workspace.register_action(Editor::cancel_language_server_work);
364 workspace.register_action(Editor::toggle_focus);
365 },
366 )
367 .detach();
368
369 cx.on_action(move |_: &workspace::NewFile, cx| {
370 let app_state = workspace::AppState::global(cx);
371 if let Some(app_state) = app_state.upgrade() {
372 workspace::open_new(
373 Default::default(),
374 app_state,
375 cx,
376 |workspace, window, cx| {
377 Editor::new_file(workspace, &Default::default(), window, cx)
378 },
379 )
380 .detach();
381 }
382 });
383 cx.on_action(move |_: &workspace::NewWindow, cx| {
384 let app_state = workspace::AppState::global(cx);
385 if let Some(app_state) = app_state.upgrade() {
386 workspace::open_new(
387 Default::default(),
388 app_state,
389 cx,
390 |workspace, window, cx| {
391 cx.activate(true);
392 Editor::new_file(workspace, &Default::default(), window, cx)
393 },
394 )
395 .detach();
396 }
397 });
398}
399
400pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
401 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
402}
403
404pub trait DiagnosticRenderer {
405 fn render_group(
406 &self,
407 diagnostic_group: Vec<DiagnosticEntry<Point>>,
408 buffer_id: BufferId,
409 snapshot: EditorSnapshot,
410 editor: WeakEntity<Editor>,
411 cx: &mut App,
412 ) -> Vec<BlockProperties<Anchor>>;
413
414 fn render_hover(
415 &self,
416 diagnostic_group: Vec<DiagnosticEntry<Point>>,
417 range: Range<Point>,
418 buffer_id: BufferId,
419 cx: &mut App,
420 ) -> Option<Entity<markdown::Markdown>>;
421
422 fn open_link(
423 &self,
424 editor: &mut Editor,
425 link: SharedString,
426 window: &mut Window,
427 cx: &mut Context<Editor>,
428 );
429}
430
431pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
432
433impl GlobalDiagnosticRenderer {
434 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
435 cx.try_global::<Self>().map(|g| g.0.clone())
436 }
437}
438
439impl gpui::Global for GlobalDiagnosticRenderer {}
440pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
441 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
442}
443
444pub struct SearchWithinRange;
445
446trait InvalidationRegion {
447 fn ranges(&self) -> &[Range<Anchor>];
448}
449
450#[derive(Clone, Debug, PartialEq)]
451pub enum SelectPhase {
452 Begin {
453 position: DisplayPoint,
454 add: bool,
455 click_count: usize,
456 },
457 BeginColumnar {
458 position: DisplayPoint,
459 reset: bool,
460 mode: ColumnarMode,
461 goal_column: u32,
462 },
463 Extend {
464 position: DisplayPoint,
465 click_count: usize,
466 },
467 Update {
468 position: DisplayPoint,
469 goal_column: u32,
470 scroll_delta: gpui::Point<f32>,
471 },
472 End,
473}
474
475#[derive(Clone, Debug, PartialEq)]
476pub enum ColumnarMode {
477 FromMouse,
478 FromSelection,
479}
480
481#[derive(Clone, Debug)]
482pub enum SelectMode {
483 Character,
484 Word(Range<Anchor>),
485 Line(Range<Anchor>),
486 All,
487}
488
489#[derive(Clone, PartialEq, Eq, Debug)]
490pub enum EditorMode {
491 SingleLine,
492 AutoHeight {
493 min_lines: usize,
494 max_lines: Option<usize>,
495 },
496 Full {
497 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
498 scale_ui_elements_with_buffer_font_size: bool,
499 /// When set to `true`, the editor will render a background for the active line.
500 show_active_line_background: bool,
501 /// When set to `true`, the editor's height will be determined by its content.
502 sized_by_content: bool,
503 },
504 Minimap {
505 parent: WeakEntity<Editor>,
506 },
507}
508
509impl EditorMode {
510 pub fn full() -> Self {
511 Self::Full {
512 scale_ui_elements_with_buffer_font_size: true,
513 show_active_line_background: true,
514 sized_by_content: false,
515 }
516 }
517
518 #[inline]
519 pub fn is_full(&self) -> bool {
520 matches!(self, Self::Full { .. })
521 }
522
523 #[inline]
524 pub fn is_single_line(&self) -> bool {
525 matches!(self, Self::SingleLine { .. })
526 }
527
528 #[inline]
529 fn is_minimap(&self) -> bool {
530 matches!(self, Self::Minimap { .. })
531 }
532}
533
534#[derive(Copy, Clone, Debug)]
535pub enum SoftWrap {
536 /// Prefer not to wrap at all.
537 ///
538 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
539 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
540 GitDiff,
541 /// Prefer a single line generally, unless an overly long line is encountered.
542 None,
543 /// Soft wrap lines that exceed the editor width.
544 EditorWidth,
545 /// Soft wrap lines at the preferred line length.
546 Column(u32),
547 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
548 Bounded(u32),
549}
550
551#[derive(Clone)]
552pub struct EditorStyle {
553 pub background: Hsla,
554 pub border: Hsla,
555 pub local_player: PlayerColor,
556 pub text: TextStyle,
557 pub scrollbar_width: Pixels,
558 pub syntax: Arc<SyntaxTheme>,
559 pub status: StatusColors,
560 pub inlay_hints_style: HighlightStyle,
561 pub edit_prediction_styles: EditPredictionStyles,
562 pub unnecessary_code_fade: f32,
563 pub show_underlines: bool,
564}
565
566impl Default for EditorStyle {
567 fn default() -> Self {
568 Self {
569 background: Hsla::default(),
570 border: Hsla::default(),
571 local_player: PlayerColor::default(),
572 text: TextStyle::default(),
573 scrollbar_width: Pixels::default(),
574 syntax: Default::default(),
575 // HACK: Status colors don't have a real default.
576 // We should look into removing the status colors from the editor
577 // style and retrieve them directly from the theme.
578 status: StatusColors::dark(),
579 inlay_hints_style: HighlightStyle::default(),
580 edit_prediction_styles: EditPredictionStyles {
581 insertion: HighlightStyle::default(),
582 whitespace: HighlightStyle::default(),
583 },
584 unnecessary_code_fade: Default::default(),
585 show_underlines: true,
586 }
587 }
588}
589
590pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
591 let show_background = language_settings::language_settings(None, None, cx)
592 .inlay_hints
593 .show_background;
594
595 HighlightStyle {
596 color: Some(cx.theme().status().hint),
597 background_color: show_background.then(|| cx.theme().status().hint_background),
598 ..HighlightStyle::default()
599 }
600}
601
602pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
603 EditPredictionStyles {
604 insertion: HighlightStyle {
605 color: Some(cx.theme().status().predictive),
606 ..HighlightStyle::default()
607 },
608 whitespace: HighlightStyle {
609 background_color: Some(cx.theme().status().created_background),
610 ..HighlightStyle::default()
611 },
612 }
613}
614
615type CompletionId = usize;
616
617pub(crate) enum EditDisplayMode {
618 TabAccept,
619 DiffPopover,
620 Inline,
621}
622
623enum EditPrediction {
624 Edit {
625 edits: Vec<(Range<Anchor>, String)>,
626 edit_preview: Option<EditPreview>,
627 display_mode: EditDisplayMode,
628 snapshot: BufferSnapshot,
629 },
630 Move {
631 target: Anchor,
632 snapshot: BufferSnapshot,
633 },
634}
635
636struct EditPredictionState {
637 inlay_ids: Vec<InlayId>,
638 completion: EditPrediction,
639 completion_id: Option<SharedString>,
640 invalidation_range: Range<Anchor>,
641}
642
643enum EditPredictionSettings {
644 Disabled,
645 Enabled {
646 show_in_menu: bool,
647 preview_requires_modifier: bool,
648 },
649}
650
651enum EditPredictionHighlight {}
652
653#[derive(Debug, Clone)]
654struct InlineDiagnostic {
655 message: SharedString,
656 group_id: usize,
657 is_primary: bool,
658 start: Point,
659 severity: lsp::DiagnosticSeverity,
660}
661
662pub enum MenuEditPredictionsPolicy {
663 Never,
664 ByProvider,
665}
666
667pub enum EditPredictionPreview {
668 /// Modifier is not pressed
669 Inactive { released_too_fast: bool },
670 /// Modifier pressed
671 Active {
672 since: Instant,
673 previous_scroll_position: Option<ScrollAnchor>,
674 },
675}
676
677impl EditPredictionPreview {
678 pub fn released_too_fast(&self) -> bool {
679 match self {
680 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
681 EditPredictionPreview::Active { .. } => false,
682 }
683 }
684
685 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
686 if let EditPredictionPreview::Active {
687 previous_scroll_position,
688 ..
689 } = self
690 {
691 *previous_scroll_position = scroll_position;
692 }
693 }
694}
695
696pub struct ContextMenuOptions {
697 pub min_entries_visible: usize,
698 pub max_entries_visible: usize,
699 pub placement: Option<ContextMenuPlacement>,
700}
701
702#[derive(Debug, Clone, PartialEq, Eq)]
703pub enum ContextMenuPlacement {
704 Above,
705 Below,
706}
707
708#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
709struct EditorActionId(usize);
710
711impl EditorActionId {
712 pub fn post_inc(&mut self) -> Self {
713 let answer = self.0;
714
715 *self = Self(answer + 1);
716
717 Self(answer)
718 }
719}
720
721// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
722// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
723
724type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
725type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
726
727#[derive(Default)]
728struct ScrollbarMarkerState {
729 scrollbar_size: Size<Pixels>,
730 dirty: bool,
731 markers: Arc<[PaintQuad]>,
732 pending_refresh: Option<Task<Result<()>>>,
733}
734
735impl ScrollbarMarkerState {
736 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
737 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
738 }
739}
740
741#[derive(Clone, Copy, PartialEq, Eq)]
742pub enum MinimapVisibility {
743 Disabled,
744 Enabled {
745 /// The configuration currently present in the users settings.
746 setting_configuration: bool,
747 /// Whether to override the currently set visibility from the users setting.
748 toggle_override: bool,
749 },
750}
751
752impl MinimapVisibility {
753 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
754 if mode.is_full() {
755 Self::Enabled {
756 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
757 toggle_override: false,
758 }
759 } else {
760 Self::Disabled
761 }
762 }
763
764 fn hidden(&self) -> Self {
765 match *self {
766 Self::Enabled {
767 setting_configuration,
768 ..
769 } => Self::Enabled {
770 setting_configuration,
771 toggle_override: setting_configuration,
772 },
773 Self::Disabled => Self::Disabled,
774 }
775 }
776
777 fn disabled(&self) -> bool {
778 matches!(*self, Self::Disabled)
779 }
780
781 fn settings_visibility(&self) -> bool {
782 match *self {
783 Self::Enabled {
784 setting_configuration,
785 ..
786 } => setting_configuration,
787 _ => false,
788 }
789 }
790
791 fn visible(&self) -> bool {
792 match *self {
793 Self::Enabled {
794 setting_configuration,
795 toggle_override,
796 } => setting_configuration ^ toggle_override,
797 _ => false,
798 }
799 }
800
801 fn toggle_visibility(&self) -> Self {
802 match *self {
803 Self::Enabled {
804 toggle_override,
805 setting_configuration,
806 } => Self::Enabled {
807 setting_configuration,
808 toggle_override: !toggle_override,
809 },
810 Self::Disabled => Self::Disabled,
811 }
812 }
813}
814
815#[derive(Clone, Debug)]
816struct RunnableTasks {
817 templates: Vec<(TaskSourceKind, TaskTemplate)>,
818 offset: multi_buffer::Anchor,
819 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
820 column: u32,
821 // Values of all named captures, including those starting with '_'
822 extra_variables: HashMap<String, String>,
823 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
824 context_range: Range<BufferOffset>,
825}
826
827impl RunnableTasks {
828 fn resolve<'a>(
829 &'a self,
830 cx: &'a task::TaskContext,
831 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
832 self.templates.iter().filter_map(|(kind, template)| {
833 template
834 .resolve_task(&kind.to_id_base(), cx)
835 .map(|task| (kind.clone(), task))
836 })
837 }
838}
839
840#[derive(Clone)]
841pub struct ResolvedTasks {
842 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
843 position: Anchor,
844}
845
846#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
847struct BufferOffset(usize);
848
849/// Addons allow storing per-editor state in other crates (e.g. Vim)
850pub trait Addon: 'static {
851 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
852
853 fn render_buffer_header_controls(
854 &self,
855 _: &ExcerptInfo,
856 _: &Window,
857 _: &App,
858 ) -> Option<AnyElement> {
859 None
860 }
861
862 fn to_any(&self) -> &dyn std::any::Any;
863
864 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
865 None
866 }
867}
868
869struct ChangeLocation {
870 current: Option<Vec<Anchor>>,
871 original: Vec<Anchor>,
872}
873impl ChangeLocation {
874 fn locations(&self) -> &[Anchor] {
875 self.current.as_ref().unwrap_or(&self.original)
876 }
877}
878
879/// A set of caret positions, registered when the editor was edited.
880pub struct ChangeList {
881 changes: Vec<ChangeLocation>,
882 /// Currently "selected" change.
883 position: Option<usize>,
884}
885
886impl ChangeList {
887 pub fn new() -> Self {
888 Self {
889 changes: Vec::new(),
890 position: None,
891 }
892 }
893
894 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
895 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
896 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
897 if self.changes.is_empty() {
898 return None;
899 }
900
901 let prev = self.position.unwrap_or(self.changes.len());
902 let next = if direction == Direction::Prev {
903 prev.saturating_sub(count)
904 } else {
905 (prev + count).min(self.changes.len() - 1)
906 };
907 self.position = Some(next);
908 self.changes.get(next).map(|change| change.locations())
909 }
910
911 /// Adds a new change to the list, resetting the change list position.
912 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
913 self.position.take();
914 if let Some(last) = self.changes.last_mut()
915 && group
916 {
917 last.current = Some(new_positions)
918 } else {
919 self.changes.push(ChangeLocation {
920 original: new_positions,
921 current: None,
922 });
923 }
924 }
925
926 pub fn last(&self) -> Option<&[Anchor]> {
927 self.changes.last().map(|change| change.locations())
928 }
929
930 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
931 self.changes.last().map(|change| change.original.as_slice())
932 }
933
934 pub fn invert_last_group(&mut self) {
935 if let Some(last) = self.changes.last_mut()
936 && let Some(current) = last.current.as_mut()
937 {
938 mem::swap(&mut last.original, current);
939 }
940 }
941}
942
943#[derive(Clone)]
944struct InlineBlamePopoverState {
945 scroll_handle: ScrollHandle,
946 commit_message: Option<ParsedCommitMessage>,
947 markdown: Entity<Markdown>,
948}
949
950struct InlineBlamePopover {
951 position: gpui::Point<Pixels>,
952 hide_task: Option<Task<()>>,
953 popover_bounds: Option<Bounds<Pixels>>,
954 popover_state: InlineBlamePopoverState,
955 keyboard_grace: bool,
956}
957
958enum SelectionDragState {
959 /// State when no drag related activity is detected.
960 None,
961 /// State when the mouse is down on a selection that is about to be dragged.
962 ReadyToDrag {
963 selection: Selection<Anchor>,
964 click_position: gpui::Point<Pixels>,
965 mouse_down_time: Instant,
966 },
967 /// State when the mouse is dragging the selection in the editor.
968 Dragging {
969 selection: Selection<Anchor>,
970 drop_cursor: Selection<Anchor>,
971 hide_drop_cursor: bool,
972 },
973}
974
975enum ColumnarSelectionState {
976 FromMouse {
977 selection_tail: Anchor,
978 display_point: Option<DisplayPoint>,
979 },
980 FromSelection {
981 selection_tail: Anchor,
982 },
983}
984
985/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
986/// a breakpoint on them.
987#[derive(Clone, Copy, Debug, PartialEq, Eq)]
988struct PhantomBreakpointIndicator {
989 display_row: DisplayRow,
990 /// There's a small debounce between hovering over the line and showing the indicator.
991 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
992 is_active: bool,
993 collides_with_existing_breakpoint: bool,
994}
995
996/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
997///
998/// See the [module level documentation](self) for more information.
999pub struct Editor {
1000 focus_handle: FocusHandle,
1001 last_focused_descendant: Option<WeakFocusHandle>,
1002 /// The text buffer being edited
1003 buffer: Entity<MultiBuffer>,
1004 /// Map of how text in the buffer should be displayed.
1005 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1006 pub display_map: Entity<DisplayMap>,
1007 placeholder_display_map: Option<Entity<DisplayMap>>,
1008 pub selections: SelectionsCollection,
1009 pub scroll_manager: ScrollManager,
1010 /// When inline assist editors are linked, they all render cursors because
1011 /// typing enters text into each of them, even the ones that aren't focused.
1012 pub(crate) show_cursor_when_unfocused: bool,
1013 columnar_selection_state: Option<ColumnarSelectionState>,
1014 add_selections_state: Option<AddSelectionsState>,
1015 select_next_state: Option<SelectNextState>,
1016 select_prev_state: Option<SelectNextState>,
1017 selection_history: SelectionHistory,
1018 defer_selection_effects: bool,
1019 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1020 autoclose_regions: Vec<AutocloseRegion>,
1021 snippet_stack: InvalidationStack<SnippetState>,
1022 select_syntax_node_history: SelectSyntaxNodeHistory,
1023 ime_transaction: Option<TransactionId>,
1024 pub diagnostics_max_severity: DiagnosticSeverity,
1025 active_diagnostics: ActiveDiagnostic,
1026 show_inline_diagnostics: bool,
1027 inline_diagnostics_update: Task<()>,
1028 inline_diagnostics_enabled: bool,
1029 diagnostics_enabled: bool,
1030 word_completions_enabled: bool,
1031 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1032 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1033 hard_wrap: Option<usize>,
1034 project: Option<Entity<Project>>,
1035 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1036 completion_provider: Option<Rc<dyn CompletionProvider>>,
1037 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1038 blink_manager: Entity<BlinkManager>,
1039 show_cursor_names: bool,
1040 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1041 pub show_local_selections: bool,
1042 mode: EditorMode,
1043 show_breadcrumbs: bool,
1044 show_gutter: bool,
1045 show_scrollbars: ScrollbarAxes,
1046 minimap_visibility: MinimapVisibility,
1047 offset_content: bool,
1048 disable_expand_excerpt_buttons: bool,
1049 show_line_numbers: Option<bool>,
1050 use_relative_line_numbers: Option<bool>,
1051 show_git_diff_gutter: Option<bool>,
1052 show_code_actions: Option<bool>,
1053 show_runnables: Option<bool>,
1054 show_breakpoints: Option<bool>,
1055 show_wrap_guides: Option<bool>,
1056 show_indent_guides: Option<bool>,
1057 highlight_order: usize,
1058 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1059 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1060 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1061 scrollbar_marker_state: ScrollbarMarkerState,
1062 active_indent_guides_state: ActiveIndentGuidesState,
1063 nav_history: Option<ItemNavHistory>,
1064 context_menu: RefCell<Option<CodeContextMenu>>,
1065 context_menu_options: Option<ContextMenuOptions>,
1066 mouse_context_menu: Option<MouseContextMenu>,
1067 completion_tasks: Vec<(CompletionId, Task<()>)>,
1068 inline_blame_popover: Option<InlineBlamePopover>,
1069 inline_blame_popover_show_task: Option<Task<()>>,
1070 signature_help_state: SignatureHelpState,
1071 auto_signature_help: Option<bool>,
1072 find_all_references_task_sources: Vec<Anchor>,
1073 next_completion_id: CompletionId,
1074 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1075 code_actions_task: Option<Task<Result<()>>>,
1076 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1077 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1078 document_highlights_task: Option<Task<()>>,
1079 linked_editing_range_task: Option<Task<Option<()>>>,
1080 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1081 pending_rename: Option<RenameState>,
1082 searchable: bool,
1083 cursor_shape: CursorShape,
1084 current_line_highlight: Option<CurrentLineHighlight>,
1085 collapse_matches: bool,
1086 autoindent_mode: Option<AutoindentMode>,
1087 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1088 input_enabled: bool,
1089 use_modal_editing: bool,
1090 read_only: bool,
1091 leader_id: Option<CollaboratorId>,
1092 remote_id: Option<ViewId>,
1093 pub hover_state: HoverState,
1094 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1095 gutter_hovered: bool,
1096 hovered_link_state: Option<HoveredLinkState>,
1097 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1098 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1099 active_edit_prediction: Option<EditPredictionState>,
1100 /// Used to prevent flickering as the user types while the menu is open
1101 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1102 edit_prediction_settings: EditPredictionSettings,
1103 edit_predictions_hidden_for_vim_mode: bool,
1104 show_edit_predictions_override: Option<bool>,
1105 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1106 edit_prediction_preview: EditPredictionPreview,
1107 edit_prediction_indent_conflict: bool,
1108 edit_prediction_requires_modifier_in_indent_conflict: bool,
1109 inlay_hint_cache: InlayHintCache,
1110 next_inlay_id: usize,
1111 _subscriptions: Vec<Subscription>,
1112 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1113 gutter_dimensions: GutterDimensions,
1114 style: Option<EditorStyle>,
1115 text_style_refinement: Option<TextStyleRefinement>,
1116 next_editor_action_id: EditorActionId,
1117 editor_actions: Rc<
1118 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1119 >,
1120 use_autoclose: bool,
1121 use_auto_surround: bool,
1122 auto_replace_emoji_shortcode: bool,
1123 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1124 show_git_blame_gutter: bool,
1125 show_git_blame_inline: bool,
1126 show_git_blame_inline_delay_task: Option<Task<()>>,
1127 git_blame_inline_enabled: bool,
1128 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1129 serialize_dirty_buffers: bool,
1130 show_selection_menu: Option<bool>,
1131 blame: Option<Entity<GitBlame>>,
1132 blame_subscription: Option<Subscription>,
1133 custom_context_menu: Option<
1134 Box<
1135 dyn 'static
1136 + Fn(
1137 &mut Self,
1138 DisplayPoint,
1139 &mut Window,
1140 &mut Context<Self>,
1141 ) -> Option<Entity<ui::ContextMenu>>,
1142 >,
1143 >,
1144 last_bounds: Option<Bounds<Pixels>>,
1145 last_position_map: Option<Rc<PositionMap>>,
1146 expect_bounds_change: Option<Bounds<Pixels>>,
1147 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1148 tasks_update_task: Option<Task<()>>,
1149 breakpoint_store: Option<Entity<BreakpointStore>>,
1150 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1151 hovered_diff_hunk_row: Option<DisplayRow>,
1152 pull_diagnostics_task: Task<()>,
1153 in_project_search: bool,
1154 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1155 breadcrumb_header: Option<String>,
1156 focused_block: Option<FocusedBlock>,
1157 next_scroll_position: NextScrollCursorCenterTopBottom,
1158 addons: HashMap<TypeId, Box<dyn Addon>>,
1159 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1160 load_diff_task: Option<Shared<Task<()>>>,
1161 /// Whether we are temporarily displaying a diff other than git's
1162 temporary_diff_override: bool,
1163 selection_mark_mode: bool,
1164 toggle_fold_multiple_buffers: Task<()>,
1165 _scroll_cursor_center_top_bottom_task: Task<()>,
1166 serialize_selections: Task<()>,
1167 serialize_folds: Task<()>,
1168 mouse_cursor_hidden: bool,
1169 minimap: Option<Entity<Self>>,
1170 hide_mouse_mode: HideMouseMode,
1171 pub change_list: ChangeList,
1172 inline_value_cache: InlineValueCache,
1173 selection_drag_state: SelectionDragState,
1174 next_color_inlay_id: usize,
1175 colors: Option<LspColorData>,
1176 folding_newlines: Task<()>,
1177 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1178}
1179
1180#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1181enum NextScrollCursorCenterTopBottom {
1182 #[default]
1183 Center,
1184 Top,
1185 Bottom,
1186}
1187
1188impl NextScrollCursorCenterTopBottom {
1189 fn next(&self) -> Self {
1190 match self {
1191 Self::Center => Self::Top,
1192 Self::Top => Self::Bottom,
1193 Self::Bottom => Self::Center,
1194 }
1195 }
1196}
1197
1198#[derive(Clone)]
1199pub struct EditorSnapshot {
1200 pub mode: EditorMode,
1201 show_gutter: bool,
1202 show_line_numbers: Option<bool>,
1203 show_git_diff_gutter: Option<bool>,
1204 show_code_actions: Option<bool>,
1205 show_runnables: Option<bool>,
1206 show_breakpoints: Option<bool>,
1207 git_blame_gutter_max_author_length: Option<usize>,
1208 pub display_snapshot: DisplaySnapshot,
1209 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1210 is_focused: bool,
1211 scroll_anchor: ScrollAnchor,
1212 ongoing_scroll: OngoingScroll,
1213 current_line_highlight: CurrentLineHighlight,
1214 gutter_hovered: bool,
1215}
1216
1217#[derive(Default, Debug, Clone, Copy)]
1218pub struct GutterDimensions {
1219 pub left_padding: Pixels,
1220 pub right_padding: Pixels,
1221 pub width: Pixels,
1222 pub margin: Pixels,
1223 pub git_blame_entries_width: Option<Pixels>,
1224}
1225
1226impl GutterDimensions {
1227 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1228 Self {
1229 margin: Self::default_gutter_margin(font_id, font_size, cx),
1230 ..Default::default()
1231 }
1232 }
1233
1234 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1235 -cx.text_system().descent(font_id, font_size)
1236 }
1237 /// The full width of the space taken up by the gutter.
1238 pub fn full_width(&self) -> Pixels {
1239 self.margin + self.width
1240 }
1241
1242 /// The width of the space reserved for the fold indicators,
1243 /// use alongside 'justify_end' and `gutter_width` to
1244 /// right align content with the line numbers
1245 pub fn fold_area_width(&self) -> Pixels {
1246 self.margin + self.right_padding
1247 }
1248}
1249
1250struct CharacterDimensions {
1251 em_width: Pixels,
1252 em_advance: Pixels,
1253 line_height: Pixels,
1254}
1255
1256#[derive(Debug)]
1257pub struct RemoteSelection {
1258 pub replica_id: ReplicaId,
1259 pub selection: Selection<Anchor>,
1260 pub cursor_shape: CursorShape,
1261 pub collaborator_id: CollaboratorId,
1262 pub line_mode: bool,
1263 pub user_name: Option<SharedString>,
1264 pub color: PlayerColor,
1265}
1266
1267#[derive(Clone, Debug)]
1268struct SelectionHistoryEntry {
1269 selections: Arc<[Selection<Anchor>]>,
1270 select_next_state: Option<SelectNextState>,
1271 select_prev_state: Option<SelectNextState>,
1272 add_selections_state: Option<AddSelectionsState>,
1273}
1274
1275#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1276enum SelectionHistoryMode {
1277 Normal,
1278 Undoing,
1279 Redoing,
1280 Skipping,
1281}
1282
1283#[derive(Clone, PartialEq, Eq, Hash)]
1284struct HoveredCursor {
1285 replica_id: u16,
1286 selection_id: usize,
1287}
1288
1289impl Default for SelectionHistoryMode {
1290 fn default() -> Self {
1291 Self::Normal
1292 }
1293}
1294
1295#[derive(Debug)]
1296/// SelectionEffects controls the side-effects of updating the selection.
1297///
1298/// The default behaviour does "what you mostly want":
1299/// - it pushes to the nav history if the cursor moved by >10 lines
1300/// - it re-triggers completion requests
1301/// - it scrolls to fit
1302///
1303/// You might want to modify these behaviours. For example when doing a "jump"
1304/// like go to definition, we always want to add to nav history; but when scrolling
1305/// in vim mode we never do.
1306///
1307/// Similarly, you might want to disable scrolling if you don't want the viewport to
1308/// move.
1309#[derive(Clone)]
1310pub struct SelectionEffects {
1311 nav_history: Option<bool>,
1312 completions: bool,
1313 scroll: Option<Autoscroll>,
1314}
1315
1316impl Default for SelectionEffects {
1317 fn default() -> Self {
1318 Self {
1319 nav_history: None,
1320 completions: true,
1321 scroll: Some(Autoscroll::fit()),
1322 }
1323 }
1324}
1325impl SelectionEffects {
1326 pub fn scroll(scroll: Autoscroll) -> Self {
1327 Self {
1328 scroll: Some(scroll),
1329 ..Default::default()
1330 }
1331 }
1332
1333 pub fn no_scroll() -> Self {
1334 Self {
1335 scroll: None,
1336 ..Default::default()
1337 }
1338 }
1339
1340 pub fn completions(self, completions: bool) -> Self {
1341 Self {
1342 completions,
1343 ..self
1344 }
1345 }
1346
1347 pub fn nav_history(self, nav_history: bool) -> Self {
1348 Self {
1349 nav_history: Some(nav_history),
1350 ..self
1351 }
1352 }
1353}
1354
1355struct DeferredSelectionEffectsState {
1356 changed: bool,
1357 effects: SelectionEffects,
1358 old_cursor_position: Anchor,
1359 history_entry: SelectionHistoryEntry,
1360}
1361
1362#[derive(Default)]
1363struct SelectionHistory {
1364 #[allow(clippy::type_complexity)]
1365 selections_by_transaction:
1366 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1367 mode: SelectionHistoryMode,
1368 undo_stack: VecDeque<SelectionHistoryEntry>,
1369 redo_stack: VecDeque<SelectionHistoryEntry>,
1370}
1371
1372impl SelectionHistory {
1373 #[track_caller]
1374 fn insert_transaction(
1375 &mut self,
1376 transaction_id: TransactionId,
1377 selections: Arc<[Selection<Anchor>]>,
1378 ) {
1379 if selections.is_empty() {
1380 log::error!(
1381 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1382 std::panic::Location::caller()
1383 );
1384 return;
1385 }
1386 self.selections_by_transaction
1387 .insert(transaction_id, (selections, None));
1388 }
1389
1390 #[allow(clippy::type_complexity)]
1391 fn transaction(
1392 &self,
1393 transaction_id: TransactionId,
1394 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1395 self.selections_by_transaction.get(&transaction_id)
1396 }
1397
1398 #[allow(clippy::type_complexity)]
1399 fn transaction_mut(
1400 &mut self,
1401 transaction_id: TransactionId,
1402 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1403 self.selections_by_transaction.get_mut(&transaction_id)
1404 }
1405
1406 fn push(&mut self, entry: SelectionHistoryEntry) {
1407 if !entry.selections.is_empty() {
1408 match self.mode {
1409 SelectionHistoryMode::Normal => {
1410 self.push_undo(entry);
1411 self.redo_stack.clear();
1412 }
1413 SelectionHistoryMode::Undoing => self.push_redo(entry),
1414 SelectionHistoryMode::Redoing => self.push_undo(entry),
1415 SelectionHistoryMode::Skipping => {}
1416 }
1417 }
1418 }
1419
1420 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1421 if self
1422 .undo_stack
1423 .back()
1424 .is_none_or(|e| e.selections != entry.selections)
1425 {
1426 self.undo_stack.push_back(entry);
1427 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1428 self.undo_stack.pop_front();
1429 }
1430 }
1431 }
1432
1433 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1434 if self
1435 .redo_stack
1436 .back()
1437 .is_none_or(|e| e.selections != entry.selections)
1438 {
1439 self.redo_stack.push_back(entry);
1440 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1441 self.redo_stack.pop_front();
1442 }
1443 }
1444 }
1445}
1446
1447#[derive(Clone, Copy)]
1448pub struct RowHighlightOptions {
1449 pub autoscroll: bool,
1450 pub include_gutter: bool,
1451}
1452
1453impl Default for RowHighlightOptions {
1454 fn default() -> Self {
1455 Self {
1456 autoscroll: Default::default(),
1457 include_gutter: true,
1458 }
1459 }
1460}
1461
1462struct RowHighlight {
1463 index: usize,
1464 range: Range<Anchor>,
1465 color: Hsla,
1466 options: RowHighlightOptions,
1467 type_id: TypeId,
1468}
1469
1470#[derive(Clone, Debug)]
1471struct AddSelectionsState {
1472 groups: Vec<AddSelectionsGroup>,
1473}
1474
1475#[derive(Clone, Debug)]
1476struct AddSelectionsGroup {
1477 above: bool,
1478 stack: Vec<usize>,
1479}
1480
1481#[derive(Clone)]
1482struct SelectNextState {
1483 query: AhoCorasick,
1484 wordwise: bool,
1485 done: bool,
1486}
1487
1488impl std::fmt::Debug for SelectNextState {
1489 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1490 f.debug_struct(std::any::type_name::<Self>())
1491 .field("wordwise", &self.wordwise)
1492 .field("done", &self.done)
1493 .finish()
1494 }
1495}
1496
1497#[derive(Debug)]
1498struct AutocloseRegion {
1499 selection_id: usize,
1500 range: Range<Anchor>,
1501 pair: BracketPair,
1502}
1503
1504#[derive(Debug)]
1505struct SnippetState {
1506 ranges: Vec<Vec<Range<Anchor>>>,
1507 active_index: usize,
1508 choices: Vec<Option<Vec<String>>>,
1509}
1510
1511#[doc(hidden)]
1512pub struct RenameState {
1513 pub range: Range<Anchor>,
1514 pub old_name: Arc<str>,
1515 pub editor: Entity<Editor>,
1516 block_id: CustomBlockId,
1517}
1518
1519struct InvalidationStack<T>(Vec<T>);
1520
1521struct RegisteredEditPredictionProvider {
1522 provider: Arc<dyn EditPredictionProviderHandle>,
1523 _subscription: Subscription,
1524}
1525
1526#[derive(Debug, PartialEq, Eq)]
1527pub struct ActiveDiagnosticGroup {
1528 pub active_range: Range<Anchor>,
1529 pub active_message: String,
1530 pub group_id: usize,
1531 pub blocks: HashSet<CustomBlockId>,
1532}
1533
1534#[derive(Debug, PartialEq, Eq)]
1535
1536pub(crate) enum ActiveDiagnostic {
1537 None,
1538 All,
1539 Group(ActiveDiagnosticGroup),
1540}
1541
1542#[derive(Serialize, Deserialize, Clone, Debug)]
1543pub struct ClipboardSelection {
1544 /// The number of bytes in this selection.
1545 pub len: usize,
1546 /// Whether this was a full-line selection.
1547 pub is_entire_line: bool,
1548 /// The indentation of the first line when this content was originally copied.
1549 pub first_line_indent: u32,
1550}
1551
1552// selections, scroll behavior, was newest selection reversed
1553type SelectSyntaxNodeHistoryState = (
1554 Box<[Selection<usize>]>,
1555 SelectSyntaxNodeScrollBehavior,
1556 bool,
1557);
1558
1559#[derive(Default)]
1560struct SelectSyntaxNodeHistory {
1561 stack: Vec<SelectSyntaxNodeHistoryState>,
1562 // disable temporarily to allow changing selections without losing the stack
1563 pub disable_clearing: bool,
1564}
1565
1566impl SelectSyntaxNodeHistory {
1567 pub fn try_clear(&mut self) {
1568 if !self.disable_clearing {
1569 self.stack.clear();
1570 }
1571 }
1572
1573 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1574 self.stack.push(selection);
1575 }
1576
1577 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1578 self.stack.pop()
1579 }
1580}
1581
1582enum SelectSyntaxNodeScrollBehavior {
1583 CursorTop,
1584 FitSelection,
1585 CursorBottom,
1586}
1587
1588#[derive(Debug)]
1589pub(crate) struct NavigationData {
1590 cursor_anchor: Anchor,
1591 cursor_position: Point,
1592 scroll_anchor: ScrollAnchor,
1593 scroll_top_row: u32,
1594}
1595
1596#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1597pub enum GotoDefinitionKind {
1598 Symbol,
1599 Declaration,
1600 Type,
1601 Implementation,
1602}
1603
1604#[derive(Debug, Clone)]
1605enum InlayHintRefreshReason {
1606 ModifiersChanged(bool),
1607 Toggle(bool),
1608 SettingsChange(InlayHintSettings),
1609 NewLinesShown,
1610 BufferEdited(HashSet<Arc<Language>>),
1611 RefreshRequested,
1612 ExcerptsRemoved(Vec<ExcerptId>),
1613}
1614
1615impl InlayHintRefreshReason {
1616 fn description(&self) -> &'static str {
1617 match self {
1618 Self::ModifiersChanged(_) => "modifiers changed",
1619 Self::Toggle(_) => "toggle",
1620 Self::SettingsChange(_) => "settings change",
1621 Self::NewLinesShown => "new lines shown",
1622 Self::BufferEdited(_) => "buffer edited",
1623 Self::RefreshRequested => "refresh requested",
1624 Self::ExcerptsRemoved(_) => "excerpts removed",
1625 }
1626 }
1627}
1628
1629pub enum FormatTarget {
1630 Buffers(HashSet<Entity<Buffer>>),
1631 Ranges(Vec<Range<MultiBufferPoint>>),
1632}
1633
1634pub(crate) struct FocusedBlock {
1635 id: BlockId,
1636 focus_handle: WeakFocusHandle,
1637}
1638
1639#[derive(Clone)]
1640enum JumpData {
1641 MultiBufferRow {
1642 row: MultiBufferRow,
1643 line_offset_from_top: u32,
1644 },
1645 MultiBufferPoint {
1646 excerpt_id: ExcerptId,
1647 position: Point,
1648 anchor: text::Anchor,
1649 line_offset_from_top: u32,
1650 },
1651}
1652
1653pub enum MultibufferSelectionMode {
1654 First,
1655 All,
1656}
1657
1658#[derive(Clone, Copy, Debug, Default)]
1659pub struct RewrapOptions {
1660 pub override_language_settings: bool,
1661 pub preserve_existing_whitespace: bool,
1662}
1663
1664impl Editor {
1665 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1666 let buffer = cx.new(|cx| Buffer::local("", cx));
1667 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1668 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1669 }
1670
1671 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1672 let buffer = cx.new(|cx| Buffer::local("", cx));
1673 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1674 Self::new(EditorMode::full(), buffer, None, window, cx)
1675 }
1676
1677 pub fn auto_height(
1678 min_lines: usize,
1679 max_lines: usize,
1680 window: &mut Window,
1681 cx: &mut Context<Self>,
1682 ) -> Self {
1683 let buffer = cx.new(|cx| Buffer::local("", cx));
1684 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1685 Self::new(
1686 EditorMode::AutoHeight {
1687 min_lines,
1688 max_lines: Some(max_lines),
1689 },
1690 buffer,
1691 None,
1692 window,
1693 cx,
1694 )
1695 }
1696
1697 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1698 /// The editor grows as tall as needed to fit its content.
1699 pub fn auto_height_unbounded(
1700 min_lines: usize,
1701 window: &mut Window,
1702 cx: &mut Context<Self>,
1703 ) -> Self {
1704 let buffer = cx.new(|cx| Buffer::local("", cx));
1705 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1706 Self::new(
1707 EditorMode::AutoHeight {
1708 min_lines,
1709 max_lines: None,
1710 },
1711 buffer,
1712 None,
1713 window,
1714 cx,
1715 )
1716 }
1717
1718 pub fn for_buffer(
1719 buffer: Entity<Buffer>,
1720 project: Option<Entity<Project>>,
1721 window: &mut Window,
1722 cx: &mut Context<Self>,
1723 ) -> Self {
1724 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1725 Self::new(EditorMode::full(), buffer, project, window, cx)
1726 }
1727
1728 pub fn for_multibuffer(
1729 buffer: Entity<MultiBuffer>,
1730 project: Option<Entity<Project>>,
1731 window: &mut Window,
1732 cx: &mut Context<Self>,
1733 ) -> Self {
1734 Self::new(EditorMode::full(), buffer, project, window, cx)
1735 }
1736
1737 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1738 let mut clone = Self::new(
1739 self.mode.clone(),
1740 self.buffer.clone(),
1741 self.project.clone(),
1742 window,
1743 cx,
1744 );
1745 self.display_map.update(cx, |display_map, cx| {
1746 let snapshot = display_map.snapshot(cx);
1747 clone.display_map.update(cx, |display_map, cx| {
1748 display_map.set_state(&snapshot, cx);
1749 });
1750 });
1751 clone.folds_did_change(cx);
1752 clone.selections.clone_state(&self.selections);
1753 clone.scroll_manager.clone_state(&self.scroll_manager);
1754 clone.searchable = self.searchable;
1755 clone.read_only = self.read_only;
1756 clone
1757 }
1758
1759 pub fn new(
1760 mode: EditorMode,
1761 buffer: Entity<MultiBuffer>,
1762 project: Option<Entity<Project>>,
1763 window: &mut Window,
1764 cx: &mut Context<Self>,
1765 ) -> Self {
1766 Editor::new_internal(mode, buffer, project, None, window, cx)
1767 }
1768
1769 fn new_internal(
1770 mode: EditorMode,
1771 buffer: Entity<MultiBuffer>,
1772 project: Option<Entity<Project>>,
1773 display_map: Option<Entity<DisplayMap>>,
1774 window: &mut Window,
1775 cx: &mut Context<Self>,
1776 ) -> Self {
1777 debug_assert!(
1778 display_map.is_none() || mode.is_minimap(),
1779 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1780 );
1781
1782 let full_mode = mode.is_full();
1783 let is_minimap = mode.is_minimap();
1784 let diagnostics_max_severity = if full_mode {
1785 EditorSettings::get_global(cx)
1786 .diagnostics_max_severity
1787 .unwrap_or(DiagnosticSeverity::Hint)
1788 } else {
1789 DiagnosticSeverity::Off
1790 };
1791 let style = window.text_style();
1792 let font_size = style.font_size.to_pixels(window.rem_size());
1793 let editor = cx.entity().downgrade();
1794 let fold_placeholder = FoldPlaceholder {
1795 constrain_width: false,
1796 render: Arc::new(move |fold_id, fold_range, cx| {
1797 let editor = editor.clone();
1798 div()
1799 .id(fold_id)
1800 .bg(cx.theme().colors().ghost_element_background)
1801 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1802 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1803 .rounded_xs()
1804 .size_full()
1805 .cursor_pointer()
1806 .child("⋯")
1807 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1808 .on_click(move |_, _window, cx| {
1809 editor
1810 .update(cx, |editor, cx| {
1811 editor.unfold_ranges(
1812 &[fold_range.start..fold_range.end],
1813 true,
1814 false,
1815 cx,
1816 );
1817 cx.stop_propagation();
1818 })
1819 .ok();
1820 })
1821 .into_any()
1822 }),
1823 merge_adjacent: true,
1824 ..FoldPlaceholder::default()
1825 };
1826 let display_map = display_map.unwrap_or_else(|| {
1827 cx.new(|cx| {
1828 DisplayMap::new(
1829 buffer.clone(),
1830 style.font(),
1831 font_size,
1832 None,
1833 FILE_HEADER_HEIGHT,
1834 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1835 fold_placeholder,
1836 diagnostics_max_severity,
1837 cx,
1838 )
1839 })
1840 });
1841
1842 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1843
1844 let blink_manager = cx.new(|cx| {
1845 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1846 if is_minimap {
1847 blink_manager.disable(cx);
1848 }
1849 blink_manager
1850 });
1851
1852 let soft_wrap_mode_override =
1853 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1854
1855 let mut project_subscriptions = Vec::new();
1856 if full_mode && let Some(project) = project.as_ref() {
1857 project_subscriptions.push(cx.subscribe_in(
1858 project,
1859 window,
1860 |editor, _, event, window, cx| match event {
1861 project::Event::RefreshCodeLens => {
1862 // we always query lens with actions, without storing them, always refreshing them
1863 }
1864 project::Event::RefreshInlayHints => {
1865 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1866 }
1867 project::Event::LanguageServerAdded(..)
1868 | project::Event::LanguageServerRemoved(..) => {
1869 if editor.tasks_update_task.is_none() {
1870 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1871 }
1872 }
1873 project::Event::SnippetEdit(id, snippet_edits) => {
1874 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1875 let focus_handle = editor.focus_handle(cx);
1876 if focus_handle.is_focused(window) {
1877 let snapshot = buffer.read(cx).snapshot();
1878 for (range, snippet) in snippet_edits {
1879 let editor_range =
1880 language::range_from_lsp(*range).to_offset(&snapshot);
1881 editor
1882 .insert_snippet(
1883 &[editor_range],
1884 snippet.clone(),
1885 window,
1886 cx,
1887 )
1888 .ok();
1889 }
1890 }
1891 }
1892 }
1893 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1894 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1895 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1896 }
1897 }
1898
1899 project::Event::EntryRenamed(transaction) => {
1900 let Some(workspace) = editor.workspace() else {
1901 return;
1902 };
1903 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1904 else {
1905 return;
1906 };
1907 if active_editor.entity_id() == cx.entity_id() {
1908 let edited_buffers_already_open = {
1909 let other_editors: Vec<Entity<Editor>> = workspace
1910 .read(cx)
1911 .panes()
1912 .iter()
1913 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1914 .filter(|editor| editor.entity_id() != cx.entity_id())
1915 .collect();
1916
1917 transaction.0.keys().all(|buffer| {
1918 other_editors.iter().any(|editor| {
1919 let multi_buffer = editor.read(cx).buffer();
1920 multi_buffer.read(cx).is_singleton()
1921 && multi_buffer.read(cx).as_singleton().map_or(
1922 false,
1923 |singleton| {
1924 singleton.entity_id() == buffer.entity_id()
1925 },
1926 )
1927 })
1928 })
1929 };
1930
1931 if !edited_buffers_already_open {
1932 let workspace = workspace.downgrade();
1933 let transaction = transaction.clone();
1934 cx.defer_in(window, move |_, window, cx| {
1935 cx.spawn_in(window, async move |editor, cx| {
1936 Self::open_project_transaction(
1937 &editor,
1938 workspace,
1939 transaction,
1940 "Rename".to_string(),
1941 cx,
1942 )
1943 .await
1944 .ok()
1945 })
1946 .detach();
1947 });
1948 }
1949 }
1950 }
1951
1952 _ => {}
1953 },
1954 ));
1955 if let Some(task_inventory) = project
1956 .read(cx)
1957 .task_store()
1958 .read(cx)
1959 .task_inventory()
1960 .cloned()
1961 {
1962 project_subscriptions.push(cx.observe_in(
1963 &task_inventory,
1964 window,
1965 |editor, _, window, cx| {
1966 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1967 },
1968 ));
1969 };
1970
1971 project_subscriptions.push(cx.subscribe_in(
1972 &project.read(cx).breakpoint_store(),
1973 window,
1974 |editor, _, event, window, cx| match event {
1975 BreakpointStoreEvent::ClearDebugLines => {
1976 editor.clear_row_highlights::<ActiveDebugLine>();
1977 editor.refresh_inline_values(cx);
1978 }
1979 BreakpointStoreEvent::SetDebugLine => {
1980 if editor.go_to_active_debug_line(window, cx) {
1981 cx.stop_propagation();
1982 }
1983
1984 editor.refresh_inline_values(cx);
1985 }
1986 _ => {}
1987 },
1988 ));
1989 let git_store = project.read(cx).git_store().clone();
1990 let project = project.clone();
1991 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1992 if let GitStoreEvent::RepositoryUpdated(
1993 _,
1994 RepositoryEvent::Updated {
1995 new_instance: true, ..
1996 },
1997 _,
1998 ) = event
1999 {
2000 this.load_diff_task = Some(
2001 update_uncommitted_diff_for_buffer(
2002 cx.entity(),
2003 &project,
2004 this.buffer.read(cx).all_buffers(),
2005 this.buffer.clone(),
2006 cx,
2007 )
2008 .shared(),
2009 );
2010 }
2011 }));
2012 }
2013
2014 let buffer_snapshot = buffer.read(cx).snapshot(cx);
2015
2016 let inlay_hint_settings =
2017 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2018 let focus_handle = cx.focus_handle();
2019 if !is_minimap {
2020 cx.on_focus(&focus_handle, window, Self::handle_focus)
2021 .detach();
2022 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2023 .detach();
2024 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2025 .detach();
2026 cx.on_blur(&focus_handle, window, Self::handle_blur)
2027 .detach();
2028 cx.observe_pending_input(window, Self::observe_pending_input)
2029 .detach();
2030 }
2031
2032 let show_indent_guides =
2033 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2034 Some(false)
2035 } else {
2036 None
2037 };
2038
2039 let breakpoint_store = match (&mode, project.as_ref()) {
2040 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2041 _ => None,
2042 };
2043
2044 let mut code_action_providers = Vec::new();
2045 let mut load_uncommitted_diff = None;
2046 if let Some(project) = project.clone() {
2047 load_uncommitted_diff = Some(
2048 update_uncommitted_diff_for_buffer(
2049 cx.entity(),
2050 &project,
2051 buffer.read(cx).all_buffers(),
2052 buffer.clone(),
2053 cx,
2054 )
2055 .shared(),
2056 );
2057 code_action_providers.push(Rc::new(project) as Rc<_>);
2058 }
2059
2060 let mut editor = Self {
2061 focus_handle,
2062 show_cursor_when_unfocused: false,
2063 last_focused_descendant: None,
2064 buffer: buffer.clone(),
2065 display_map: display_map.clone(),
2066 placeholder_display_map: None,
2067 selections,
2068 scroll_manager: ScrollManager::new(cx),
2069 columnar_selection_state: None,
2070 add_selections_state: None,
2071 select_next_state: None,
2072 select_prev_state: None,
2073 selection_history: SelectionHistory::default(),
2074 defer_selection_effects: false,
2075 deferred_selection_effects_state: None,
2076 autoclose_regions: Vec::new(),
2077 snippet_stack: InvalidationStack::default(),
2078 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2079 ime_transaction: None,
2080 active_diagnostics: ActiveDiagnostic::None,
2081 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2082 inline_diagnostics_update: Task::ready(()),
2083 inline_diagnostics: Vec::new(),
2084 soft_wrap_mode_override,
2085 diagnostics_max_severity,
2086 hard_wrap: None,
2087 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2088 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2089 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2090 project,
2091 blink_manager: blink_manager.clone(),
2092 show_local_selections: true,
2093 show_scrollbars: ScrollbarAxes {
2094 horizontal: full_mode,
2095 vertical: full_mode,
2096 },
2097 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2098 offset_content: !matches!(mode, EditorMode::SingleLine),
2099 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2100 show_gutter: full_mode,
2101 show_line_numbers: (!full_mode).then_some(false),
2102 use_relative_line_numbers: None,
2103 disable_expand_excerpt_buttons: !full_mode,
2104 show_git_diff_gutter: None,
2105 show_code_actions: None,
2106 show_runnables: None,
2107 show_breakpoints: None,
2108 show_wrap_guides: None,
2109 show_indent_guides,
2110 highlight_order: 0,
2111 highlighted_rows: HashMap::default(),
2112 background_highlights: HashMap::default(),
2113 gutter_highlights: HashMap::default(),
2114 scrollbar_marker_state: ScrollbarMarkerState::default(),
2115 active_indent_guides_state: ActiveIndentGuidesState::default(),
2116 nav_history: None,
2117 context_menu: RefCell::new(None),
2118 context_menu_options: None,
2119 mouse_context_menu: None,
2120 completion_tasks: Vec::new(),
2121 inline_blame_popover: None,
2122 inline_blame_popover_show_task: None,
2123 signature_help_state: SignatureHelpState::default(),
2124 auto_signature_help: None,
2125 find_all_references_task_sources: Vec::new(),
2126 next_completion_id: 0,
2127 next_inlay_id: 0,
2128 code_action_providers,
2129 available_code_actions: None,
2130 code_actions_task: None,
2131 quick_selection_highlight_task: None,
2132 debounced_selection_highlight_task: None,
2133 document_highlights_task: None,
2134 linked_editing_range_task: None,
2135 pending_rename: None,
2136 searchable: !is_minimap,
2137 cursor_shape: EditorSettings::get_global(cx)
2138 .cursor_shape
2139 .unwrap_or_default(),
2140 current_line_highlight: None,
2141 autoindent_mode: Some(AutoindentMode::EachLine),
2142 collapse_matches: false,
2143 workspace: None,
2144 input_enabled: !is_minimap,
2145 use_modal_editing: full_mode,
2146 read_only: is_minimap,
2147 use_autoclose: true,
2148 use_auto_surround: true,
2149 auto_replace_emoji_shortcode: false,
2150 jsx_tag_auto_close_enabled_in_any_buffer: false,
2151 leader_id: None,
2152 remote_id: None,
2153 hover_state: HoverState::default(),
2154 pending_mouse_down: None,
2155 hovered_link_state: None,
2156 edit_prediction_provider: None,
2157 active_edit_prediction: None,
2158 stale_edit_prediction_in_menu: None,
2159 edit_prediction_preview: EditPredictionPreview::Inactive {
2160 released_too_fast: false,
2161 },
2162 inline_diagnostics_enabled: full_mode,
2163 diagnostics_enabled: full_mode,
2164 word_completions_enabled: full_mode,
2165 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2166 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2167 gutter_hovered: false,
2168 pixel_position_of_newest_cursor: None,
2169 last_bounds: None,
2170 last_position_map: None,
2171 expect_bounds_change: None,
2172 gutter_dimensions: GutterDimensions::default(),
2173 style: None,
2174 show_cursor_names: false,
2175 hovered_cursors: HashMap::default(),
2176 next_editor_action_id: EditorActionId::default(),
2177 editor_actions: Rc::default(),
2178 edit_predictions_hidden_for_vim_mode: false,
2179 show_edit_predictions_override: None,
2180 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2181 edit_prediction_settings: EditPredictionSettings::Disabled,
2182 edit_prediction_indent_conflict: false,
2183 edit_prediction_requires_modifier_in_indent_conflict: true,
2184 custom_context_menu: None,
2185 show_git_blame_gutter: false,
2186 show_git_blame_inline: false,
2187 show_selection_menu: None,
2188 show_git_blame_inline_delay_task: None,
2189 git_blame_inline_enabled: full_mode
2190 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2191 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2192 serialize_dirty_buffers: !is_minimap
2193 && ProjectSettings::get_global(cx)
2194 .session
2195 .restore_unsaved_buffers,
2196 blame: None,
2197 blame_subscription: None,
2198 tasks: BTreeMap::default(),
2199
2200 breakpoint_store,
2201 gutter_breakpoint_indicator: (None, None),
2202 hovered_diff_hunk_row: None,
2203 _subscriptions: (!is_minimap)
2204 .then(|| {
2205 vec![
2206 cx.observe(&buffer, Self::on_buffer_changed),
2207 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2208 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2209 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2210 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2211 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2212 cx.observe_window_activation(window, |editor, window, cx| {
2213 let active = window.is_window_active();
2214 editor.blink_manager.update(cx, |blink_manager, cx| {
2215 if active {
2216 blink_manager.enable(cx);
2217 } else {
2218 blink_manager.disable(cx);
2219 }
2220 });
2221 if active {
2222 editor.show_mouse_cursor(cx);
2223 }
2224 }),
2225 ]
2226 })
2227 .unwrap_or_default(),
2228 tasks_update_task: None,
2229 pull_diagnostics_task: Task::ready(()),
2230 colors: None,
2231 next_color_inlay_id: 0,
2232 linked_edit_ranges: Default::default(),
2233 in_project_search: false,
2234 previous_search_ranges: None,
2235 breadcrumb_header: None,
2236 focused_block: None,
2237 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2238 addons: HashMap::default(),
2239 registered_buffers: HashMap::default(),
2240 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2241 selection_mark_mode: false,
2242 toggle_fold_multiple_buffers: Task::ready(()),
2243 serialize_selections: Task::ready(()),
2244 serialize_folds: Task::ready(()),
2245 text_style_refinement: None,
2246 load_diff_task: load_uncommitted_diff,
2247 temporary_diff_override: false,
2248 mouse_cursor_hidden: false,
2249 minimap: None,
2250 hide_mouse_mode: EditorSettings::get_global(cx)
2251 .hide_mouse
2252 .unwrap_or_default(),
2253 change_list: ChangeList::new(),
2254 mode,
2255 selection_drag_state: SelectionDragState::None,
2256 folding_newlines: Task::ready(()),
2257 lookup_key: None,
2258 };
2259
2260 if is_minimap {
2261 return editor;
2262 }
2263
2264 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2265 editor
2266 ._subscriptions
2267 .push(cx.observe(breakpoints, |_, _, cx| {
2268 cx.notify();
2269 }));
2270 }
2271 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2272 editor._subscriptions.extend(project_subscriptions);
2273
2274 editor._subscriptions.push(cx.subscribe_in(
2275 &cx.entity(),
2276 window,
2277 |editor, _, e: &EditorEvent, window, cx| match e {
2278 EditorEvent::ScrollPositionChanged { local, .. } => {
2279 if *local {
2280 let new_anchor = editor.scroll_manager.anchor();
2281 let snapshot = editor.snapshot(window, cx);
2282 editor.update_restoration_data(cx, move |data| {
2283 data.scroll_position = (
2284 new_anchor.top_row(&snapshot.buffer_snapshot),
2285 new_anchor.offset,
2286 );
2287 });
2288 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2289 editor.inline_blame_popover.take();
2290 }
2291 }
2292 EditorEvent::Edited { .. } => {
2293 if !vim_enabled(cx) {
2294 let (map, selections) = editor.selections.all_adjusted_display(cx);
2295 let pop_state = editor
2296 .change_list
2297 .last()
2298 .map(|previous| {
2299 previous.len() == selections.len()
2300 && previous.iter().enumerate().all(|(ix, p)| {
2301 p.to_display_point(&map).row()
2302 == selections[ix].head().row()
2303 })
2304 })
2305 .unwrap_or(false);
2306 let new_positions = selections
2307 .into_iter()
2308 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2309 .collect();
2310 editor
2311 .change_list
2312 .push_to_change_list(pop_state, new_positions);
2313 }
2314 }
2315 _ => (),
2316 },
2317 ));
2318
2319 if let Some(dap_store) = editor
2320 .project
2321 .as_ref()
2322 .map(|project| project.read(cx).dap_store())
2323 {
2324 let weak_editor = cx.weak_entity();
2325
2326 editor
2327 ._subscriptions
2328 .push(
2329 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2330 let session_entity = cx.entity();
2331 weak_editor
2332 .update(cx, |editor, cx| {
2333 editor._subscriptions.push(
2334 cx.subscribe(&session_entity, Self::on_debug_session_event),
2335 );
2336 })
2337 .ok();
2338 }),
2339 );
2340
2341 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2342 editor
2343 ._subscriptions
2344 .push(cx.subscribe(&session, Self::on_debug_session_event));
2345 }
2346 }
2347
2348 // skip adding the initial selection to selection history
2349 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2350 editor.end_selection(window, cx);
2351 editor.selection_history.mode = SelectionHistoryMode::Normal;
2352
2353 editor.scroll_manager.show_scrollbars(window, cx);
2354 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2355
2356 if full_mode {
2357 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2358 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2359
2360 if editor.git_blame_inline_enabled {
2361 editor.start_git_blame_inline(false, window, cx);
2362 }
2363
2364 editor.go_to_active_debug_line(window, cx);
2365
2366 if let Some(buffer) = buffer.read(cx).as_singleton()
2367 && let Some(project) = editor.project()
2368 {
2369 let handle = project.update(cx, |project, cx| {
2370 project.register_buffer_with_language_servers(&buffer, cx)
2371 });
2372 editor
2373 .registered_buffers
2374 .insert(buffer.read(cx).remote_id(), handle);
2375 }
2376
2377 editor.minimap =
2378 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2379 editor.colors = Some(LspColorData::new(cx));
2380 editor.update_lsp_data(false, None, window, cx);
2381 }
2382
2383 if editor.mode.is_full() {
2384 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2385 }
2386
2387 editor
2388 }
2389
2390 pub fn deploy_mouse_context_menu(
2391 &mut self,
2392 position: gpui::Point<Pixels>,
2393 context_menu: Entity<ContextMenu>,
2394 window: &mut Window,
2395 cx: &mut Context<Self>,
2396 ) {
2397 self.mouse_context_menu = Some(MouseContextMenu::new(
2398 self,
2399 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2400 context_menu,
2401 window,
2402 cx,
2403 ));
2404 }
2405
2406 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2407 self.mouse_context_menu
2408 .as_ref()
2409 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2410 }
2411
2412 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2413 if self
2414 .selections
2415 .pending_anchor()
2416 .is_some_and(|pending_selection| {
2417 let snapshot = self.buffer().read(cx).snapshot(cx);
2418 pending_selection.range().includes(range, &snapshot)
2419 })
2420 {
2421 return true;
2422 }
2423
2424 self.selections
2425 .disjoint_in_range::<usize>(range.clone(), cx)
2426 .into_iter()
2427 .any(|selection| {
2428 // This is needed to cover a corner case, if we just check for an existing
2429 // selection in the fold range, having a cursor at the start of the fold
2430 // marks it as selected. Non-empty selections don't cause this.
2431 let length = selection.end - selection.start;
2432 length > 0
2433 })
2434 }
2435
2436 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2437 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2438 }
2439
2440 fn key_context_internal(
2441 &self,
2442 has_active_edit_prediction: bool,
2443 window: &Window,
2444 cx: &App,
2445 ) -> KeyContext {
2446 let mut key_context = KeyContext::new_with_defaults();
2447 key_context.add("Editor");
2448 let mode = match self.mode {
2449 EditorMode::SingleLine => "single_line",
2450 EditorMode::AutoHeight { .. } => "auto_height",
2451 EditorMode::Minimap { .. } => "minimap",
2452 EditorMode::Full { .. } => "full",
2453 };
2454
2455 if EditorSettings::jupyter_enabled(cx) {
2456 key_context.add("jupyter");
2457 }
2458
2459 key_context.set("mode", mode);
2460 if self.pending_rename.is_some() {
2461 key_context.add("renaming");
2462 }
2463
2464 match self.context_menu.borrow().as_ref() {
2465 Some(CodeContextMenu::Completions(menu)) => {
2466 if menu.visible() {
2467 key_context.add("menu");
2468 key_context.add("showing_completions");
2469 }
2470 }
2471 Some(CodeContextMenu::CodeActions(menu)) => {
2472 if menu.visible() {
2473 key_context.add("menu");
2474 key_context.add("showing_code_actions")
2475 }
2476 }
2477 None => {}
2478 }
2479
2480 if self.signature_help_state.has_multiple_signatures() {
2481 key_context.add("showing_signature_help");
2482 }
2483
2484 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2485 if !self.focus_handle(cx).contains_focused(window, cx)
2486 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2487 {
2488 for addon in self.addons.values() {
2489 addon.extend_key_context(&mut key_context, cx)
2490 }
2491 }
2492
2493 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2494 if let Some(extension) = singleton_buffer
2495 .read(cx)
2496 .file()
2497 .and_then(|file| file.path().extension()?.to_str())
2498 {
2499 key_context.set("extension", extension.to_string());
2500 }
2501 } else {
2502 key_context.add("multibuffer");
2503 }
2504
2505 if has_active_edit_prediction {
2506 if self.edit_prediction_in_conflict() {
2507 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2508 } else {
2509 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2510 key_context.add("copilot_suggestion");
2511 }
2512 }
2513
2514 if self.selection_mark_mode {
2515 key_context.add("selection_mode");
2516 }
2517
2518 key_context
2519 }
2520
2521 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2522 if self.mouse_cursor_hidden {
2523 self.mouse_cursor_hidden = false;
2524 cx.notify();
2525 }
2526 }
2527
2528 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2529 let hide_mouse_cursor = match origin {
2530 HideMouseCursorOrigin::TypingAction => {
2531 matches!(
2532 self.hide_mouse_mode,
2533 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2534 )
2535 }
2536 HideMouseCursorOrigin::MovementAction => {
2537 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2538 }
2539 };
2540 if self.mouse_cursor_hidden != hide_mouse_cursor {
2541 self.mouse_cursor_hidden = hide_mouse_cursor;
2542 cx.notify();
2543 }
2544 }
2545
2546 pub fn edit_prediction_in_conflict(&self) -> bool {
2547 if !self.show_edit_predictions_in_menu() {
2548 return false;
2549 }
2550
2551 let showing_completions = self
2552 .context_menu
2553 .borrow()
2554 .as_ref()
2555 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2556
2557 showing_completions
2558 || self.edit_prediction_requires_modifier()
2559 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2560 // bindings to insert tab characters.
2561 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2562 }
2563
2564 pub fn accept_edit_prediction_keybind(
2565 &self,
2566 accept_partial: bool,
2567 window: &Window,
2568 cx: &App,
2569 ) -> AcceptEditPredictionBinding {
2570 let key_context = self.key_context_internal(true, window, cx);
2571 let in_conflict = self.edit_prediction_in_conflict();
2572
2573 let bindings = if accept_partial {
2574 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2575 } else {
2576 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2577 };
2578
2579 // TODO: if the binding contains multiple keystrokes, display all of them, not
2580 // just the first one.
2581 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2582 !in_conflict
2583 || binding
2584 .keystrokes()
2585 .first()
2586 .is_some_and(|keystroke| keystroke.modifiers().modified())
2587 }))
2588 }
2589
2590 pub fn new_file(
2591 workspace: &mut Workspace,
2592 _: &workspace::NewFile,
2593 window: &mut Window,
2594 cx: &mut Context<Workspace>,
2595 ) {
2596 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2597 "Failed to create buffer",
2598 window,
2599 cx,
2600 |e, _, _| match e.error_code() {
2601 ErrorCode::RemoteUpgradeRequired => Some(format!(
2602 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2603 e.error_tag("required").unwrap_or("the latest version")
2604 )),
2605 _ => None,
2606 },
2607 );
2608 }
2609
2610 pub fn new_in_workspace(
2611 workspace: &mut Workspace,
2612 window: &mut Window,
2613 cx: &mut Context<Workspace>,
2614 ) -> Task<Result<Entity<Editor>>> {
2615 let project = workspace.project().clone();
2616 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2617
2618 cx.spawn_in(window, async move |workspace, cx| {
2619 let buffer = create.await?;
2620 workspace.update_in(cx, |workspace, window, cx| {
2621 let editor =
2622 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2623 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2624 editor
2625 })
2626 })
2627 }
2628
2629 fn new_file_vertical(
2630 workspace: &mut Workspace,
2631 _: &workspace::NewFileSplitVertical,
2632 window: &mut Window,
2633 cx: &mut Context<Workspace>,
2634 ) {
2635 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2636 }
2637
2638 fn new_file_horizontal(
2639 workspace: &mut Workspace,
2640 _: &workspace::NewFileSplitHorizontal,
2641 window: &mut Window,
2642 cx: &mut Context<Workspace>,
2643 ) {
2644 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2645 }
2646
2647 fn new_file_in_direction(
2648 workspace: &mut Workspace,
2649 direction: SplitDirection,
2650 window: &mut Window,
2651 cx: &mut Context<Workspace>,
2652 ) {
2653 let project = workspace.project().clone();
2654 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2655
2656 cx.spawn_in(window, async move |workspace, cx| {
2657 let buffer = create.await?;
2658 workspace.update_in(cx, move |workspace, window, cx| {
2659 workspace.split_item(
2660 direction,
2661 Box::new(
2662 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2663 ),
2664 window,
2665 cx,
2666 )
2667 })?;
2668 anyhow::Ok(())
2669 })
2670 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2671 match e.error_code() {
2672 ErrorCode::RemoteUpgradeRequired => Some(format!(
2673 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2674 e.error_tag("required").unwrap_or("the latest version")
2675 )),
2676 _ => None,
2677 }
2678 });
2679 }
2680
2681 pub fn leader_id(&self) -> Option<CollaboratorId> {
2682 self.leader_id
2683 }
2684
2685 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2686 &self.buffer
2687 }
2688
2689 pub fn project(&self) -> Option<&Entity<Project>> {
2690 self.project.as_ref()
2691 }
2692
2693 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2694 self.workspace.as_ref()?.0.upgrade()
2695 }
2696
2697 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2698 self.buffer().read(cx).title(cx)
2699 }
2700
2701 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2702 let git_blame_gutter_max_author_length = self
2703 .render_git_blame_gutter(cx)
2704 .then(|| {
2705 if let Some(blame) = self.blame.as_ref() {
2706 let max_author_length =
2707 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2708 Some(max_author_length)
2709 } else {
2710 None
2711 }
2712 })
2713 .flatten();
2714
2715 EditorSnapshot {
2716 mode: self.mode.clone(),
2717 show_gutter: self.show_gutter,
2718 show_line_numbers: self.show_line_numbers,
2719 show_git_diff_gutter: self.show_git_diff_gutter,
2720 show_code_actions: self.show_code_actions,
2721 show_runnables: self.show_runnables,
2722 show_breakpoints: self.show_breakpoints,
2723 git_blame_gutter_max_author_length,
2724 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2725 placeholder_display_snapshot: self
2726 .placeholder_display_map
2727 .as_ref()
2728 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2729 scroll_anchor: self.scroll_manager.anchor(),
2730 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2731 is_focused: self.focus_handle.is_focused(window),
2732 current_line_highlight: self
2733 .current_line_highlight
2734 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2735 gutter_hovered: self.gutter_hovered,
2736 }
2737 }
2738
2739 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2740 self.buffer.read(cx).language_at(point, cx)
2741 }
2742
2743 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2744 self.buffer.read(cx).read(cx).file_at(point).cloned()
2745 }
2746
2747 pub fn active_excerpt(
2748 &self,
2749 cx: &App,
2750 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2751 self.buffer
2752 .read(cx)
2753 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2754 }
2755
2756 pub fn mode(&self) -> &EditorMode {
2757 &self.mode
2758 }
2759
2760 pub fn set_mode(&mut self, mode: EditorMode) {
2761 self.mode = mode;
2762 }
2763
2764 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2765 self.collaboration_hub.as_deref()
2766 }
2767
2768 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2769 self.collaboration_hub = Some(hub);
2770 }
2771
2772 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2773 self.in_project_search = in_project_search;
2774 }
2775
2776 pub fn set_custom_context_menu(
2777 &mut self,
2778 f: impl 'static
2779 + Fn(
2780 &mut Self,
2781 DisplayPoint,
2782 &mut Window,
2783 &mut Context<Self>,
2784 ) -> Option<Entity<ui::ContextMenu>>,
2785 ) {
2786 self.custom_context_menu = Some(Box::new(f))
2787 }
2788
2789 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2790 self.completion_provider = provider;
2791 }
2792
2793 #[cfg(any(test, feature = "test-support"))]
2794 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2795 self.completion_provider.clone()
2796 }
2797
2798 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2799 self.semantics_provider.clone()
2800 }
2801
2802 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2803 self.semantics_provider = provider;
2804 }
2805
2806 pub fn set_edit_prediction_provider<T>(
2807 &mut self,
2808 provider: Option<Entity<T>>,
2809 window: &mut Window,
2810 cx: &mut Context<Self>,
2811 ) where
2812 T: EditPredictionProvider,
2813 {
2814 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2815 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2816 if this.focus_handle.is_focused(window) {
2817 this.update_visible_edit_prediction(window, cx);
2818 }
2819 }),
2820 provider: Arc::new(provider),
2821 });
2822 self.update_edit_prediction_settings(cx);
2823 self.refresh_edit_prediction(false, false, window, cx);
2824 }
2825
2826 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2827 self.placeholder_display_map
2828 .as_ref()
2829 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2830 }
2831
2832 pub fn set_placeholder_text(
2833 &mut self,
2834 placeholder_text: &str,
2835 window: &mut Window,
2836 cx: &mut Context<Self>,
2837 ) {
2838 let multibuffer = cx
2839 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2840
2841 let style = window.text_style();
2842
2843 self.placeholder_display_map = Some(cx.new(|cx| {
2844 DisplayMap::new(
2845 multibuffer,
2846 style.font(),
2847 style.font_size.to_pixels(window.rem_size()),
2848 None,
2849 FILE_HEADER_HEIGHT,
2850 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2851 Default::default(),
2852 DiagnosticSeverity::Off,
2853 cx,
2854 )
2855 }));
2856 cx.notify();
2857 }
2858
2859 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2860 self.cursor_shape = cursor_shape;
2861
2862 // Disrupt blink for immediate user feedback that the cursor shape has changed
2863 self.blink_manager.update(cx, BlinkManager::show_cursor);
2864
2865 cx.notify();
2866 }
2867
2868 pub fn set_current_line_highlight(
2869 &mut self,
2870 current_line_highlight: Option<CurrentLineHighlight>,
2871 ) {
2872 self.current_line_highlight = current_line_highlight;
2873 }
2874
2875 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2876 self.collapse_matches = collapse_matches;
2877 }
2878
2879 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2880 let buffers = self.buffer.read(cx).all_buffers();
2881 let Some(project) = self.project.as_ref() else {
2882 return;
2883 };
2884 project.update(cx, |project, cx| {
2885 for buffer in buffers {
2886 self.registered_buffers
2887 .entry(buffer.read(cx).remote_id())
2888 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2889 }
2890 })
2891 }
2892
2893 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2894 if self.collapse_matches {
2895 return range.start..range.start;
2896 }
2897 range.clone()
2898 }
2899
2900 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2901 if self.display_map.read(cx).clip_at_line_ends != clip {
2902 self.display_map
2903 .update(cx, |map, _| map.clip_at_line_ends = clip);
2904 }
2905 }
2906
2907 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2908 self.input_enabled = input_enabled;
2909 }
2910
2911 pub fn set_edit_predictions_hidden_for_vim_mode(
2912 &mut self,
2913 hidden: bool,
2914 window: &mut Window,
2915 cx: &mut Context<Self>,
2916 ) {
2917 if hidden != self.edit_predictions_hidden_for_vim_mode {
2918 self.edit_predictions_hidden_for_vim_mode = hidden;
2919 if hidden {
2920 self.update_visible_edit_prediction(window, cx);
2921 } else {
2922 self.refresh_edit_prediction(true, false, window, cx);
2923 }
2924 }
2925 }
2926
2927 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2928 self.menu_edit_predictions_policy = value;
2929 }
2930
2931 pub fn set_autoindent(&mut self, autoindent: bool) {
2932 if autoindent {
2933 self.autoindent_mode = Some(AutoindentMode::EachLine);
2934 } else {
2935 self.autoindent_mode = None;
2936 }
2937 }
2938
2939 pub fn read_only(&self, cx: &App) -> bool {
2940 self.read_only || self.buffer.read(cx).read_only()
2941 }
2942
2943 pub fn set_read_only(&mut self, read_only: bool) {
2944 self.read_only = read_only;
2945 }
2946
2947 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2948 self.use_autoclose = autoclose;
2949 }
2950
2951 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2952 self.use_auto_surround = auto_surround;
2953 }
2954
2955 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2956 self.auto_replace_emoji_shortcode = auto_replace;
2957 }
2958
2959 pub fn toggle_edit_predictions(
2960 &mut self,
2961 _: &ToggleEditPrediction,
2962 window: &mut Window,
2963 cx: &mut Context<Self>,
2964 ) {
2965 if self.show_edit_predictions_override.is_some() {
2966 self.set_show_edit_predictions(None, window, cx);
2967 } else {
2968 let show_edit_predictions = !self.edit_predictions_enabled();
2969 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2970 }
2971 }
2972
2973 pub fn set_show_edit_predictions(
2974 &mut self,
2975 show_edit_predictions: Option<bool>,
2976 window: &mut Window,
2977 cx: &mut Context<Self>,
2978 ) {
2979 self.show_edit_predictions_override = show_edit_predictions;
2980 self.update_edit_prediction_settings(cx);
2981
2982 if let Some(false) = show_edit_predictions {
2983 self.discard_edit_prediction(false, cx);
2984 } else {
2985 self.refresh_edit_prediction(false, true, window, cx);
2986 }
2987 }
2988
2989 fn edit_predictions_disabled_in_scope(
2990 &self,
2991 buffer: &Entity<Buffer>,
2992 buffer_position: language::Anchor,
2993 cx: &App,
2994 ) -> bool {
2995 let snapshot = buffer.read(cx).snapshot();
2996 let settings = snapshot.settings_at(buffer_position, cx);
2997
2998 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2999 return false;
3000 };
3001
3002 scope.override_name().is_some_and(|scope_name| {
3003 settings
3004 .edit_predictions_disabled_in
3005 .iter()
3006 .any(|s| s == scope_name)
3007 })
3008 }
3009
3010 pub fn set_use_modal_editing(&mut self, to: bool) {
3011 self.use_modal_editing = to;
3012 }
3013
3014 pub fn use_modal_editing(&self) -> bool {
3015 self.use_modal_editing
3016 }
3017
3018 fn selections_did_change(
3019 &mut self,
3020 local: bool,
3021 old_cursor_position: &Anchor,
3022 effects: SelectionEffects,
3023 window: &mut Window,
3024 cx: &mut Context<Self>,
3025 ) {
3026 window.invalidate_character_coordinates();
3027
3028 // Copy selections to primary selection buffer
3029 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3030 if local {
3031 let selections = self.selections.all::<usize>(cx);
3032 let buffer_handle = self.buffer.read(cx).read(cx);
3033
3034 let mut text = String::new();
3035 for (index, selection) in selections.iter().enumerate() {
3036 let text_for_selection = buffer_handle
3037 .text_for_range(selection.start..selection.end)
3038 .collect::<String>();
3039
3040 text.push_str(&text_for_selection);
3041 if index != selections.len() - 1 {
3042 text.push('\n');
3043 }
3044 }
3045
3046 if !text.is_empty() {
3047 cx.write_to_primary(ClipboardItem::new_string(text));
3048 }
3049 }
3050
3051 let selection_anchors = self.selections.disjoint_anchors_arc();
3052
3053 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3054 self.buffer.update(cx, |buffer, cx| {
3055 buffer.set_active_selections(
3056 &selection_anchors,
3057 self.selections.line_mode,
3058 self.cursor_shape,
3059 cx,
3060 )
3061 });
3062 }
3063 let display_map = self
3064 .display_map
3065 .update(cx, |display_map, cx| display_map.snapshot(cx));
3066 let buffer = &display_map.buffer_snapshot;
3067 if self.selections.count() == 1 {
3068 self.add_selections_state = None;
3069 }
3070 self.select_next_state = None;
3071 self.select_prev_state = None;
3072 self.select_syntax_node_history.try_clear();
3073 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3074 self.snippet_stack.invalidate(&selection_anchors, buffer);
3075 self.take_rename(false, window, cx);
3076
3077 let newest_selection = self.selections.newest_anchor();
3078 let new_cursor_position = newest_selection.head();
3079 let selection_start = newest_selection.start;
3080
3081 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3082 self.push_to_nav_history(
3083 *old_cursor_position,
3084 Some(new_cursor_position.to_point(buffer)),
3085 false,
3086 effects.nav_history == Some(true),
3087 cx,
3088 );
3089 }
3090
3091 if local {
3092 if let Some(buffer_id) = new_cursor_position.buffer_id
3093 && !self.registered_buffers.contains_key(&buffer_id)
3094 && let Some(project) = self.project.as_ref()
3095 {
3096 project.update(cx, |project, cx| {
3097 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3098 return;
3099 };
3100 self.registered_buffers.insert(
3101 buffer_id,
3102 project.register_buffer_with_language_servers(&buffer, cx),
3103 );
3104 })
3105 }
3106
3107 let mut context_menu = self.context_menu.borrow_mut();
3108 let completion_menu = match context_menu.as_ref() {
3109 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3110 Some(CodeContextMenu::CodeActions(_)) => {
3111 *context_menu = None;
3112 None
3113 }
3114 None => None,
3115 };
3116 let completion_position = completion_menu.map(|menu| menu.initial_position);
3117 drop(context_menu);
3118
3119 if effects.completions
3120 && let Some(completion_position) = completion_position
3121 {
3122 let start_offset = selection_start.to_offset(buffer);
3123 let position_matches = start_offset == completion_position.to_offset(buffer);
3124 let continue_showing = if position_matches {
3125 if self.snippet_stack.is_empty() {
3126 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3127 == Some(CharKind::Word)
3128 } else {
3129 // Snippet choices can be shown even when the cursor is in whitespace.
3130 // Dismissing the menu with actions like backspace is handled by
3131 // invalidation regions.
3132 true
3133 }
3134 } else {
3135 false
3136 };
3137
3138 if continue_showing {
3139 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3140 } else {
3141 self.hide_context_menu(window, cx);
3142 }
3143 }
3144
3145 hide_hover(self, cx);
3146
3147 if old_cursor_position.to_display_point(&display_map).row()
3148 != new_cursor_position.to_display_point(&display_map).row()
3149 {
3150 self.available_code_actions.take();
3151 }
3152 self.refresh_code_actions(window, cx);
3153 self.refresh_document_highlights(cx);
3154 self.refresh_selected_text_highlights(false, window, cx);
3155 refresh_matching_bracket_highlights(self, window, cx);
3156 self.update_visible_edit_prediction(window, cx);
3157 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3158 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3159 self.inline_blame_popover.take();
3160 if self.git_blame_inline_enabled {
3161 self.start_inline_blame_timer(window, cx);
3162 }
3163 }
3164
3165 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3166 cx.emit(EditorEvent::SelectionsChanged { local });
3167
3168 let selections = &self.selections.disjoint_anchors_arc();
3169 if selections.len() == 1 {
3170 cx.emit(SearchEvent::ActiveMatchChanged)
3171 }
3172 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3173 let inmemory_selections = selections
3174 .iter()
3175 .map(|s| {
3176 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3177 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3178 })
3179 .collect();
3180 self.update_restoration_data(cx, |data| {
3181 data.selections = inmemory_selections;
3182 });
3183
3184 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3185 && let Some(workspace_id) =
3186 self.workspace.as_ref().and_then(|workspace| workspace.1)
3187 {
3188 let snapshot = self.buffer().read(cx).snapshot(cx);
3189 let selections = selections.clone();
3190 let background_executor = cx.background_executor().clone();
3191 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3192 self.serialize_selections = cx.background_spawn(async move {
3193 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3194 let db_selections = selections
3195 .iter()
3196 .map(|selection| {
3197 (
3198 selection.start.to_offset(&snapshot),
3199 selection.end.to_offset(&snapshot),
3200 )
3201 })
3202 .collect();
3203
3204 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3205 .await
3206 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3207 .log_err();
3208 });
3209 }
3210 }
3211
3212 cx.notify();
3213 }
3214
3215 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3216 use text::ToOffset as _;
3217 use text::ToPoint as _;
3218
3219 if self.mode.is_minimap()
3220 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3221 {
3222 return;
3223 }
3224
3225 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3226 return;
3227 };
3228
3229 let snapshot = singleton.read(cx).snapshot();
3230 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3231 let display_snapshot = display_map.snapshot(cx);
3232
3233 display_snapshot
3234 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3235 .map(|fold| {
3236 fold.range.start.text_anchor.to_point(&snapshot)
3237 ..fold.range.end.text_anchor.to_point(&snapshot)
3238 })
3239 .collect()
3240 });
3241 self.update_restoration_data(cx, |data| {
3242 data.folds = inmemory_folds;
3243 });
3244
3245 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3246 return;
3247 };
3248 let background_executor = cx.background_executor().clone();
3249 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3250 let db_folds = self.display_map.update(cx, |display_map, cx| {
3251 display_map
3252 .snapshot(cx)
3253 .folds_in_range(0..snapshot.len())
3254 .map(|fold| {
3255 (
3256 fold.range.start.text_anchor.to_offset(&snapshot),
3257 fold.range.end.text_anchor.to_offset(&snapshot),
3258 )
3259 })
3260 .collect()
3261 });
3262 self.serialize_folds = cx.background_spawn(async move {
3263 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3264 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3265 .await
3266 .with_context(|| {
3267 format!(
3268 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3269 )
3270 })
3271 .log_err();
3272 });
3273 }
3274
3275 pub fn sync_selections(
3276 &mut self,
3277 other: Entity<Editor>,
3278 cx: &mut Context<Self>,
3279 ) -> gpui::Subscription {
3280 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3281 self.selections.change_with(cx, |selections| {
3282 selections.select_anchors(other_selections);
3283 });
3284
3285 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3286 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3287 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3288 if other_selections.is_empty() {
3289 return;
3290 }
3291 this.selections.change_with(cx, |selections| {
3292 selections.select_anchors(other_selections);
3293 });
3294 }
3295 });
3296
3297 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3298 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3299 let these_selections = this.selections.disjoint_anchors().to_vec();
3300 if these_selections.is_empty() {
3301 return;
3302 }
3303 other.update(cx, |other_editor, cx| {
3304 other_editor.selections.change_with(cx, |selections| {
3305 selections.select_anchors(these_selections);
3306 })
3307 });
3308 }
3309 });
3310
3311 Subscription::join(other_subscription, this_subscription)
3312 }
3313
3314 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3315 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3316 /// effects of selection change occur at the end of the transaction.
3317 pub fn change_selections<R>(
3318 &mut self,
3319 effects: SelectionEffects,
3320 window: &mut Window,
3321 cx: &mut Context<Self>,
3322 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3323 ) -> R {
3324 if let Some(state) = &mut self.deferred_selection_effects_state {
3325 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3326 state.effects.completions = effects.completions;
3327 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3328 let (changed, result) = self.selections.change_with(cx, change);
3329 state.changed |= changed;
3330 return result;
3331 }
3332 let mut state = DeferredSelectionEffectsState {
3333 changed: false,
3334 effects,
3335 old_cursor_position: self.selections.newest_anchor().head(),
3336 history_entry: SelectionHistoryEntry {
3337 selections: self.selections.disjoint_anchors_arc(),
3338 select_next_state: self.select_next_state.clone(),
3339 select_prev_state: self.select_prev_state.clone(),
3340 add_selections_state: self.add_selections_state.clone(),
3341 },
3342 };
3343 let (changed, result) = self.selections.change_with(cx, change);
3344 state.changed = state.changed || changed;
3345 if self.defer_selection_effects {
3346 self.deferred_selection_effects_state = Some(state);
3347 } else {
3348 self.apply_selection_effects(state, window, cx);
3349 }
3350 result
3351 }
3352
3353 /// Defers the effects of selection change, so that the effects of multiple calls to
3354 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3355 /// to selection history and the state of popovers based on selection position aren't
3356 /// erroneously updated.
3357 pub fn with_selection_effects_deferred<R>(
3358 &mut self,
3359 window: &mut Window,
3360 cx: &mut Context<Self>,
3361 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3362 ) -> R {
3363 let already_deferred = self.defer_selection_effects;
3364 self.defer_selection_effects = true;
3365 let result = update(self, window, cx);
3366 if !already_deferred {
3367 self.defer_selection_effects = false;
3368 if let Some(state) = self.deferred_selection_effects_state.take() {
3369 self.apply_selection_effects(state, window, cx);
3370 }
3371 }
3372 result
3373 }
3374
3375 fn apply_selection_effects(
3376 &mut self,
3377 state: DeferredSelectionEffectsState,
3378 window: &mut Window,
3379 cx: &mut Context<Self>,
3380 ) {
3381 if state.changed {
3382 self.selection_history.push(state.history_entry);
3383
3384 if let Some(autoscroll) = state.effects.scroll {
3385 self.request_autoscroll(autoscroll, cx);
3386 }
3387
3388 let old_cursor_position = &state.old_cursor_position;
3389
3390 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3391
3392 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3393 self.show_signature_help(&ShowSignatureHelp, window, cx);
3394 }
3395 }
3396 }
3397
3398 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3399 where
3400 I: IntoIterator<Item = (Range<S>, T)>,
3401 S: ToOffset,
3402 T: Into<Arc<str>>,
3403 {
3404 if self.read_only(cx) {
3405 return;
3406 }
3407
3408 self.buffer
3409 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3410 }
3411
3412 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3413 where
3414 I: IntoIterator<Item = (Range<S>, T)>,
3415 S: ToOffset,
3416 T: Into<Arc<str>>,
3417 {
3418 if self.read_only(cx) {
3419 return;
3420 }
3421
3422 self.buffer.update(cx, |buffer, cx| {
3423 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3424 });
3425 }
3426
3427 pub fn edit_with_block_indent<I, S, T>(
3428 &mut self,
3429 edits: I,
3430 original_indent_columns: Vec<Option<u32>>,
3431 cx: &mut Context<Self>,
3432 ) where
3433 I: IntoIterator<Item = (Range<S>, T)>,
3434 S: ToOffset,
3435 T: Into<Arc<str>>,
3436 {
3437 if self.read_only(cx) {
3438 return;
3439 }
3440
3441 self.buffer.update(cx, |buffer, cx| {
3442 buffer.edit(
3443 edits,
3444 Some(AutoindentMode::Block {
3445 original_indent_columns,
3446 }),
3447 cx,
3448 )
3449 });
3450 }
3451
3452 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3453 self.hide_context_menu(window, cx);
3454
3455 match phase {
3456 SelectPhase::Begin {
3457 position,
3458 add,
3459 click_count,
3460 } => self.begin_selection(position, add, click_count, window, cx),
3461 SelectPhase::BeginColumnar {
3462 position,
3463 goal_column,
3464 reset,
3465 mode,
3466 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3467 SelectPhase::Extend {
3468 position,
3469 click_count,
3470 } => self.extend_selection(position, click_count, window, cx),
3471 SelectPhase::Update {
3472 position,
3473 goal_column,
3474 scroll_delta,
3475 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3476 SelectPhase::End => self.end_selection(window, cx),
3477 }
3478 }
3479
3480 fn extend_selection(
3481 &mut self,
3482 position: DisplayPoint,
3483 click_count: usize,
3484 window: &mut Window,
3485 cx: &mut Context<Self>,
3486 ) {
3487 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3488 let tail = self.selections.newest::<usize>(cx).tail();
3489 self.begin_selection(position, false, click_count, window, cx);
3490
3491 let position = position.to_offset(&display_map, Bias::Left);
3492 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3493
3494 let mut pending_selection = self
3495 .selections
3496 .pending_anchor()
3497 .cloned()
3498 .expect("extend_selection not called with pending selection");
3499 if position >= tail {
3500 pending_selection.start = tail_anchor;
3501 } else {
3502 pending_selection.end = tail_anchor;
3503 pending_selection.reversed = true;
3504 }
3505
3506 let mut pending_mode = self.selections.pending_mode().unwrap();
3507 match &mut pending_mode {
3508 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3509 _ => {}
3510 }
3511
3512 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3513 SelectionEffects::scroll(Autoscroll::fit())
3514 } else {
3515 SelectionEffects::no_scroll()
3516 };
3517
3518 self.change_selections(effects, window, cx, |s| {
3519 s.set_pending(pending_selection.clone(), pending_mode)
3520 });
3521 }
3522
3523 fn begin_selection(
3524 &mut self,
3525 position: DisplayPoint,
3526 add: bool,
3527 click_count: usize,
3528 window: &mut Window,
3529 cx: &mut Context<Self>,
3530 ) {
3531 if !self.focus_handle.is_focused(window) {
3532 self.last_focused_descendant = None;
3533 window.focus(&self.focus_handle);
3534 }
3535
3536 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3537 let buffer = &display_map.buffer_snapshot;
3538 let position = display_map.clip_point(position, Bias::Left);
3539
3540 let start;
3541 let end;
3542 let mode;
3543 let mut auto_scroll;
3544 match click_count {
3545 1 => {
3546 start = buffer.anchor_before(position.to_point(&display_map));
3547 end = start;
3548 mode = SelectMode::Character;
3549 auto_scroll = true;
3550 }
3551 2 => {
3552 let position = display_map
3553 .clip_point(position, Bias::Left)
3554 .to_offset(&display_map, Bias::Left);
3555 let (range, _) = buffer.surrounding_word(position, None);
3556 start = buffer.anchor_before(range.start);
3557 end = buffer.anchor_before(range.end);
3558 mode = SelectMode::Word(start..end);
3559 auto_scroll = true;
3560 }
3561 3 => {
3562 let position = display_map
3563 .clip_point(position, Bias::Left)
3564 .to_point(&display_map);
3565 let line_start = display_map.prev_line_boundary(position).0;
3566 let next_line_start = buffer.clip_point(
3567 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3568 Bias::Left,
3569 );
3570 start = buffer.anchor_before(line_start);
3571 end = buffer.anchor_before(next_line_start);
3572 mode = SelectMode::Line(start..end);
3573 auto_scroll = true;
3574 }
3575 _ => {
3576 start = buffer.anchor_before(0);
3577 end = buffer.anchor_before(buffer.len());
3578 mode = SelectMode::All;
3579 auto_scroll = false;
3580 }
3581 }
3582 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3583
3584 let point_to_delete: Option<usize> = {
3585 let selected_points: Vec<Selection<Point>> =
3586 self.selections.disjoint_in_range(start..end, cx);
3587
3588 if !add || click_count > 1 {
3589 None
3590 } else if !selected_points.is_empty() {
3591 Some(selected_points[0].id)
3592 } else {
3593 let clicked_point_already_selected =
3594 self.selections.disjoint_anchors().iter().find(|selection| {
3595 selection.start.to_point(buffer) == start.to_point(buffer)
3596 || selection.end.to_point(buffer) == end.to_point(buffer)
3597 });
3598
3599 clicked_point_already_selected.map(|selection| selection.id)
3600 }
3601 };
3602
3603 let selections_count = self.selections.count();
3604 let effects = if auto_scroll {
3605 SelectionEffects::default()
3606 } else {
3607 SelectionEffects::no_scroll()
3608 };
3609
3610 self.change_selections(effects, window, cx, |s| {
3611 if let Some(point_to_delete) = point_to_delete {
3612 s.delete(point_to_delete);
3613
3614 if selections_count == 1 {
3615 s.set_pending_anchor_range(start..end, mode);
3616 }
3617 } else {
3618 if !add {
3619 s.clear_disjoint();
3620 }
3621
3622 s.set_pending_anchor_range(start..end, mode);
3623 }
3624 });
3625 }
3626
3627 fn begin_columnar_selection(
3628 &mut self,
3629 position: DisplayPoint,
3630 goal_column: u32,
3631 reset: bool,
3632 mode: ColumnarMode,
3633 window: &mut Window,
3634 cx: &mut Context<Self>,
3635 ) {
3636 if !self.focus_handle.is_focused(window) {
3637 self.last_focused_descendant = None;
3638 window.focus(&self.focus_handle);
3639 }
3640
3641 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3642
3643 if reset {
3644 let pointer_position = display_map
3645 .buffer_snapshot
3646 .anchor_before(position.to_point(&display_map));
3647
3648 self.change_selections(
3649 SelectionEffects::scroll(Autoscroll::newest()),
3650 window,
3651 cx,
3652 |s| {
3653 s.clear_disjoint();
3654 s.set_pending_anchor_range(
3655 pointer_position..pointer_position,
3656 SelectMode::Character,
3657 );
3658 },
3659 );
3660 };
3661
3662 let tail = self.selections.newest::<Point>(cx).tail();
3663 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3664 self.columnar_selection_state = match mode {
3665 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3666 selection_tail: selection_anchor,
3667 display_point: if reset {
3668 if position.column() != goal_column {
3669 Some(DisplayPoint::new(position.row(), goal_column))
3670 } else {
3671 None
3672 }
3673 } else {
3674 None
3675 },
3676 }),
3677 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3678 selection_tail: selection_anchor,
3679 }),
3680 };
3681
3682 if !reset {
3683 self.select_columns(position, goal_column, &display_map, window, cx);
3684 }
3685 }
3686
3687 fn update_selection(
3688 &mut self,
3689 position: DisplayPoint,
3690 goal_column: u32,
3691 scroll_delta: gpui::Point<f32>,
3692 window: &mut Window,
3693 cx: &mut Context<Self>,
3694 ) {
3695 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3696
3697 if self.columnar_selection_state.is_some() {
3698 self.select_columns(position, goal_column, &display_map, window, cx);
3699 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3700 let buffer = &display_map.buffer_snapshot;
3701 let head;
3702 let tail;
3703 let mode = self.selections.pending_mode().unwrap();
3704 match &mode {
3705 SelectMode::Character => {
3706 head = position.to_point(&display_map);
3707 tail = pending.tail().to_point(buffer);
3708 }
3709 SelectMode::Word(original_range) => {
3710 let offset = display_map
3711 .clip_point(position, Bias::Left)
3712 .to_offset(&display_map, Bias::Left);
3713 let original_range = original_range.to_offset(buffer);
3714
3715 let head_offset = if buffer.is_inside_word(offset, None)
3716 || original_range.contains(&offset)
3717 {
3718 let (word_range, _) = buffer.surrounding_word(offset, None);
3719 if word_range.start < original_range.start {
3720 word_range.start
3721 } else {
3722 word_range.end
3723 }
3724 } else {
3725 offset
3726 };
3727
3728 head = head_offset.to_point(buffer);
3729 if head_offset <= original_range.start {
3730 tail = original_range.end.to_point(buffer);
3731 } else {
3732 tail = original_range.start.to_point(buffer);
3733 }
3734 }
3735 SelectMode::Line(original_range) => {
3736 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3737
3738 let position = display_map
3739 .clip_point(position, Bias::Left)
3740 .to_point(&display_map);
3741 let line_start = display_map.prev_line_boundary(position).0;
3742 let next_line_start = buffer.clip_point(
3743 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3744 Bias::Left,
3745 );
3746
3747 if line_start < original_range.start {
3748 head = line_start
3749 } else {
3750 head = next_line_start
3751 }
3752
3753 if head <= original_range.start {
3754 tail = original_range.end;
3755 } else {
3756 tail = original_range.start;
3757 }
3758 }
3759 SelectMode::All => {
3760 return;
3761 }
3762 };
3763
3764 if head < tail {
3765 pending.start = buffer.anchor_before(head);
3766 pending.end = buffer.anchor_before(tail);
3767 pending.reversed = true;
3768 } else {
3769 pending.start = buffer.anchor_before(tail);
3770 pending.end = buffer.anchor_before(head);
3771 pending.reversed = false;
3772 }
3773
3774 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3775 s.set_pending(pending.clone(), mode);
3776 });
3777 } else {
3778 log::error!("update_selection dispatched with no pending selection");
3779 return;
3780 }
3781
3782 self.apply_scroll_delta(scroll_delta, window, cx);
3783 cx.notify();
3784 }
3785
3786 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3787 self.columnar_selection_state.take();
3788 if self.selections.pending_anchor().is_some() {
3789 let selections = self.selections.all::<usize>(cx);
3790 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3791 s.select(selections);
3792 s.clear_pending();
3793 });
3794 }
3795 }
3796
3797 fn select_columns(
3798 &mut self,
3799 head: DisplayPoint,
3800 goal_column: u32,
3801 display_map: &DisplaySnapshot,
3802 window: &mut Window,
3803 cx: &mut Context<Self>,
3804 ) {
3805 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3806 return;
3807 };
3808
3809 let tail = match columnar_state {
3810 ColumnarSelectionState::FromMouse {
3811 selection_tail,
3812 display_point,
3813 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3814 ColumnarSelectionState::FromSelection { selection_tail } => {
3815 selection_tail.to_display_point(display_map)
3816 }
3817 };
3818
3819 let start_row = cmp::min(tail.row(), head.row());
3820 let end_row = cmp::max(tail.row(), head.row());
3821 let start_column = cmp::min(tail.column(), goal_column);
3822 let end_column = cmp::max(tail.column(), goal_column);
3823 let reversed = start_column < tail.column();
3824
3825 let selection_ranges = (start_row.0..=end_row.0)
3826 .map(DisplayRow)
3827 .filter_map(|row| {
3828 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3829 || start_column <= display_map.line_len(row))
3830 && !display_map.is_block_line(row)
3831 {
3832 let start = display_map
3833 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3834 .to_point(display_map);
3835 let end = display_map
3836 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3837 .to_point(display_map);
3838 if reversed {
3839 Some(end..start)
3840 } else {
3841 Some(start..end)
3842 }
3843 } else {
3844 None
3845 }
3846 })
3847 .collect::<Vec<_>>();
3848
3849 let ranges = match columnar_state {
3850 ColumnarSelectionState::FromMouse { .. } => {
3851 let mut non_empty_ranges = selection_ranges
3852 .iter()
3853 .filter(|selection_range| selection_range.start != selection_range.end)
3854 .peekable();
3855 if non_empty_ranges.peek().is_some() {
3856 non_empty_ranges.cloned().collect()
3857 } else {
3858 selection_ranges
3859 }
3860 }
3861 _ => selection_ranges,
3862 };
3863
3864 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3865 s.select_ranges(ranges);
3866 });
3867 cx.notify();
3868 }
3869
3870 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3871 self.selections
3872 .all_adjusted(cx)
3873 .iter()
3874 .any(|selection| !selection.is_empty())
3875 }
3876
3877 pub fn has_pending_nonempty_selection(&self) -> bool {
3878 let pending_nonempty_selection = match self.selections.pending_anchor() {
3879 Some(Selection { start, end, .. }) => start != end,
3880 None => false,
3881 };
3882
3883 pending_nonempty_selection
3884 || (self.columnar_selection_state.is_some()
3885 && self.selections.disjoint_anchors().len() > 1)
3886 }
3887
3888 pub fn has_pending_selection(&self) -> bool {
3889 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3890 }
3891
3892 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3893 self.selection_mark_mode = false;
3894 self.selection_drag_state = SelectionDragState::None;
3895
3896 if self.clear_expanded_diff_hunks(cx) {
3897 cx.notify();
3898 return;
3899 }
3900 if self.dismiss_menus_and_popups(true, window, cx) {
3901 return;
3902 }
3903
3904 if self.mode.is_full()
3905 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3906 {
3907 return;
3908 }
3909
3910 cx.propagate();
3911 }
3912
3913 pub fn dismiss_menus_and_popups(
3914 &mut self,
3915 is_user_requested: bool,
3916 window: &mut Window,
3917 cx: &mut Context<Self>,
3918 ) -> bool {
3919 if self.take_rename(false, window, cx).is_some() {
3920 return true;
3921 }
3922
3923 if hide_hover(self, cx) {
3924 return true;
3925 }
3926
3927 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3928 return true;
3929 }
3930
3931 if self.hide_context_menu(window, cx).is_some() {
3932 return true;
3933 }
3934
3935 if self.mouse_context_menu.take().is_some() {
3936 return true;
3937 }
3938
3939 if is_user_requested && self.discard_edit_prediction(true, cx) {
3940 return true;
3941 }
3942
3943 if self.snippet_stack.pop().is_some() {
3944 return true;
3945 }
3946
3947 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3948 self.dismiss_diagnostics(cx);
3949 return true;
3950 }
3951
3952 false
3953 }
3954
3955 fn linked_editing_ranges_for(
3956 &self,
3957 selection: Range<text::Anchor>,
3958 cx: &App,
3959 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3960 if self.linked_edit_ranges.is_empty() {
3961 return None;
3962 }
3963 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3964 selection.end.buffer_id.and_then(|end_buffer_id| {
3965 if selection.start.buffer_id != Some(end_buffer_id) {
3966 return None;
3967 }
3968 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3969 let snapshot = buffer.read(cx).snapshot();
3970 self.linked_edit_ranges
3971 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3972 .map(|ranges| (ranges, snapshot, buffer))
3973 })?;
3974 use text::ToOffset as TO;
3975 // find offset from the start of current range to current cursor position
3976 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3977
3978 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3979 let start_difference = start_offset - start_byte_offset;
3980 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3981 let end_difference = end_offset - start_byte_offset;
3982 // Current range has associated linked ranges.
3983 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3984 for range in linked_ranges.iter() {
3985 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3986 let end_offset = start_offset + end_difference;
3987 let start_offset = start_offset + start_difference;
3988 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3989 continue;
3990 }
3991 if self.selections.disjoint_anchor_ranges().any(|s| {
3992 if s.start.buffer_id != selection.start.buffer_id
3993 || s.end.buffer_id != selection.end.buffer_id
3994 {
3995 return false;
3996 }
3997 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3998 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3999 }) {
4000 continue;
4001 }
4002 let start = buffer_snapshot.anchor_after(start_offset);
4003 let end = buffer_snapshot.anchor_after(end_offset);
4004 linked_edits
4005 .entry(buffer.clone())
4006 .or_default()
4007 .push(start..end);
4008 }
4009 Some(linked_edits)
4010 }
4011
4012 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4013 let text: Arc<str> = text.into();
4014
4015 if self.read_only(cx) {
4016 return;
4017 }
4018
4019 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4020
4021 let selections = self.selections.all_adjusted(cx);
4022 let mut bracket_inserted = false;
4023 let mut edits = Vec::new();
4024 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4025 let mut new_selections = Vec::with_capacity(selections.len());
4026 let mut new_autoclose_regions = Vec::new();
4027 let snapshot = self.buffer.read(cx).read(cx);
4028 let mut clear_linked_edit_ranges = false;
4029
4030 for (selection, autoclose_region) in
4031 self.selections_with_autoclose_regions(selections, &snapshot)
4032 {
4033 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4034 // Determine if the inserted text matches the opening or closing
4035 // bracket of any of this language's bracket pairs.
4036 let mut bracket_pair = None;
4037 let mut is_bracket_pair_start = false;
4038 let mut is_bracket_pair_end = false;
4039 if !text.is_empty() {
4040 let mut bracket_pair_matching_end = None;
4041 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4042 // and they are removing the character that triggered IME popup.
4043 for (pair, enabled) in scope.brackets() {
4044 if !pair.close && !pair.surround {
4045 continue;
4046 }
4047
4048 if enabled && pair.start.ends_with(text.as_ref()) {
4049 let prefix_len = pair.start.len() - text.len();
4050 let preceding_text_matches_prefix = prefix_len == 0
4051 || (selection.start.column >= (prefix_len as u32)
4052 && snapshot.contains_str_at(
4053 Point::new(
4054 selection.start.row,
4055 selection.start.column - (prefix_len as u32),
4056 ),
4057 &pair.start[..prefix_len],
4058 ));
4059 if preceding_text_matches_prefix {
4060 bracket_pair = Some(pair.clone());
4061 is_bracket_pair_start = true;
4062 break;
4063 }
4064 }
4065 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4066 {
4067 // take first bracket pair matching end, but don't break in case a later bracket
4068 // pair matches start
4069 bracket_pair_matching_end = Some(pair.clone());
4070 }
4071 }
4072 if let Some(end) = bracket_pair_matching_end
4073 && bracket_pair.is_none()
4074 {
4075 bracket_pair = Some(end);
4076 is_bracket_pair_end = true;
4077 }
4078 }
4079
4080 if let Some(bracket_pair) = bracket_pair {
4081 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4082 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4083 let auto_surround =
4084 self.use_auto_surround && snapshot_settings.use_auto_surround;
4085 if selection.is_empty() {
4086 if is_bracket_pair_start {
4087 // If the inserted text is a suffix of an opening bracket and the
4088 // selection is preceded by the rest of the opening bracket, then
4089 // insert the closing bracket.
4090 let following_text_allows_autoclose = snapshot
4091 .chars_at(selection.start)
4092 .next()
4093 .is_none_or(|c| scope.should_autoclose_before(c));
4094
4095 let preceding_text_allows_autoclose = selection.start.column == 0
4096 || snapshot
4097 .reversed_chars_at(selection.start)
4098 .next()
4099 .is_none_or(|c| {
4100 bracket_pair.start != bracket_pair.end
4101 || !snapshot
4102 .char_classifier_at(selection.start)
4103 .is_word(c)
4104 });
4105
4106 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4107 && bracket_pair.start.len() == 1
4108 {
4109 let target = bracket_pair.start.chars().next().unwrap();
4110 let current_line_count = snapshot
4111 .reversed_chars_at(selection.start)
4112 .take_while(|&c| c != '\n')
4113 .filter(|&c| c == target)
4114 .count();
4115 current_line_count % 2 == 1
4116 } else {
4117 false
4118 };
4119
4120 if autoclose
4121 && bracket_pair.close
4122 && following_text_allows_autoclose
4123 && preceding_text_allows_autoclose
4124 && !is_closing_quote
4125 {
4126 let anchor = snapshot.anchor_before(selection.end);
4127 new_selections.push((selection.map(|_| anchor), text.len()));
4128 new_autoclose_regions.push((
4129 anchor,
4130 text.len(),
4131 selection.id,
4132 bracket_pair.clone(),
4133 ));
4134 edits.push((
4135 selection.range(),
4136 format!("{}{}", text, bracket_pair.end).into(),
4137 ));
4138 bracket_inserted = true;
4139 continue;
4140 }
4141 }
4142
4143 if let Some(region) = autoclose_region {
4144 // If the selection is followed by an auto-inserted closing bracket,
4145 // then don't insert that closing bracket again; just move the selection
4146 // past the closing bracket.
4147 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4148 && text.as_ref() == region.pair.end.as_str()
4149 && snapshot.contains_str_at(region.range.end, text.as_ref());
4150 if should_skip {
4151 let anchor = snapshot.anchor_after(selection.end);
4152 new_selections
4153 .push((selection.map(|_| anchor), region.pair.end.len()));
4154 continue;
4155 }
4156 }
4157
4158 let always_treat_brackets_as_autoclosed = snapshot
4159 .language_settings_at(selection.start, cx)
4160 .always_treat_brackets_as_autoclosed;
4161 if always_treat_brackets_as_autoclosed
4162 && is_bracket_pair_end
4163 && snapshot.contains_str_at(selection.end, text.as_ref())
4164 {
4165 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4166 // and the inserted text is a closing bracket and the selection is followed
4167 // by the closing bracket then move the selection past the closing bracket.
4168 let anchor = snapshot.anchor_after(selection.end);
4169 new_selections.push((selection.map(|_| anchor), text.len()));
4170 continue;
4171 }
4172 }
4173 // If an opening bracket is 1 character long and is typed while
4174 // text is selected, then surround that text with the bracket pair.
4175 else if auto_surround
4176 && bracket_pair.surround
4177 && is_bracket_pair_start
4178 && bracket_pair.start.chars().count() == 1
4179 {
4180 edits.push((selection.start..selection.start, text.clone()));
4181 edits.push((
4182 selection.end..selection.end,
4183 bracket_pair.end.as_str().into(),
4184 ));
4185 bracket_inserted = true;
4186 new_selections.push((
4187 Selection {
4188 id: selection.id,
4189 start: snapshot.anchor_after(selection.start),
4190 end: snapshot.anchor_before(selection.end),
4191 reversed: selection.reversed,
4192 goal: selection.goal,
4193 },
4194 0,
4195 ));
4196 continue;
4197 }
4198 }
4199 }
4200
4201 if self.auto_replace_emoji_shortcode
4202 && selection.is_empty()
4203 && text.as_ref().ends_with(':')
4204 && let Some(possible_emoji_short_code) =
4205 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4206 && !possible_emoji_short_code.is_empty()
4207 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4208 {
4209 let emoji_shortcode_start = Point::new(
4210 selection.start.row,
4211 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4212 );
4213
4214 // Remove shortcode from buffer
4215 edits.push((
4216 emoji_shortcode_start..selection.start,
4217 "".to_string().into(),
4218 ));
4219 new_selections.push((
4220 Selection {
4221 id: selection.id,
4222 start: snapshot.anchor_after(emoji_shortcode_start),
4223 end: snapshot.anchor_before(selection.start),
4224 reversed: selection.reversed,
4225 goal: selection.goal,
4226 },
4227 0,
4228 ));
4229
4230 // Insert emoji
4231 let selection_start_anchor = snapshot.anchor_after(selection.start);
4232 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4233 edits.push((selection.start..selection.end, emoji.to_string().into()));
4234
4235 continue;
4236 }
4237
4238 // If not handling any auto-close operation, then just replace the selected
4239 // text with the given input and move the selection to the end of the
4240 // newly inserted text.
4241 let anchor = snapshot.anchor_after(selection.end);
4242 if !self.linked_edit_ranges.is_empty() {
4243 let start_anchor = snapshot.anchor_before(selection.start);
4244
4245 let is_word_char = text.chars().next().is_none_or(|char| {
4246 let classifier = snapshot
4247 .char_classifier_at(start_anchor.to_offset(&snapshot))
4248 .scope_context(Some(CharScopeContext::LinkedEdit));
4249 classifier.is_word(char)
4250 });
4251
4252 if is_word_char {
4253 if let Some(ranges) = self
4254 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4255 {
4256 for (buffer, edits) in ranges {
4257 linked_edits
4258 .entry(buffer.clone())
4259 .or_default()
4260 .extend(edits.into_iter().map(|range| (range, text.clone())));
4261 }
4262 }
4263 } else {
4264 clear_linked_edit_ranges = true;
4265 }
4266 }
4267
4268 new_selections.push((selection.map(|_| anchor), 0));
4269 edits.push((selection.start..selection.end, text.clone()));
4270 }
4271
4272 drop(snapshot);
4273
4274 self.transact(window, cx, |this, window, cx| {
4275 if clear_linked_edit_ranges {
4276 this.linked_edit_ranges.clear();
4277 }
4278 let initial_buffer_versions =
4279 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4280
4281 this.buffer.update(cx, |buffer, cx| {
4282 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4283 });
4284 for (buffer, edits) in linked_edits {
4285 buffer.update(cx, |buffer, cx| {
4286 let snapshot = buffer.snapshot();
4287 let edits = edits
4288 .into_iter()
4289 .map(|(range, text)| {
4290 use text::ToPoint as TP;
4291 let end_point = TP::to_point(&range.end, &snapshot);
4292 let start_point = TP::to_point(&range.start, &snapshot);
4293 (start_point..end_point, text)
4294 })
4295 .sorted_by_key(|(range, _)| range.start);
4296 buffer.edit(edits, None, cx);
4297 })
4298 }
4299 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4300 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4301 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4302 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4303 .zip(new_selection_deltas)
4304 .map(|(selection, delta)| Selection {
4305 id: selection.id,
4306 start: selection.start + delta,
4307 end: selection.end + delta,
4308 reversed: selection.reversed,
4309 goal: SelectionGoal::None,
4310 })
4311 .collect::<Vec<_>>();
4312
4313 let mut i = 0;
4314 for (position, delta, selection_id, pair) in new_autoclose_regions {
4315 let position = position.to_offset(&map.buffer_snapshot) + delta;
4316 let start = map.buffer_snapshot.anchor_before(position);
4317 let end = map.buffer_snapshot.anchor_after(position);
4318 while let Some(existing_state) = this.autoclose_regions.get(i) {
4319 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4320 Ordering::Less => i += 1,
4321 Ordering::Greater => break,
4322 Ordering::Equal => {
4323 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4324 Ordering::Less => i += 1,
4325 Ordering::Equal => break,
4326 Ordering::Greater => break,
4327 }
4328 }
4329 }
4330 }
4331 this.autoclose_regions.insert(
4332 i,
4333 AutocloseRegion {
4334 selection_id,
4335 range: start..end,
4336 pair,
4337 },
4338 );
4339 }
4340
4341 let had_active_edit_prediction = this.has_active_edit_prediction();
4342 this.change_selections(
4343 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4344 window,
4345 cx,
4346 |s| s.select(new_selections),
4347 );
4348
4349 if !bracket_inserted
4350 && let Some(on_type_format_task) =
4351 this.trigger_on_type_formatting(text.to_string(), window, cx)
4352 {
4353 on_type_format_task.detach_and_log_err(cx);
4354 }
4355
4356 let editor_settings = EditorSettings::get_global(cx);
4357 if bracket_inserted
4358 && (editor_settings.auto_signature_help
4359 || editor_settings.show_signature_help_after_edits)
4360 {
4361 this.show_signature_help(&ShowSignatureHelp, window, cx);
4362 }
4363
4364 let trigger_in_words =
4365 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4366 if this.hard_wrap.is_some() {
4367 let latest: Range<Point> = this.selections.newest(cx).range();
4368 if latest.is_empty()
4369 && this
4370 .buffer()
4371 .read(cx)
4372 .snapshot(cx)
4373 .line_len(MultiBufferRow(latest.start.row))
4374 == latest.start.column
4375 {
4376 this.rewrap_impl(
4377 RewrapOptions {
4378 override_language_settings: true,
4379 preserve_existing_whitespace: true,
4380 },
4381 cx,
4382 )
4383 }
4384 }
4385 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4386 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4387 this.refresh_edit_prediction(true, false, window, cx);
4388 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4389 });
4390 }
4391
4392 fn find_possible_emoji_shortcode_at_position(
4393 snapshot: &MultiBufferSnapshot,
4394 position: Point,
4395 ) -> Option<String> {
4396 let mut chars = Vec::new();
4397 let mut found_colon = false;
4398 for char in snapshot.reversed_chars_at(position).take(100) {
4399 // Found a possible emoji shortcode in the middle of the buffer
4400 if found_colon {
4401 if char.is_whitespace() {
4402 chars.reverse();
4403 return Some(chars.iter().collect());
4404 }
4405 // If the previous character is not a whitespace, we are in the middle of a word
4406 // and we only want to complete the shortcode if the word is made up of other emojis
4407 let mut containing_word = String::new();
4408 for ch in snapshot
4409 .reversed_chars_at(position)
4410 .skip(chars.len() + 1)
4411 .take(100)
4412 {
4413 if ch.is_whitespace() {
4414 break;
4415 }
4416 containing_word.push(ch);
4417 }
4418 let containing_word = containing_word.chars().rev().collect::<String>();
4419 if util::word_consists_of_emojis(containing_word.as_str()) {
4420 chars.reverse();
4421 return Some(chars.iter().collect());
4422 }
4423 }
4424
4425 if char.is_whitespace() || !char.is_ascii() {
4426 return None;
4427 }
4428 if char == ':' {
4429 found_colon = true;
4430 } else {
4431 chars.push(char);
4432 }
4433 }
4434 // Found a possible emoji shortcode at the beginning of the buffer
4435 chars.reverse();
4436 Some(chars.iter().collect())
4437 }
4438
4439 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4440 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4441 self.transact(window, cx, |this, window, cx| {
4442 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4443 let selections = this.selections.all::<usize>(cx);
4444 let multi_buffer = this.buffer.read(cx);
4445 let buffer = multi_buffer.snapshot(cx);
4446 selections
4447 .iter()
4448 .map(|selection| {
4449 let start_point = selection.start.to_point(&buffer);
4450 let mut existing_indent =
4451 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4452 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4453 let start = selection.start;
4454 let end = selection.end;
4455 let selection_is_empty = start == end;
4456 let language_scope = buffer.language_scope_at(start);
4457 let (
4458 comment_delimiter,
4459 doc_delimiter,
4460 insert_extra_newline,
4461 indent_on_newline,
4462 indent_on_extra_newline,
4463 ) = if let Some(language) = &language_scope {
4464 let mut insert_extra_newline =
4465 insert_extra_newline_brackets(&buffer, start..end, language)
4466 || insert_extra_newline_tree_sitter(&buffer, start..end);
4467
4468 // Comment extension on newline is allowed only for cursor selections
4469 let comment_delimiter = maybe!({
4470 if !selection_is_empty {
4471 return None;
4472 }
4473
4474 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4475 return None;
4476 }
4477
4478 let delimiters = language.line_comment_prefixes();
4479 let max_len_of_delimiter =
4480 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4481 let (snapshot, range) =
4482 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4483
4484 let num_of_whitespaces = snapshot
4485 .chars_for_range(range.clone())
4486 .take_while(|c| c.is_whitespace())
4487 .count();
4488 let comment_candidate = snapshot
4489 .chars_for_range(range.clone())
4490 .skip(num_of_whitespaces)
4491 .take(max_len_of_delimiter)
4492 .collect::<String>();
4493 let (delimiter, trimmed_len) = delimiters
4494 .iter()
4495 .filter_map(|delimiter| {
4496 let prefix = delimiter.trim_end();
4497 if comment_candidate.starts_with(prefix) {
4498 Some((delimiter, prefix.len()))
4499 } else {
4500 None
4501 }
4502 })
4503 .max_by_key(|(_, len)| *len)?;
4504
4505 if let Some(BlockCommentConfig {
4506 start: block_start, ..
4507 }) = language.block_comment()
4508 {
4509 let block_start_trimmed = block_start.trim_end();
4510 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4511 let line_content = snapshot
4512 .chars_for_range(range)
4513 .skip(num_of_whitespaces)
4514 .take(block_start_trimmed.len())
4515 .collect::<String>();
4516
4517 if line_content.starts_with(block_start_trimmed) {
4518 return None;
4519 }
4520 }
4521 }
4522
4523 let cursor_is_placed_after_comment_marker =
4524 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4525 if cursor_is_placed_after_comment_marker {
4526 Some(delimiter.clone())
4527 } else {
4528 None
4529 }
4530 });
4531
4532 let mut indent_on_newline = IndentSize::spaces(0);
4533 let mut indent_on_extra_newline = IndentSize::spaces(0);
4534
4535 let doc_delimiter = maybe!({
4536 if !selection_is_empty {
4537 return None;
4538 }
4539
4540 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4541 return None;
4542 }
4543
4544 let BlockCommentConfig {
4545 start: start_tag,
4546 end: end_tag,
4547 prefix: delimiter,
4548 tab_size: len,
4549 } = language.documentation_comment()?;
4550 let is_within_block_comment = buffer
4551 .language_scope_at(start_point)
4552 .is_some_and(|scope| scope.override_name() == Some("comment"));
4553 if !is_within_block_comment {
4554 return None;
4555 }
4556
4557 let (snapshot, range) =
4558 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4559
4560 let num_of_whitespaces = snapshot
4561 .chars_for_range(range.clone())
4562 .take_while(|c| c.is_whitespace())
4563 .count();
4564
4565 // 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.
4566 let column = start_point.column;
4567 let cursor_is_after_start_tag = {
4568 let start_tag_len = start_tag.len();
4569 let start_tag_line = snapshot
4570 .chars_for_range(range.clone())
4571 .skip(num_of_whitespaces)
4572 .take(start_tag_len)
4573 .collect::<String>();
4574 if start_tag_line.starts_with(start_tag.as_ref()) {
4575 num_of_whitespaces + start_tag_len <= column as usize
4576 } else {
4577 false
4578 }
4579 };
4580
4581 let cursor_is_after_delimiter = {
4582 let delimiter_trim = delimiter.trim_end();
4583 let delimiter_line = snapshot
4584 .chars_for_range(range.clone())
4585 .skip(num_of_whitespaces)
4586 .take(delimiter_trim.len())
4587 .collect::<String>();
4588 if delimiter_line.starts_with(delimiter_trim) {
4589 num_of_whitespaces + delimiter_trim.len() <= column as usize
4590 } else {
4591 false
4592 }
4593 };
4594
4595 let cursor_is_before_end_tag_if_exists = {
4596 let mut char_position = 0u32;
4597 let mut end_tag_offset = None;
4598
4599 'outer: for chunk in snapshot.text_for_range(range) {
4600 if let Some(byte_pos) = chunk.find(&**end_tag) {
4601 let chars_before_match =
4602 chunk[..byte_pos].chars().count() as u32;
4603 end_tag_offset =
4604 Some(char_position + chars_before_match);
4605 break 'outer;
4606 }
4607 char_position += chunk.chars().count() as u32;
4608 }
4609
4610 if let Some(end_tag_offset) = end_tag_offset {
4611 let cursor_is_before_end_tag = column <= end_tag_offset;
4612 if cursor_is_after_start_tag {
4613 if cursor_is_before_end_tag {
4614 insert_extra_newline = true;
4615 }
4616 let cursor_is_at_start_of_end_tag =
4617 column == end_tag_offset;
4618 if cursor_is_at_start_of_end_tag {
4619 indent_on_extra_newline.len = *len;
4620 }
4621 }
4622 cursor_is_before_end_tag
4623 } else {
4624 true
4625 }
4626 };
4627
4628 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4629 && cursor_is_before_end_tag_if_exists
4630 {
4631 if cursor_is_after_start_tag {
4632 indent_on_newline.len = *len;
4633 }
4634 Some(delimiter.clone())
4635 } else {
4636 None
4637 }
4638 });
4639
4640 (
4641 comment_delimiter,
4642 doc_delimiter,
4643 insert_extra_newline,
4644 indent_on_newline,
4645 indent_on_extra_newline,
4646 )
4647 } else {
4648 (
4649 None,
4650 None,
4651 false,
4652 IndentSize::default(),
4653 IndentSize::default(),
4654 )
4655 };
4656
4657 let prevent_auto_indent = doc_delimiter.is_some();
4658 let delimiter = comment_delimiter.or(doc_delimiter);
4659
4660 let capacity_for_delimiter =
4661 delimiter.as_deref().map(str::len).unwrap_or_default();
4662 let mut new_text = String::with_capacity(
4663 1 + capacity_for_delimiter
4664 + existing_indent.len as usize
4665 + indent_on_newline.len as usize
4666 + indent_on_extra_newline.len as usize,
4667 );
4668 new_text.push('\n');
4669 new_text.extend(existing_indent.chars());
4670 new_text.extend(indent_on_newline.chars());
4671
4672 if let Some(delimiter) = &delimiter {
4673 new_text.push_str(delimiter);
4674 }
4675
4676 if insert_extra_newline {
4677 new_text.push('\n');
4678 new_text.extend(existing_indent.chars());
4679 new_text.extend(indent_on_extra_newline.chars());
4680 }
4681
4682 let anchor = buffer.anchor_after(end);
4683 let new_selection = selection.map(|_| anchor);
4684 (
4685 ((start..end, new_text), prevent_auto_indent),
4686 (insert_extra_newline, new_selection),
4687 )
4688 })
4689 .unzip()
4690 };
4691
4692 let mut auto_indent_edits = Vec::new();
4693 let mut edits = Vec::new();
4694 for (edit, prevent_auto_indent) in edits_with_flags {
4695 if prevent_auto_indent {
4696 edits.push(edit);
4697 } else {
4698 auto_indent_edits.push(edit);
4699 }
4700 }
4701 if !edits.is_empty() {
4702 this.edit(edits, cx);
4703 }
4704 if !auto_indent_edits.is_empty() {
4705 this.edit_with_autoindent(auto_indent_edits, cx);
4706 }
4707
4708 let buffer = this.buffer.read(cx).snapshot(cx);
4709 let new_selections = selection_info
4710 .into_iter()
4711 .map(|(extra_newline_inserted, new_selection)| {
4712 let mut cursor = new_selection.end.to_point(&buffer);
4713 if extra_newline_inserted {
4714 cursor.row -= 1;
4715 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4716 }
4717 new_selection.map(|_| cursor)
4718 })
4719 .collect();
4720
4721 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4722 this.refresh_edit_prediction(true, false, window, cx);
4723 });
4724 }
4725
4726 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4727 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4728
4729 let buffer = self.buffer.read(cx);
4730 let snapshot = buffer.snapshot(cx);
4731
4732 let mut edits = Vec::new();
4733 let mut rows = Vec::new();
4734
4735 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4736 let cursor = selection.head();
4737 let row = cursor.row;
4738
4739 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4740
4741 let newline = "\n".to_string();
4742 edits.push((start_of_line..start_of_line, newline));
4743
4744 rows.push(row + rows_inserted as u32);
4745 }
4746
4747 self.transact(window, cx, |editor, window, cx| {
4748 editor.edit(edits, cx);
4749
4750 editor.change_selections(Default::default(), window, cx, |s| {
4751 let mut index = 0;
4752 s.move_cursors_with(|map, _, _| {
4753 let row = rows[index];
4754 index += 1;
4755
4756 let point = Point::new(row, 0);
4757 let boundary = map.next_line_boundary(point).1;
4758 let clipped = map.clip_point(boundary, Bias::Left);
4759
4760 (clipped, SelectionGoal::None)
4761 });
4762 });
4763
4764 let mut indent_edits = Vec::new();
4765 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4766 for row in rows {
4767 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4768 for (row, indent) in indents {
4769 if indent.len == 0 {
4770 continue;
4771 }
4772
4773 let text = match indent.kind {
4774 IndentKind::Space => " ".repeat(indent.len as usize),
4775 IndentKind::Tab => "\t".repeat(indent.len as usize),
4776 };
4777 let point = Point::new(row.0, 0);
4778 indent_edits.push((point..point, text));
4779 }
4780 }
4781 editor.edit(indent_edits, cx);
4782 });
4783 }
4784
4785 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4786 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4787
4788 let buffer = self.buffer.read(cx);
4789 let snapshot = buffer.snapshot(cx);
4790
4791 let mut edits = Vec::new();
4792 let mut rows = Vec::new();
4793 let mut rows_inserted = 0;
4794
4795 for selection in self.selections.all_adjusted(cx) {
4796 let cursor = selection.head();
4797 let row = cursor.row;
4798
4799 let point = Point::new(row + 1, 0);
4800 let start_of_line = snapshot.clip_point(point, Bias::Left);
4801
4802 let newline = "\n".to_string();
4803 edits.push((start_of_line..start_of_line, newline));
4804
4805 rows_inserted += 1;
4806 rows.push(row + rows_inserted);
4807 }
4808
4809 self.transact(window, cx, |editor, window, cx| {
4810 editor.edit(edits, cx);
4811
4812 editor.change_selections(Default::default(), window, cx, |s| {
4813 let mut index = 0;
4814 s.move_cursors_with(|map, _, _| {
4815 let row = rows[index];
4816 index += 1;
4817
4818 let point = Point::new(row, 0);
4819 let boundary = map.next_line_boundary(point).1;
4820 let clipped = map.clip_point(boundary, Bias::Left);
4821
4822 (clipped, SelectionGoal::None)
4823 });
4824 });
4825
4826 let mut indent_edits = Vec::new();
4827 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4828 for row in rows {
4829 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4830 for (row, indent) in indents {
4831 if indent.len == 0 {
4832 continue;
4833 }
4834
4835 let text = match indent.kind {
4836 IndentKind::Space => " ".repeat(indent.len as usize),
4837 IndentKind::Tab => "\t".repeat(indent.len as usize),
4838 };
4839 let point = Point::new(row.0, 0);
4840 indent_edits.push((point..point, text));
4841 }
4842 }
4843 editor.edit(indent_edits, cx);
4844 });
4845 }
4846
4847 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4848 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4849 original_indent_columns: Vec::new(),
4850 });
4851 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4852 }
4853
4854 fn insert_with_autoindent_mode(
4855 &mut self,
4856 text: &str,
4857 autoindent_mode: Option<AutoindentMode>,
4858 window: &mut Window,
4859 cx: &mut Context<Self>,
4860 ) {
4861 if self.read_only(cx) {
4862 return;
4863 }
4864
4865 let text: Arc<str> = text.into();
4866 self.transact(window, cx, |this, window, cx| {
4867 let old_selections = this.selections.all_adjusted(cx);
4868 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4869 let anchors = {
4870 let snapshot = buffer.read(cx);
4871 old_selections
4872 .iter()
4873 .map(|s| {
4874 let anchor = snapshot.anchor_after(s.head());
4875 s.map(|_| anchor)
4876 })
4877 .collect::<Vec<_>>()
4878 };
4879 buffer.edit(
4880 old_selections
4881 .iter()
4882 .map(|s| (s.start..s.end, text.clone())),
4883 autoindent_mode,
4884 cx,
4885 );
4886 anchors
4887 });
4888
4889 this.change_selections(Default::default(), window, cx, |s| {
4890 s.select_anchors(selection_anchors);
4891 });
4892
4893 cx.notify();
4894 });
4895 }
4896
4897 fn trigger_completion_on_input(
4898 &mut self,
4899 text: &str,
4900 trigger_in_words: bool,
4901 window: &mut Window,
4902 cx: &mut Context<Self>,
4903 ) {
4904 let completions_source = self
4905 .context_menu
4906 .borrow()
4907 .as_ref()
4908 .and_then(|menu| match menu {
4909 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4910 CodeContextMenu::CodeActions(_) => None,
4911 });
4912
4913 match completions_source {
4914 Some(CompletionsMenuSource::Words { .. }) => {
4915 self.open_or_update_completions_menu(
4916 Some(CompletionsMenuSource::Words {
4917 ignore_threshold: false,
4918 }),
4919 None,
4920 window,
4921 cx,
4922 );
4923 }
4924 Some(CompletionsMenuSource::Normal)
4925 | Some(CompletionsMenuSource::SnippetChoices)
4926 | None
4927 if self.is_completion_trigger(
4928 text,
4929 trigger_in_words,
4930 completions_source.is_some(),
4931 cx,
4932 ) =>
4933 {
4934 self.show_completions(
4935 &ShowCompletions {
4936 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4937 },
4938 window,
4939 cx,
4940 )
4941 }
4942 _ => {
4943 self.hide_context_menu(window, cx);
4944 }
4945 }
4946 }
4947
4948 fn is_completion_trigger(
4949 &self,
4950 text: &str,
4951 trigger_in_words: bool,
4952 menu_is_open: bool,
4953 cx: &mut Context<Self>,
4954 ) -> bool {
4955 let position = self.selections.newest_anchor().head();
4956 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4957 return false;
4958 };
4959
4960 if let Some(completion_provider) = &self.completion_provider {
4961 completion_provider.is_completion_trigger(
4962 &buffer,
4963 position.text_anchor,
4964 text,
4965 trigger_in_words,
4966 menu_is_open,
4967 cx,
4968 )
4969 } else {
4970 false
4971 }
4972 }
4973
4974 /// If any empty selections is touching the start of its innermost containing autoclose
4975 /// region, expand it to select the brackets.
4976 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4977 let selections = self.selections.all::<usize>(cx);
4978 let buffer = self.buffer.read(cx).read(cx);
4979 let new_selections = self
4980 .selections_with_autoclose_regions(selections, &buffer)
4981 .map(|(mut selection, region)| {
4982 if !selection.is_empty() {
4983 return selection;
4984 }
4985
4986 if let Some(region) = region {
4987 let mut range = region.range.to_offset(&buffer);
4988 if selection.start == range.start && range.start >= region.pair.start.len() {
4989 range.start -= region.pair.start.len();
4990 if buffer.contains_str_at(range.start, ®ion.pair.start)
4991 && buffer.contains_str_at(range.end, ®ion.pair.end)
4992 {
4993 range.end += region.pair.end.len();
4994 selection.start = range.start;
4995 selection.end = range.end;
4996
4997 return selection;
4998 }
4999 }
5000 }
5001
5002 let always_treat_brackets_as_autoclosed = buffer
5003 .language_settings_at(selection.start, cx)
5004 .always_treat_brackets_as_autoclosed;
5005
5006 if !always_treat_brackets_as_autoclosed {
5007 return selection;
5008 }
5009
5010 if let Some(scope) = buffer.language_scope_at(selection.start) {
5011 for (pair, enabled) in scope.brackets() {
5012 if !enabled || !pair.close {
5013 continue;
5014 }
5015
5016 if buffer.contains_str_at(selection.start, &pair.end) {
5017 let pair_start_len = pair.start.len();
5018 if buffer.contains_str_at(
5019 selection.start.saturating_sub(pair_start_len),
5020 &pair.start,
5021 ) {
5022 selection.start -= pair_start_len;
5023 selection.end += pair.end.len();
5024
5025 return selection;
5026 }
5027 }
5028 }
5029 }
5030
5031 selection
5032 })
5033 .collect();
5034
5035 drop(buffer);
5036 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5037 selections.select(new_selections)
5038 });
5039 }
5040
5041 /// Iterate the given selections, and for each one, find the smallest surrounding
5042 /// autoclose region. This uses the ordering of the selections and the autoclose
5043 /// regions to avoid repeated comparisons.
5044 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5045 &'a self,
5046 selections: impl IntoIterator<Item = Selection<D>>,
5047 buffer: &'a MultiBufferSnapshot,
5048 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5049 let mut i = 0;
5050 let mut regions = self.autoclose_regions.as_slice();
5051 selections.into_iter().map(move |selection| {
5052 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5053
5054 let mut enclosing = None;
5055 while let Some(pair_state) = regions.get(i) {
5056 if pair_state.range.end.to_offset(buffer) < range.start {
5057 regions = ®ions[i + 1..];
5058 i = 0;
5059 } else if pair_state.range.start.to_offset(buffer) > range.end {
5060 break;
5061 } else {
5062 if pair_state.selection_id == selection.id {
5063 enclosing = Some(pair_state);
5064 }
5065 i += 1;
5066 }
5067 }
5068
5069 (selection, enclosing)
5070 })
5071 }
5072
5073 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5074 fn invalidate_autoclose_regions(
5075 &mut self,
5076 mut selections: &[Selection<Anchor>],
5077 buffer: &MultiBufferSnapshot,
5078 ) {
5079 self.autoclose_regions.retain(|state| {
5080 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5081 return false;
5082 }
5083
5084 let mut i = 0;
5085 while let Some(selection) = selections.get(i) {
5086 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5087 selections = &selections[1..];
5088 continue;
5089 }
5090 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5091 break;
5092 }
5093 if selection.id == state.selection_id {
5094 return true;
5095 } else {
5096 i += 1;
5097 }
5098 }
5099 false
5100 });
5101 }
5102
5103 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5104 let offset = position.to_offset(buffer);
5105 let (word_range, kind) =
5106 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5107 if offset > word_range.start && kind == Some(CharKind::Word) {
5108 Some(
5109 buffer
5110 .text_for_range(word_range.start..offset)
5111 .collect::<String>(),
5112 )
5113 } else {
5114 None
5115 }
5116 }
5117
5118 pub fn toggle_inline_values(
5119 &mut self,
5120 _: &ToggleInlineValues,
5121 _: &mut Window,
5122 cx: &mut Context<Self>,
5123 ) {
5124 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5125
5126 self.refresh_inline_values(cx);
5127 }
5128
5129 pub fn toggle_inlay_hints(
5130 &mut self,
5131 _: &ToggleInlayHints,
5132 _: &mut Window,
5133 cx: &mut Context<Self>,
5134 ) {
5135 self.refresh_inlay_hints(
5136 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5137 cx,
5138 );
5139 }
5140
5141 pub fn inlay_hints_enabled(&self) -> bool {
5142 self.inlay_hint_cache.enabled
5143 }
5144
5145 pub fn inline_values_enabled(&self) -> bool {
5146 self.inline_value_cache.enabled
5147 }
5148
5149 #[cfg(any(test, feature = "test-support"))]
5150 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5151 self.display_map
5152 .read(cx)
5153 .current_inlays()
5154 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5155 .cloned()
5156 .collect()
5157 }
5158
5159 #[cfg(any(test, feature = "test-support"))]
5160 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5161 self.display_map
5162 .read(cx)
5163 .current_inlays()
5164 .cloned()
5165 .collect()
5166 }
5167
5168 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5169 if self.semantics_provider.is_none() || !self.mode.is_full() {
5170 return;
5171 }
5172
5173 let reason_description = reason.description();
5174 let ignore_debounce = matches!(
5175 reason,
5176 InlayHintRefreshReason::SettingsChange(_)
5177 | InlayHintRefreshReason::Toggle(_)
5178 | InlayHintRefreshReason::ExcerptsRemoved(_)
5179 | InlayHintRefreshReason::ModifiersChanged(_)
5180 );
5181 let (invalidate_cache, required_languages) = match reason {
5182 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5183 match self.inlay_hint_cache.modifiers_override(enabled) {
5184 Some(enabled) => {
5185 if enabled {
5186 (InvalidationStrategy::RefreshRequested, None)
5187 } else {
5188 self.splice_inlays(
5189 &self
5190 .visible_inlay_hints(cx)
5191 .iter()
5192 .map(|inlay| inlay.id)
5193 .collect::<Vec<InlayId>>(),
5194 Vec::new(),
5195 cx,
5196 );
5197 return;
5198 }
5199 }
5200 None => return,
5201 }
5202 }
5203 InlayHintRefreshReason::Toggle(enabled) => {
5204 if self.inlay_hint_cache.toggle(enabled) {
5205 if enabled {
5206 (InvalidationStrategy::RefreshRequested, None)
5207 } else {
5208 self.splice_inlays(
5209 &self
5210 .visible_inlay_hints(cx)
5211 .iter()
5212 .map(|inlay| inlay.id)
5213 .collect::<Vec<InlayId>>(),
5214 Vec::new(),
5215 cx,
5216 );
5217 return;
5218 }
5219 } else {
5220 return;
5221 }
5222 }
5223 InlayHintRefreshReason::SettingsChange(new_settings) => {
5224 match self.inlay_hint_cache.update_settings(
5225 &self.buffer,
5226 new_settings,
5227 self.visible_inlay_hints(cx),
5228 cx,
5229 ) {
5230 ControlFlow::Break(Some(InlaySplice {
5231 to_remove,
5232 to_insert,
5233 })) => {
5234 self.splice_inlays(&to_remove, to_insert, cx);
5235 return;
5236 }
5237 ControlFlow::Break(None) => return,
5238 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5239 }
5240 }
5241 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5242 if let Some(InlaySplice {
5243 to_remove,
5244 to_insert,
5245 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5246 {
5247 self.splice_inlays(&to_remove, to_insert, cx);
5248 }
5249 self.display_map.update(cx, |display_map, _| {
5250 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5251 });
5252 return;
5253 }
5254 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5255 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5256 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5257 }
5258 InlayHintRefreshReason::RefreshRequested => {
5259 (InvalidationStrategy::RefreshRequested, None)
5260 }
5261 };
5262
5263 if let Some(InlaySplice {
5264 to_remove,
5265 to_insert,
5266 }) = self.inlay_hint_cache.spawn_hint_refresh(
5267 reason_description,
5268 self.visible_excerpts(required_languages.as_ref(), cx),
5269 invalidate_cache,
5270 ignore_debounce,
5271 cx,
5272 ) {
5273 self.splice_inlays(&to_remove, to_insert, cx);
5274 }
5275 }
5276
5277 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5278 self.display_map
5279 .read(cx)
5280 .current_inlays()
5281 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5282 .cloned()
5283 .collect()
5284 }
5285
5286 pub fn visible_excerpts(
5287 &self,
5288 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5289 cx: &mut Context<Editor>,
5290 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5291 let Some(project) = self.project() else {
5292 return HashMap::default();
5293 };
5294 let project = project.read(cx);
5295 let multi_buffer = self.buffer().read(cx);
5296 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5297 let multi_buffer_visible_start = self
5298 .scroll_manager
5299 .anchor()
5300 .anchor
5301 .to_point(&multi_buffer_snapshot);
5302 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5303 multi_buffer_visible_start
5304 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5305 Bias::Left,
5306 );
5307 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5308 multi_buffer_snapshot
5309 .range_to_buffer_ranges(multi_buffer_visible_range)
5310 .into_iter()
5311 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5312 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5313 let buffer_file = project::File::from_dyn(buffer.file())?;
5314 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5315 let worktree_entry = buffer_worktree
5316 .read(cx)
5317 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5318 if worktree_entry.is_ignored {
5319 return None;
5320 }
5321
5322 let language = buffer.language()?;
5323 if let Some(restrict_to_languages) = restrict_to_languages
5324 && !restrict_to_languages.contains(language)
5325 {
5326 return None;
5327 }
5328 Some((
5329 excerpt_id,
5330 (
5331 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5332 buffer.version().clone(),
5333 excerpt_visible_range,
5334 ),
5335 ))
5336 })
5337 .collect()
5338 }
5339
5340 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5341 TextLayoutDetails {
5342 text_system: window.text_system().clone(),
5343 editor_style: self.style.clone().unwrap(),
5344 rem_size: window.rem_size(),
5345 scroll_anchor: self.scroll_manager.anchor(),
5346 visible_rows: self.visible_line_count(),
5347 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5348 }
5349 }
5350
5351 pub fn splice_inlays(
5352 &self,
5353 to_remove: &[InlayId],
5354 to_insert: Vec<Inlay>,
5355 cx: &mut Context<Self>,
5356 ) {
5357 self.display_map.update(cx, |display_map, cx| {
5358 display_map.splice_inlays(to_remove, to_insert, cx)
5359 });
5360 cx.notify();
5361 }
5362
5363 fn trigger_on_type_formatting(
5364 &self,
5365 input: String,
5366 window: &mut Window,
5367 cx: &mut Context<Self>,
5368 ) -> Option<Task<Result<()>>> {
5369 if input.len() != 1 {
5370 return None;
5371 }
5372
5373 let project = self.project()?;
5374 let position = self.selections.newest_anchor().head();
5375 let (buffer, buffer_position) = self
5376 .buffer
5377 .read(cx)
5378 .text_anchor_for_position(position, cx)?;
5379
5380 let settings = language_settings::language_settings(
5381 buffer
5382 .read(cx)
5383 .language_at(buffer_position)
5384 .map(|l| l.name()),
5385 buffer.read(cx).file(),
5386 cx,
5387 );
5388 if !settings.use_on_type_format {
5389 return None;
5390 }
5391
5392 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5393 // hence we do LSP request & edit on host side only — add formats to host's history.
5394 let push_to_lsp_host_history = true;
5395 // If this is not the host, append its history with new edits.
5396 let push_to_client_history = project.read(cx).is_via_collab();
5397
5398 let on_type_formatting = project.update(cx, |project, cx| {
5399 project.on_type_format(
5400 buffer.clone(),
5401 buffer_position,
5402 input,
5403 push_to_lsp_host_history,
5404 cx,
5405 )
5406 });
5407 Some(cx.spawn_in(window, async move |editor, cx| {
5408 if let Some(transaction) = on_type_formatting.await? {
5409 if push_to_client_history {
5410 buffer
5411 .update(cx, |buffer, _| {
5412 buffer.push_transaction(transaction, Instant::now());
5413 buffer.finalize_last_transaction();
5414 })
5415 .ok();
5416 }
5417 editor.update(cx, |editor, cx| {
5418 editor.refresh_document_highlights(cx);
5419 })?;
5420 }
5421 Ok(())
5422 }))
5423 }
5424
5425 pub fn show_word_completions(
5426 &mut self,
5427 _: &ShowWordCompletions,
5428 window: &mut Window,
5429 cx: &mut Context<Self>,
5430 ) {
5431 self.open_or_update_completions_menu(
5432 Some(CompletionsMenuSource::Words {
5433 ignore_threshold: true,
5434 }),
5435 None,
5436 window,
5437 cx,
5438 );
5439 }
5440
5441 pub fn show_completions(
5442 &mut self,
5443 options: &ShowCompletions,
5444 window: &mut Window,
5445 cx: &mut Context<Self>,
5446 ) {
5447 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5448 }
5449
5450 fn open_or_update_completions_menu(
5451 &mut self,
5452 requested_source: Option<CompletionsMenuSource>,
5453 trigger: Option<&str>,
5454 window: &mut Window,
5455 cx: &mut Context<Self>,
5456 ) {
5457 if self.pending_rename.is_some() {
5458 return;
5459 }
5460
5461 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5462
5463 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5464 // inserted and selected. To handle that case, the start of the selection is used so that
5465 // the menu starts with all choices.
5466 let position = self
5467 .selections
5468 .newest_anchor()
5469 .start
5470 .bias_right(&multibuffer_snapshot);
5471 if position.diff_base_anchor.is_some() {
5472 return;
5473 }
5474 let buffer_position = multibuffer_snapshot.anchor_before(position);
5475 let Some(buffer) = buffer_position
5476 .buffer_id
5477 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5478 else {
5479 return;
5480 };
5481 let buffer_snapshot = buffer.read(cx).snapshot();
5482
5483 let query: Option<Arc<String>> =
5484 Self::completion_query(&multibuffer_snapshot, buffer_position)
5485 .map(|query| query.into());
5486
5487 drop(multibuffer_snapshot);
5488
5489 // Hide the current completions menu when query is empty. Without this, cached
5490 // completions from before the trigger char may be reused (#32774).
5491 if query.is_none() {
5492 let menu_is_open = matches!(
5493 self.context_menu.borrow().as_ref(),
5494 Some(CodeContextMenu::Completions(_))
5495 );
5496 if menu_is_open {
5497 self.hide_context_menu(window, cx);
5498 }
5499 }
5500
5501 let mut ignore_word_threshold = false;
5502 let provider = match requested_source {
5503 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5504 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5505 ignore_word_threshold = ignore_threshold;
5506 None
5507 }
5508 Some(CompletionsMenuSource::SnippetChoices) => {
5509 log::error!("bug: SnippetChoices requested_source is not handled");
5510 None
5511 }
5512 };
5513
5514 let sort_completions = provider
5515 .as_ref()
5516 .is_some_and(|provider| provider.sort_completions());
5517
5518 let filter_completions = provider
5519 .as_ref()
5520 .is_none_or(|provider| provider.filter_completions());
5521
5522 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5523 if filter_completions {
5524 menu.filter(query.clone(), provider.clone(), window, cx);
5525 }
5526 // When `is_incomplete` is false, no need to re-query completions when the current query
5527 // is a suffix of the initial query.
5528 if !menu.is_incomplete {
5529 // If the new query is a suffix of the old query (typing more characters) and
5530 // the previous result was complete, the existing completions can be filtered.
5531 //
5532 // Note that this is always true for snippet completions.
5533 let query_matches = match (&menu.initial_query, &query) {
5534 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5535 (None, _) => true,
5536 _ => false,
5537 };
5538 if query_matches {
5539 let position_matches = if menu.initial_position == position {
5540 true
5541 } else {
5542 let snapshot = self.buffer.read(cx).read(cx);
5543 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5544 };
5545 if position_matches {
5546 return;
5547 }
5548 }
5549 }
5550 };
5551
5552 let trigger_kind = match trigger {
5553 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5554 CompletionTriggerKind::TRIGGER_CHARACTER
5555 }
5556 _ => CompletionTriggerKind::INVOKED,
5557 };
5558 let completion_context = CompletionContext {
5559 trigger_character: trigger.and_then(|trigger| {
5560 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5561 Some(String::from(trigger))
5562 } else {
5563 None
5564 }
5565 }),
5566 trigger_kind,
5567 };
5568
5569 let Anchor {
5570 excerpt_id: buffer_excerpt_id,
5571 text_anchor: buffer_position,
5572 ..
5573 } = buffer_position;
5574
5575 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5576 buffer_snapshot.surrounding_word(buffer_position, None)
5577 {
5578 let word_to_exclude = buffer_snapshot
5579 .text_for_range(word_range.clone())
5580 .collect::<String>();
5581 (
5582 buffer_snapshot.anchor_before(word_range.start)
5583 ..buffer_snapshot.anchor_after(buffer_position),
5584 Some(word_to_exclude),
5585 )
5586 } else {
5587 (buffer_position..buffer_position, None)
5588 };
5589
5590 let language = buffer_snapshot
5591 .language_at(buffer_position)
5592 .map(|language| language.name());
5593
5594 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5595 .completions
5596 .clone();
5597
5598 let show_completion_documentation = buffer_snapshot
5599 .settings_at(buffer_position, cx)
5600 .show_completion_documentation;
5601
5602 // The document can be large, so stay in reasonable bounds when searching for words,
5603 // otherwise completion pop-up might be slow to appear.
5604 const WORD_LOOKUP_ROWS: u32 = 5_000;
5605 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5606 let min_word_search = buffer_snapshot.clip_point(
5607 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5608 Bias::Left,
5609 );
5610 let max_word_search = buffer_snapshot.clip_point(
5611 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5612 Bias::Right,
5613 );
5614 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5615 ..buffer_snapshot.point_to_offset(max_word_search);
5616
5617 let skip_digits = query
5618 .as_ref()
5619 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5620
5621 let omit_word_completions = !self.word_completions_enabled
5622 || (!ignore_word_threshold
5623 && match &query {
5624 Some(query) => query.chars().count() < completion_settings.words_min_length,
5625 None => completion_settings.words_min_length != 0,
5626 });
5627
5628 let (mut words, provider_responses) = match &provider {
5629 Some(provider) => {
5630 let provider_responses = provider.completions(
5631 buffer_excerpt_id,
5632 &buffer,
5633 buffer_position,
5634 completion_context,
5635 window,
5636 cx,
5637 );
5638
5639 let words = match (omit_word_completions, completion_settings.words) {
5640 (true, _) | (_, WordsCompletionMode::Disabled) => {
5641 Task::ready(BTreeMap::default())
5642 }
5643 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5644 .background_spawn(async move {
5645 buffer_snapshot.words_in_range(WordsQuery {
5646 fuzzy_contents: None,
5647 range: word_search_range,
5648 skip_digits,
5649 })
5650 }),
5651 };
5652
5653 (words, provider_responses)
5654 }
5655 None => {
5656 let words = if omit_word_completions {
5657 Task::ready(BTreeMap::default())
5658 } else {
5659 cx.background_spawn(async move {
5660 buffer_snapshot.words_in_range(WordsQuery {
5661 fuzzy_contents: None,
5662 range: word_search_range,
5663 skip_digits,
5664 })
5665 })
5666 };
5667 (words, Task::ready(Ok(Vec::new())))
5668 }
5669 };
5670
5671 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5672
5673 let id = post_inc(&mut self.next_completion_id);
5674 let task = cx.spawn_in(window, async move |editor, cx| {
5675 let Ok(()) = editor.update(cx, |this, _| {
5676 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5677 }) else {
5678 return;
5679 };
5680
5681 // TODO: Ideally completions from different sources would be selectively re-queried, so
5682 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5683 let mut completions = Vec::new();
5684 let mut is_incomplete = false;
5685 let mut display_options: Option<CompletionDisplayOptions> = None;
5686 if let Some(provider_responses) = provider_responses.await.log_err()
5687 && !provider_responses.is_empty()
5688 {
5689 for response in provider_responses {
5690 completions.extend(response.completions);
5691 is_incomplete = is_incomplete || response.is_incomplete;
5692 match display_options.as_mut() {
5693 None => {
5694 display_options = Some(response.display_options);
5695 }
5696 Some(options) => options.merge(&response.display_options),
5697 }
5698 }
5699 if completion_settings.words == WordsCompletionMode::Fallback {
5700 words = Task::ready(BTreeMap::default());
5701 }
5702 }
5703 let display_options = display_options.unwrap_or_default();
5704
5705 let mut words = words.await;
5706 if let Some(word_to_exclude) = &word_to_exclude {
5707 words.remove(word_to_exclude);
5708 }
5709 for lsp_completion in &completions {
5710 words.remove(&lsp_completion.new_text);
5711 }
5712 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5713 replace_range: word_replace_range.clone(),
5714 new_text: word.clone(),
5715 label: CodeLabel::plain(word, None),
5716 icon_path: None,
5717 documentation: None,
5718 source: CompletionSource::BufferWord {
5719 word_range,
5720 resolved: false,
5721 },
5722 insert_text_mode: Some(InsertTextMode::AS_IS),
5723 confirm: None,
5724 }));
5725
5726 let menu = if completions.is_empty() {
5727 None
5728 } else {
5729 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5730 let languages = editor
5731 .workspace
5732 .as_ref()
5733 .and_then(|(workspace, _)| workspace.upgrade())
5734 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5735 let menu = CompletionsMenu::new(
5736 id,
5737 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5738 sort_completions,
5739 show_completion_documentation,
5740 position,
5741 query.clone(),
5742 is_incomplete,
5743 buffer.clone(),
5744 completions.into(),
5745 display_options,
5746 snippet_sort_order,
5747 languages,
5748 language,
5749 cx,
5750 );
5751
5752 let query = if filter_completions { query } else { None };
5753 let matches_task = if let Some(query) = query {
5754 menu.do_async_filtering(query, cx)
5755 } else {
5756 Task::ready(menu.unfiltered_matches())
5757 };
5758 (menu, matches_task)
5759 }) else {
5760 return;
5761 };
5762
5763 let matches = matches_task.await;
5764
5765 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5766 // Newer menu already set, so exit.
5767 if let Some(CodeContextMenu::Completions(prev_menu)) =
5768 editor.context_menu.borrow().as_ref()
5769 && prev_menu.id > id
5770 {
5771 return;
5772 };
5773
5774 // Only valid to take prev_menu because it the new menu is immediately set
5775 // below, or the menu is hidden.
5776 if let Some(CodeContextMenu::Completions(prev_menu)) =
5777 editor.context_menu.borrow_mut().take()
5778 {
5779 let position_matches =
5780 if prev_menu.initial_position == menu.initial_position {
5781 true
5782 } else {
5783 let snapshot = editor.buffer.read(cx).read(cx);
5784 prev_menu.initial_position.to_offset(&snapshot)
5785 == menu.initial_position.to_offset(&snapshot)
5786 };
5787 if position_matches {
5788 // Preserve markdown cache before `set_filter_results` because it will
5789 // try to populate the documentation cache.
5790 menu.preserve_markdown_cache(prev_menu);
5791 }
5792 };
5793
5794 menu.set_filter_results(matches, provider, window, cx);
5795 }) else {
5796 return;
5797 };
5798
5799 menu.visible().then_some(menu)
5800 };
5801
5802 editor
5803 .update_in(cx, |editor, window, cx| {
5804 if editor.focus_handle.is_focused(window)
5805 && let Some(menu) = menu
5806 {
5807 *editor.context_menu.borrow_mut() =
5808 Some(CodeContextMenu::Completions(menu));
5809
5810 crate::hover_popover::hide_hover(editor, cx);
5811 if editor.show_edit_predictions_in_menu() {
5812 editor.update_visible_edit_prediction(window, cx);
5813 } else {
5814 editor.discard_edit_prediction(false, cx);
5815 }
5816
5817 cx.notify();
5818 return;
5819 }
5820
5821 if editor.completion_tasks.len() <= 1 {
5822 // If there are no more completion tasks and the last menu was empty, we should hide it.
5823 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5824 // If it was already hidden and we don't show edit predictions in the menu,
5825 // we should also show the edit prediction when available.
5826 if was_hidden && editor.show_edit_predictions_in_menu() {
5827 editor.update_visible_edit_prediction(window, cx);
5828 }
5829 }
5830 })
5831 .ok();
5832 });
5833
5834 self.completion_tasks.push((id, task));
5835 }
5836
5837 #[cfg(feature = "test-support")]
5838 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5839 let menu = self.context_menu.borrow();
5840 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5841 let completions = menu.completions.borrow();
5842 Some(completions.to_vec())
5843 } else {
5844 None
5845 }
5846 }
5847
5848 pub fn with_completions_menu_matching_id<R>(
5849 &self,
5850 id: CompletionId,
5851 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5852 ) -> R {
5853 let mut context_menu = self.context_menu.borrow_mut();
5854 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5855 return f(None);
5856 };
5857 if completions_menu.id != id {
5858 return f(None);
5859 }
5860 f(Some(completions_menu))
5861 }
5862
5863 pub fn confirm_completion(
5864 &mut self,
5865 action: &ConfirmCompletion,
5866 window: &mut Window,
5867 cx: &mut Context<Self>,
5868 ) -> Option<Task<Result<()>>> {
5869 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5870 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5871 }
5872
5873 pub fn confirm_completion_insert(
5874 &mut self,
5875 _: &ConfirmCompletionInsert,
5876 window: &mut Window,
5877 cx: &mut Context<Self>,
5878 ) -> Option<Task<Result<()>>> {
5879 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5880 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5881 }
5882
5883 pub fn confirm_completion_replace(
5884 &mut self,
5885 _: &ConfirmCompletionReplace,
5886 window: &mut Window,
5887 cx: &mut Context<Self>,
5888 ) -> Option<Task<Result<()>>> {
5889 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5890 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5891 }
5892
5893 pub fn compose_completion(
5894 &mut self,
5895 action: &ComposeCompletion,
5896 window: &mut Window,
5897 cx: &mut Context<Self>,
5898 ) -> Option<Task<Result<()>>> {
5899 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5900 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5901 }
5902
5903 fn do_completion(
5904 &mut self,
5905 item_ix: Option<usize>,
5906 intent: CompletionIntent,
5907 window: &mut Window,
5908 cx: &mut Context<Editor>,
5909 ) -> Option<Task<Result<()>>> {
5910 use language::ToOffset as _;
5911
5912 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5913 else {
5914 return None;
5915 };
5916
5917 let candidate_id = {
5918 let entries = completions_menu.entries.borrow();
5919 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5920 if self.show_edit_predictions_in_menu() {
5921 self.discard_edit_prediction(true, cx);
5922 }
5923 mat.candidate_id
5924 };
5925
5926 let completion = completions_menu
5927 .completions
5928 .borrow()
5929 .get(candidate_id)?
5930 .clone();
5931 cx.stop_propagation();
5932
5933 let buffer_handle = completions_menu.buffer.clone();
5934
5935 let CompletionEdit {
5936 new_text,
5937 snippet,
5938 replace_range,
5939 } = process_completion_for_edit(
5940 &completion,
5941 intent,
5942 &buffer_handle,
5943 &completions_menu.initial_position.text_anchor,
5944 cx,
5945 );
5946
5947 let buffer = buffer_handle.read(cx);
5948 let snapshot = self.buffer.read(cx).snapshot(cx);
5949 let newest_anchor = self.selections.newest_anchor();
5950 let replace_range_multibuffer = {
5951 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5952 let multibuffer_anchor = snapshot
5953 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5954 .unwrap()
5955 ..snapshot
5956 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5957 .unwrap();
5958 multibuffer_anchor.start.to_offset(&snapshot)
5959 ..multibuffer_anchor.end.to_offset(&snapshot)
5960 };
5961 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5962 return None;
5963 }
5964
5965 let old_text = buffer
5966 .text_for_range(replace_range.clone())
5967 .collect::<String>();
5968 let lookbehind = newest_anchor
5969 .start
5970 .text_anchor
5971 .to_offset(buffer)
5972 .saturating_sub(replace_range.start);
5973 let lookahead = replace_range
5974 .end
5975 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5976 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5977 let suffix = &old_text[lookbehind.min(old_text.len())..];
5978
5979 let selections = self.selections.all::<usize>(cx);
5980 let mut ranges = Vec::new();
5981 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5982
5983 for selection in &selections {
5984 let range = if selection.id == newest_anchor.id {
5985 replace_range_multibuffer.clone()
5986 } else {
5987 let mut range = selection.range();
5988
5989 // if prefix is present, don't duplicate it
5990 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5991 range.start = range.start.saturating_sub(lookbehind);
5992
5993 // if suffix is also present, mimic the newest cursor and replace it
5994 if selection.id != newest_anchor.id
5995 && snapshot.contains_str_at(range.end, suffix)
5996 {
5997 range.end += lookahead;
5998 }
5999 }
6000 range
6001 };
6002
6003 ranges.push(range.clone());
6004
6005 if !self.linked_edit_ranges.is_empty() {
6006 let start_anchor = snapshot.anchor_before(range.start);
6007 let end_anchor = snapshot.anchor_after(range.end);
6008 if let Some(ranges) = self
6009 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6010 {
6011 for (buffer, edits) in ranges {
6012 linked_edits
6013 .entry(buffer.clone())
6014 .or_default()
6015 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6016 }
6017 }
6018 }
6019 }
6020
6021 let common_prefix_len = old_text
6022 .chars()
6023 .zip(new_text.chars())
6024 .take_while(|(a, b)| a == b)
6025 .map(|(a, _)| a.len_utf8())
6026 .sum::<usize>();
6027
6028 cx.emit(EditorEvent::InputHandled {
6029 utf16_range_to_replace: None,
6030 text: new_text[common_prefix_len..].into(),
6031 });
6032
6033 self.transact(window, cx, |editor, window, cx| {
6034 if let Some(mut snippet) = snippet {
6035 snippet.text = new_text.to_string();
6036 editor
6037 .insert_snippet(&ranges, snippet, window, cx)
6038 .log_err();
6039 } else {
6040 editor.buffer.update(cx, |multi_buffer, cx| {
6041 let auto_indent = match completion.insert_text_mode {
6042 Some(InsertTextMode::AS_IS) => None,
6043 _ => editor.autoindent_mode.clone(),
6044 };
6045 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6046 multi_buffer.edit(edits, auto_indent, cx);
6047 });
6048 }
6049 for (buffer, edits) in linked_edits {
6050 buffer.update(cx, |buffer, cx| {
6051 let snapshot = buffer.snapshot();
6052 let edits = edits
6053 .into_iter()
6054 .map(|(range, text)| {
6055 use text::ToPoint as TP;
6056 let end_point = TP::to_point(&range.end, &snapshot);
6057 let start_point = TP::to_point(&range.start, &snapshot);
6058 (start_point..end_point, text)
6059 })
6060 .sorted_by_key(|(range, _)| range.start);
6061 buffer.edit(edits, None, cx);
6062 })
6063 }
6064
6065 editor.refresh_edit_prediction(true, false, window, cx);
6066 });
6067 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6068
6069 let show_new_completions_on_confirm = completion
6070 .confirm
6071 .as_ref()
6072 .is_some_and(|confirm| confirm(intent, window, cx));
6073 if show_new_completions_on_confirm {
6074 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6075 }
6076
6077 let provider = self.completion_provider.as_ref()?;
6078 drop(completion);
6079 let apply_edits = provider.apply_additional_edits_for_completion(
6080 buffer_handle,
6081 completions_menu.completions.clone(),
6082 candidate_id,
6083 true,
6084 cx,
6085 );
6086
6087 let editor_settings = EditorSettings::get_global(cx);
6088 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6089 // After the code completion is finished, users often want to know what signatures are needed.
6090 // so we should automatically call signature_help
6091 self.show_signature_help(&ShowSignatureHelp, window, cx);
6092 }
6093
6094 Some(cx.foreground_executor().spawn(async move {
6095 apply_edits.await?;
6096 Ok(())
6097 }))
6098 }
6099
6100 pub fn toggle_code_actions(
6101 &mut self,
6102 action: &ToggleCodeActions,
6103 window: &mut Window,
6104 cx: &mut Context<Self>,
6105 ) {
6106 let quick_launch = action.quick_launch;
6107 let mut context_menu = self.context_menu.borrow_mut();
6108 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6109 if code_actions.deployed_from == action.deployed_from {
6110 // Toggle if we're selecting the same one
6111 *context_menu = None;
6112 cx.notify();
6113 return;
6114 } else {
6115 // Otherwise, clear it and start a new one
6116 *context_menu = None;
6117 cx.notify();
6118 }
6119 }
6120 drop(context_menu);
6121 let snapshot = self.snapshot(window, cx);
6122 let deployed_from = action.deployed_from.clone();
6123 let action = action.clone();
6124 self.completion_tasks.clear();
6125 self.discard_edit_prediction(false, cx);
6126
6127 let multibuffer_point = match &action.deployed_from {
6128 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6129 DisplayPoint::new(*row, 0).to_point(&snapshot)
6130 }
6131 _ => self.selections.newest::<Point>(cx).head(),
6132 };
6133 let Some((buffer, buffer_row)) = snapshot
6134 .buffer_snapshot
6135 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6136 .and_then(|(buffer_snapshot, range)| {
6137 self.buffer()
6138 .read(cx)
6139 .buffer(buffer_snapshot.remote_id())
6140 .map(|buffer| (buffer, range.start.row))
6141 })
6142 else {
6143 return;
6144 };
6145 let buffer_id = buffer.read(cx).remote_id();
6146 let tasks = self
6147 .tasks
6148 .get(&(buffer_id, buffer_row))
6149 .map(|t| Arc::new(t.to_owned()));
6150
6151 if !self.focus_handle.is_focused(window) {
6152 return;
6153 }
6154 let project = self.project.clone();
6155
6156 let code_actions_task = match deployed_from {
6157 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6158 _ => self.code_actions(buffer_row, window, cx),
6159 };
6160
6161 let runnable_task = match deployed_from {
6162 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6163 _ => {
6164 let mut task_context_task = Task::ready(None);
6165 if let Some(tasks) = &tasks
6166 && let Some(project) = project
6167 {
6168 task_context_task =
6169 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6170 }
6171
6172 cx.spawn_in(window, {
6173 let buffer = buffer.clone();
6174 async move |editor, cx| {
6175 let task_context = task_context_task.await;
6176
6177 let resolved_tasks =
6178 tasks
6179 .zip(task_context.clone())
6180 .map(|(tasks, task_context)| ResolvedTasks {
6181 templates: tasks.resolve(&task_context).collect(),
6182 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6183 multibuffer_point.row,
6184 tasks.column,
6185 )),
6186 });
6187 let debug_scenarios = editor
6188 .update(cx, |editor, cx| {
6189 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6190 })?
6191 .await;
6192 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6193 }
6194 })
6195 }
6196 };
6197
6198 cx.spawn_in(window, async move |editor, cx| {
6199 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6200 let code_actions = code_actions_task.await;
6201 let spawn_straight_away = quick_launch
6202 && resolved_tasks
6203 .as_ref()
6204 .is_some_and(|tasks| tasks.templates.len() == 1)
6205 && code_actions
6206 .as_ref()
6207 .is_none_or(|actions| actions.is_empty())
6208 && debug_scenarios.is_empty();
6209
6210 editor.update_in(cx, |editor, window, cx| {
6211 crate::hover_popover::hide_hover(editor, cx);
6212 let actions = CodeActionContents::new(
6213 resolved_tasks,
6214 code_actions,
6215 debug_scenarios,
6216 task_context.unwrap_or_default(),
6217 );
6218
6219 // Don't show the menu if there are no actions available
6220 if actions.is_empty() {
6221 cx.notify();
6222 return Task::ready(Ok(()));
6223 }
6224
6225 *editor.context_menu.borrow_mut() =
6226 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6227 buffer,
6228 actions,
6229 selected_item: Default::default(),
6230 scroll_handle: UniformListScrollHandle::default(),
6231 deployed_from,
6232 }));
6233 cx.notify();
6234 if spawn_straight_away
6235 && let Some(task) = editor.confirm_code_action(
6236 &ConfirmCodeAction { item_ix: Some(0) },
6237 window,
6238 cx,
6239 )
6240 {
6241 return task;
6242 }
6243
6244 Task::ready(Ok(()))
6245 })
6246 })
6247 .detach_and_log_err(cx);
6248 }
6249
6250 fn debug_scenarios(
6251 &mut self,
6252 resolved_tasks: &Option<ResolvedTasks>,
6253 buffer: &Entity<Buffer>,
6254 cx: &mut App,
6255 ) -> Task<Vec<task::DebugScenario>> {
6256 maybe!({
6257 let project = self.project()?;
6258 let dap_store = project.read(cx).dap_store();
6259 let mut scenarios = vec![];
6260 let resolved_tasks = resolved_tasks.as_ref()?;
6261 let buffer = buffer.read(cx);
6262 let language = buffer.language()?;
6263 let file = buffer.file();
6264 let debug_adapter = language_settings(language.name().into(), file, cx)
6265 .debuggers
6266 .first()
6267 .map(SharedString::from)
6268 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6269
6270 dap_store.update(cx, |dap_store, cx| {
6271 for (_, task) in &resolved_tasks.templates {
6272 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6273 task.original_task().clone(),
6274 debug_adapter.clone().into(),
6275 task.display_label().to_owned().into(),
6276 cx,
6277 );
6278 scenarios.push(maybe_scenario);
6279 }
6280 });
6281 Some(cx.background_spawn(async move {
6282 futures::future::join_all(scenarios)
6283 .await
6284 .into_iter()
6285 .flatten()
6286 .collect::<Vec<_>>()
6287 }))
6288 })
6289 .unwrap_or_else(|| Task::ready(vec![]))
6290 }
6291
6292 fn code_actions(
6293 &mut self,
6294 buffer_row: u32,
6295 window: &mut Window,
6296 cx: &mut Context<Self>,
6297 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6298 let mut task = self.code_actions_task.take();
6299 cx.spawn_in(window, async move |editor, cx| {
6300 while let Some(prev_task) = task {
6301 prev_task.await.log_err();
6302 task = editor
6303 .update(cx, |this, _| this.code_actions_task.take())
6304 .ok()?;
6305 }
6306
6307 editor
6308 .update(cx, |editor, cx| {
6309 editor
6310 .available_code_actions
6311 .clone()
6312 .and_then(|(location, code_actions)| {
6313 let snapshot = location.buffer.read(cx).snapshot();
6314 let point_range = location.range.to_point(&snapshot);
6315 let point_range = point_range.start.row..=point_range.end.row;
6316 if point_range.contains(&buffer_row) {
6317 Some(code_actions)
6318 } else {
6319 None
6320 }
6321 })
6322 })
6323 .ok()
6324 .flatten()
6325 })
6326 }
6327
6328 pub fn confirm_code_action(
6329 &mut self,
6330 action: &ConfirmCodeAction,
6331 window: &mut Window,
6332 cx: &mut Context<Self>,
6333 ) -> Option<Task<Result<()>>> {
6334 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6335
6336 let actions_menu =
6337 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6338 menu
6339 } else {
6340 return None;
6341 };
6342
6343 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6344 let action = actions_menu.actions.get(action_ix)?;
6345 let title = action.label();
6346 let buffer = actions_menu.buffer;
6347 let workspace = self.workspace()?;
6348
6349 match action {
6350 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6351 workspace.update(cx, |workspace, cx| {
6352 workspace.schedule_resolved_task(
6353 task_source_kind,
6354 resolved_task,
6355 false,
6356 window,
6357 cx,
6358 );
6359
6360 Some(Task::ready(Ok(())))
6361 })
6362 }
6363 CodeActionsItem::CodeAction {
6364 excerpt_id,
6365 action,
6366 provider,
6367 } => {
6368 let apply_code_action =
6369 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6370 let workspace = workspace.downgrade();
6371 Some(cx.spawn_in(window, async move |editor, cx| {
6372 let project_transaction = apply_code_action.await?;
6373 Self::open_project_transaction(
6374 &editor,
6375 workspace,
6376 project_transaction,
6377 title,
6378 cx,
6379 )
6380 .await
6381 }))
6382 }
6383 CodeActionsItem::DebugScenario(scenario) => {
6384 let context = actions_menu.actions.context;
6385
6386 workspace.update(cx, |workspace, cx| {
6387 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6388 workspace.start_debug_session(
6389 scenario,
6390 context,
6391 Some(buffer),
6392 None,
6393 window,
6394 cx,
6395 );
6396 });
6397 Some(Task::ready(Ok(())))
6398 }
6399 }
6400 }
6401
6402 pub async fn open_project_transaction(
6403 editor: &WeakEntity<Editor>,
6404 workspace: WeakEntity<Workspace>,
6405 transaction: ProjectTransaction,
6406 title: String,
6407 cx: &mut AsyncWindowContext,
6408 ) -> Result<()> {
6409 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6410 cx.update(|_, cx| {
6411 entries.sort_unstable_by_key(|(buffer, _)| {
6412 buffer.read(cx).file().map(|f| f.path().clone())
6413 });
6414 })?;
6415
6416 // If the project transaction's edits are all contained within this editor, then
6417 // avoid opening a new editor to display them.
6418
6419 if let Some((buffer, transaction)) = entries.first() {
6420 if entries.len() == 1 {
6421 let excerpt = editor.update(cx, |editor, cx| {
6422 editor
6423 .buffer()
6424 .read(cx)
6425 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6426 })?;
6427 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6428 && excerpted_buffer == *buffer
6429 {
6430 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6431 let excerpt_range = excerpt_range.to_offset(buffer);
6432 buffer
6433 .edited_ranges_for_transaction::<usize>(transaction)
6434 .all(|range| {
6435 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6436 })
6437 })?;
6438
6439 if all_edits_within_excerpt {
6440 return Ok(());
6441 }
6442 }
6443 }
6444 } else {
6445 return Ok(());
6446 }
6447
6448 let mut ranges_to_highlight = Vec::new();
6449 let excerpt_buffer = cx.new(|cx| {
6450 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6451 for (buffer_handle, transaction) in &entries {
6452 let edited_ranges = buffer_handle
6453 .read(cx)
6454 .edited_ranges_for_transaction::<Point>(transaction)
6455 .collect::<Vec<_>>();
6456 let (ranges, _) = multibuffer.set_excerpts_for_path(
6457 PathKey::for_buffer(buffer_handle, cx),
6458 buffer_handle.clone(),
6459 edited_ranges,
6460 multibuffer_context_lines(cx),
6461 cx,
6462 );
6463
6464 ranges_to_highlight.extend(ranges);
6465 }
6466 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6467 multibuffer
6468 })?;
6469
6470 workspace.update_in(cx, |workspace, window, cx| {
6471 let project = workspace.project().clone();
6472 let editor =
6473 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6474 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6475 editor.update(cx, |editor, cx| {
6476 editor.highlight_background::<Self>(
6477 &ranges_to_highlight,
6478 |theme| theme.colors().editor_highlighted_line_background,
6479 cx,
6480 );
6481 });
6482 })?;
6483
6484 Ok(())
6485 }
6486
6487 pub fn clear_code_action_providers(&mut self) {
6488 self.code_action_providers.clear();
6489 self.available_code_actions.take();
6490 }
6491
6492 pub fn add_code_action_provider(
6493 &mut self,
6494 provider: Rc<dyn CodeActionProvider>,
6495 window: &mut Window,
6496 cx: &mut Context<Self>,
6497 ) {
6498 if self
6499 .code_action_providers
6500 .iter()
6501 .any(|existing_provider| existing_provider.id() == provider.id())
6502 {
6503 return;
6504 }
6505
6506 self.code_action_providers.push(provider);
6507 self.refresh_code_actions(window, cx);
6508 }
6509
6510 pub fn remove_code_action_provider(
6511 &mut self,
6512 id: Arc<str>,
6513 window: &mut Window,
6514 cx: &mut Context<Self>,
6515 ) {
6516 self.code_action_providers
6517 .retain(|provider| provider.id() != id);
6518 self.refresh_code_actions(window, cx);
6519 }
6520
6521 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6522 !self.code_action_providers.is_empty()
6523 && EditorSettings::get_global(cx).toolbar.code_actions
6524 }
6525
6526 pub fn has_available_code_actions(&self) -> bool {
6527 self.available_code_actions
6528 .as_ref()
6529 .is_some_and(|(_, actions)| !actions.is_empty())
6530 }
6531
6532 fn render_inline_code_actions(
6533 &self,
6534 icon_size: ui::IconSize,
6535 display_row: DisplayRow,
6536 is_active: bool,
6537 cx: &mut Context<Self>,
6538 ) -> AnyElement {
6539 let show_tooltip = !self.context_menu_visible();
6540 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6541 .icon_size(icon_size)
6542 .shape(ui::IconButtonShape::Square)
6543 .icon_color(ui::Color::Hidden)
6544 .toggle_state(is_active)
6545 .when(show_tooltip, |this| {
6546 this.tooltip({
6547 let focus_handle = self.focus_handle.clone();
6548 move |window, cx| {
6549 Tooltip::for_action_in(
6550 "Toggle Code Actions",
6551 &ToggleCodeActions {
6552 deployed_from: None,
6553 quick_launch: false,
6554 },
6555 &focus_handle,
6556 window,
6557 cx,
6558 )
6559 }
6560 })
6561 })
6562 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6563 window.focus(&editor.focus_handle(cx));
6564 editor.toggle_code_actions(
6565 &crate::actions::ToggleCodeActions {
6566 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6567 display_row,
6568 )),
6569 quick_launch: false,
6570 },
6571 window,
6572 cx,
6573 );
6574 }))
6575 .into_any_element()
6576 }
6577
6578 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6579 &self.context_menu
6580 }
6581
6582 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6583 let newest_selection = self.selections.newest_anchor().clone();
6584 let newest_selection_adjusted = self.selections.newest_adjusted(cx);
6585 let buffer = self.buffer.read(cx);
6586 if newest_selection.head().diff_base_anchor.is_some() {
6587 return None;
6588 }
6589 let (start_buffer, start) =
6590 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6591 let (end_buffer, end) =
6592 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6593 if start_buffer != end_buffer {
6594 return None;
6595 }
6596
6597 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6598 cx.background_executor()
6599 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6600 .await;
6601
6602 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6603 let providers = this.code_action_providers.clone();
6604 let tasks = this
6605 .code_action_providers
6606 .iter()
6607 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6608 .collect::<Vec<_>>();
6609 (providers, tasks)
6610 })?;
6611
6612 let mut actions = Vec::new();
6613 for (provider, provider_actions) in
6614 providers.into_iter().zip(future::join_all(tasks).await)
6615 {
6616 if let Some(provider_actions) = provider_actions.log_err() {
6617 actions.extend(provider_actions.into_iter().map(|action| {
6618 AvailableCodeAction {
6619 excerpt_id: newest_selection.start.excerpt_id,
6620 action,
6621 provider: provider.clone(),
6622 }
6623 }));
6624 }
6625 }
6626
6627 this.update(cx, |this, cx| {
6628 this.available_code_actions = if actions.is_empty() {
6629 None
6630 } else {
6631 Some((
6632 Location {
6633 buffer: start_buffer,
6634 range: start..end,
6635 },
6636 actions.into(),
6637 ))
6638 };
6639 cx.notify();
6640 })
6641 }));
6642 None
6643 }
6644
6645 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6646 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6647 self.show_git_blame_inline = false;
6648
6649 self.show_git_blame_inline_delay_task =
6650 Some(cx.spawn_in(window, async move |this, cx| {
6651 cx.background_executor().timer(delay).await;
6652
6653 this.update(cx, |this, cx| {
6654 this.show_git_blame_inline = true;
6655 cx.notify();
6656 })
6657 .log_err();
6658 }));
6659 }
6660 }
6661
6662 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6663 let snapshot = self.snapshot(window, cx);
6664 let cursor = self.selections.newest::<Point>(cx).head();
6665 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6666 else {
6667 return;
6668 };
6669
6670 let Some(blame) = self.blame.as_ref() else {
6671 return;
6672 };
6673
6674 let row_info = RowInfo {
6675 buffer_id: Some(buffer.remote_id()),
6676 buffer_row: Some(point.row),
6677 ..Default::default()
6678 };
6679 let Some((buffer, blame_entry)) = blame
6680 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6681 .flatten()
6682 else {
6683 return;
6684 };
6685
6686 let anchor = self.selections.newest_anchor().head();
6687 let position = self.to_pixel_point(anchor, &snapshot, window);
6688 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6689 self.show_blame_popover(
6690 buffer,
6691 &blame_entry,
6692 position + last_bounds.origin,
6693 true,
6694 cx,
6695 );
6696 };
6697 }
6698
6699 fn show_blame_popover(
6700 &mut self,
6701 buffer: BufferId,
6702 blame_entry: &BlameEntry,
6703 position: gpui::Point<Pixels>,
6704 ignore_timeout: bool,
6705 cx: &mut Context<Self>,
6706 ) {
6707 if let Some(state) = &mut self.inline_blame_popover {
6708 state.hide_task.take();
6709 } else {
6710 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6711 let blame_entry = blame_entry.clone();
6712 let show_task = cx.spawn(async move |editor, cx| {
6713 if !ignore_timeout {
6714 cx.background_executor()
6715 .timer(std::time::Duration::from_millis(blame_popover_delay))
6716 .await;
6717 }
6718 editor
6719 .update(cx, |editor, cx| {
6720 editor.inline_blame_popover_show_task.take();
6721 let Some(blame) = editor.blame.as_ref() else {
6722 return;
6723 };
6724 let blame = blame.read(cx);
6725 let details = blame.details_for_entry(buffer, &blame_entry);
6726 let markdown = cx.new(|cx| {
6727 Markdown::new(
6728 details
6729 .as_ref()
6730 .map(|message| message.message.clone())
6731 .unwrap_or_default(),
6732 None,
6733 None,
6734 cx,
6735 )
6736 });
6737 editor.inline_blame_popover = Some(InlineBlamePopover {
6738 position,
6739 hide_task: None,
6740 popover_bounds: None,
6741 popover_state: InlineBlamePopoverState {
6742 scroll_handle: ScrollHandle::new(),
6743 commit_message: details,
6744 markdown,
6745 },
6746 keyboard_grace: ignore_timeout,
6747 });
6748 cx.notify();
6749 })
6750 .ok();
6751 });
6752 self.inline_blame_popover_show_task = Some(show_task);
6753 }
6754 }
6755
6756 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6757 self.inline_blame_popover_show_task.take();
6758 if let Some(state) = &mut self.inline_blame_popover {
6759 let hide_task = cx.spawn(async move |editor, cx| {
6760 cx.background_executor()
6761 .timer(std::time::Duration::from_millis(100))
6762 .await;
6763 editor
6764 .update(cx, |editor, cx| {
6765 editor.inline_blame_popover.take();
6766 cx.notify();
6767 })
6768 .ok();
6769 });
6770 state.hide_task = Some(hide_task);
6771 }
6772 }
6773
6774 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6775 if self.pending_rename.is_some() {
6776 return None;
6777 }
6778
6779 let provider = self.semantics_provider.clone()?;
6780 let buffer = self.buffer.read(cx);
6781 let newest_selection = self.selections.newest_anchor().clone();
6782 let cursor_position = newest_selection.head();
6783 let (cursor_buffer, cursor_buffer_position) =
6784 buffer.text_anchor_for_position(cursor_position, cx)?;
6785 let (tail_buffer, tail_buffer_position) =
6786 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6787 if cursor_buffer != tail_buffer {
6788 return None;
6789 }
6790
6791 let snapshot = cursor_buffer.read(cx).snapshot();
6792 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6793 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6794 if start_word_range != end_word_range {
6795 self.document_highlights_task.take();
6796 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6797 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6798 return None;
6799 }
6800
6801 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6802 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6803 cx.background_executor()
6804 .timer(Duration::from_millis(debounce))
6805 .await;
6806
6807 let highlights = if let Some(highlights) = cx
6808 .update(|cx| {
6809 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6810 })
6811 .ok()
6812 .flatten()
6813 {
6814 highlights.await.log_err()
6815 } else {
6816 None
6817 };
6818
6819 if let Some(highlights) = highlights {
6820 this.update(cx, |this, cx| {
6821 if this.pending_rename.is_some() {
6822 return;
6823 }
6824
6825 let buffer = this.buffer.read(cx);
6826 if buffer
6827 .text_anchor_for_position(cursor_position, cx)
6828 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6829 {
6830 return;
6831 }
6832
6833 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6834 let mut write_ranges = Vec::new();
6835 let mut read_ranges = Vec::new();
6836 for highlight in highlights {
6837 let buffer_id = cursor_buffer.read(cx).remote_id();
6838 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6839 {
6840 let start = highlight
6841 .range
6842 .start
6843 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6844 let end = highlight
6845 .range
6846 .end
6847 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6848 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6849 continue;
6850 }
6851
6852 let range = Anchor {
6853 buffer_id: Some(buffer_id),
6854 excerpt_id,
6855 text_anchor: start,
6856 diff_base_anchor: None,
6857 }..Anchor {
6858 buffer_id: Some(buffer_id),
6859 excerpt_id,
6860 text_anchor: end,
6861 diff_base_anchor: None,
6862 };
6863 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6864 write_ranges.push(range);
6865 } else {
6866 read_ranges.push(range);
6867 }
6868 }
6869 }
6870
6871 this.highlight_background::<DocumentHighlightRead>(
6872 &read_ranges,
6873 |theme| theme.colors().editor_document_highlight_read_background,
6874 cx,
6875 );
6876 this.highlight_background::<DocumentHighlightWrite>(
6877 &write_ranges,
6878 |theme| theme.colors().editor_document_highlight_write_background,
6879 cx,
6880 );
6881 cx.notify();
6882 })
6883 .log_err();
6884 }
6885 }));
6886 None
6887 }
6888
6889 fn prepare_highlight_query_from_selection(
6890 &mut self,
6891 cx: &mut Context<Editor>,
6892 ) -> Option<(String, Range<Anchor>)> {
6893 if matches!(self.mode, EditorMode::SingleLine) {
6894 return None;
6895 }
6896 if !EditorSettings::get_global(cx).selection_highlight {
6897 return None;
6898 }
6899 if self.selections.count() != 1 || self.selections.line_mode {
6900 return None;
6901 }
6902 let selection = self.selections.newest::<Point>(cx);
6903 if selection.is_empty() || selection.start.row != selection.end.row {
6904 return None;
6905 }
6906 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6907 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6908 let query = multi_buffer_snapshot
6909 .text_for_range(selection_anchor_range.clone())
6910 .collect::<String>();
6911 if query.trim().is_empty() {
6912 return None;
6913 }
6914 Some((query, selection_anchor_range))
6915 }
6916
6917 fn update_selection_occurrence_highlights(
6918 &mut self,
6919 query_text: String,
6920 query_range: Range<Anchor>,
6921 multi_buffer_range_to_query: Range<Point>,
6922 use_debounce: bool,
6923 window: &mut Window,
6924 cx: &mut Context<Editor>,
6925 ) -> Task<()> {
6926 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6927 cx.spawn_in(window, async move |editor, cx| {
6928 if use_debounce {
6929 cx.background_executor()
6930 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6931 .await;
6932 }
6933 let match_task = cx.background_spawn(async move {
6934 let buffer_ranges = multi_buffer_snapshot
6935 .range_to_buffer_ranges(multi_buffer_range_to_query)
6936 .into_iter()
6937 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6938 let mut match_ranges = Vec::new();
6939 let Ok(regex) = project::search::SearchQuery::text(
6940 query_text.clone(),
6941 false,
6942 false,
6943 false,
6944 Default::default(),
6945 Default::default(),
6946 false,
6947 None,
6948 ) else {
6949 return Vec::default();
6950 };
6951 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6952 match_ranges.extend(
6953 regex
6954 .search(buffer_snapshot, Some(search_range.clone()))
6955 .await
6956 .into_iter()
6957 .filter_map(|match_range| {
6958 let match_start = buffer_snapshot
6959 .anchor_after(search_range.start + match_range.start);
6960 let match_end = buffer_snapshot
6961 .anchor_before(search_range.start + match_range.end);
6962 let match_anchor_range = Anchor::range_in_buffer(
6963 excerpt_id,
6964 buffer_snapshot.remote_id(),
6965 match_start..match_end,
6966 );
6967 (match_anchor_range != query_range).then_some(match_anchor_range)
6968 }),
6969 );
6970 }
6971 match_ranges
6972 });
6973 let match_ranges = match_task.await;
6974 editor
6975 .update_in(cx, |editor, _, cx| {
6976 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6977 if !match_ranges.is_empty() {
6978 editor.highlight_background::<SelectedTextHighlight>(
6979 &match_ranges,
6980 |theme| theme.colors().editor_document_highlight_bracket_background,
6981 cx,
6982 )
6983 }
6984 })
6985 .log_err();
6986 })
6987 }
6988
6989 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6990 struct NewlineFold;
6991 let type_id = std::any::TypeId::of::<NewlineFold>();
6992 if !self.mode.is_single_line() {
6993 return;
6994 }
6995 let snapshot = self.snapshot(window, cx);
6996 if snapshot.buffer_snapshot.max_point().row == 0 {
6997 return;
6998 }
6999 let task = cx.background_spawn(async move {
7000 let new_newlines = snapshot
7001 .buffer_chars_at(0)
7002 .filter_map(|(c, i)| {
7003 if c == '\n' {
7004 Some(
7005 snapshot.buffer_snapshot.anchor_after(i)
7006 ..snapshot.buffer_snapshot.anchor_before(i + 1),
7007 )
7008 } else {
7009 None
7010 }
7011 })
7012 .collect::<Vec<_>>();
7013 let existing_newlines = snapshot
7014 .folds_in_range(0..snapshot.buffer_snapshot.len())
7015 .filter_map(|fold| {
7016 if fold.placeholder.type_tag == Some(type_id) {
7017 Some(fold.range.start..fold.range.end)
7018 } else {
7019 None
7020 }
7021 })
7022 .collect::<Vec<_>>();
7023
7024 (new_newlines, existing_newlines)
7025 });
7026 self.folding_newlines = cx.spawn(async move |this, cx| {
7027 let (new_newlines, existing_newlines) = task.await;
7028 if new_newlines == existing_newlines {
7029 return;
7030 }
7031 let placeholder = FoldPlaceholder {
7032 render: Arc::new(move |_, _, cx| {
7033 div()
7034 .bg(cx.theme().status().hint_background)
7035 .border_b_1()
7036 .size_full()
7037 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7038 .border_color(cx.theme().status().hint)
7039 .child("\\n")
7040 .into_any()
7041 }),
7042 constrain_width: false,
7043 merge_adjacent: false,
7044 type_tag: Some(type_id),
7045 };
7046 let creases = new_newlines
7047 .into_iter()
7048 .map(|range| Crease::simple(range, placeholder.clone()))
7049 .collect();
7050 this.update(cx, |this, cx| {
7051 this.display_map.update(cx, |display_map, cx| {
7052 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7053 display_map.fold(creases, cx);
7054 });
7055 })
7056 .ok();
7057 });
7058 }
7059
7060 fn refresh_selected_text_highlights(
7061 &mut self,
7062 on_buffer_edit: bool,
7063 window: &mut Window,
7064 cx: &mut Context<Editor>,
7065 ) {
7066 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7067 else {
7068 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7069 self.quick_selection_highlight_task.take();
7070 self.debounced_selection_highlight_task.take();
7071 return;
7072 };
7073 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7074 if on_buffer_edit
7075 || self
7076 .quick_selection_highlight_task
7077 .as_ref()
7078 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7079 {
7080 let multi_buffer_visible_start = self
7081 .scroll_manager
7082 .anchor()
7083 .anchor
7084 .to_point(&multi_buffer_snapshot);
7085 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7086 multi_buffer_visible_start
7087 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7088 Bias::Left,
7089 );
7090 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7091 self.quick_selection_highlight_task = Some((
7092 query_range.clone(),
7093 self.update_selection_occurrence_highlights(
7094 query_text.clone(),
7095 query_range.clone(),
7096 multi_buffer_visible_range,
7097 false,
7098 window,
7099 cx,
7100 ),
7101 ));
7102 }
7103 if on_buffer_edit
7104 || self
7105 .debounced_selection_highlight_task
7106 .as_ref()
7107 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7108 {
7109 let multi_buffer_start = multi_buffer_snapshot
7110 .anchor_before(0)
7111 .to_point(&multi_buffer_snapshot);
7112 let multi_buffer_end = multi_buffer_snapshot
7113 .anchor_after(multi_buffer_snapshot.len())
7114 .to_point(&multi_buffer_snapshot);
7115 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7116 self.debounced_selection_highlight_task = Some((
7117 query_range.clone(),
7118 self.update_selection_occurrence_highlights(
7119 query_text,
7120 query_range,
7121 multi_buffer_full_range,
7122 true,
7123 window,
7124 cx,
7125 ),
7126 ));
7127 }
7128 }
7129
7130 pub fn refresh_edit_prediction(
7131 &mut self,
7132 debounce: bool,
7133 user_requested: bool,
7134 window: &mut Window,
7135 cx: &mut Context<Self>,
7136 ) -> Option<()> {
7137 if DisableAiSettings::get_global(cx).disable_ai {
7138 return None;
7139 }
7140
7141 let provider = self.edit_prediction_provider()?;
7142 let cursor = self.selections.newest_anchor().head();
7143 let (buffer, cursor_buffer_position) =
7144 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7145
7146 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7147 self.discard_edit_prediction(false, cx);
7148 return None;
7149 }
7150
7151 if !user_requested
7152 && (!self.should_show_edit_predictions()
7153 || !self.is_focused(window)
7154 || buffer.read(cx).is_empty())
7155 {
7156 self.discard_edit_prediction(false, cx);
7157 return None;
7158 }
7159
7160 self.update_visible_edit_prediction(window, cx);
7161 provider.refresh(
7162 self.project.clone(),
7163 buffer,
7164 cursor_buffer_position,
7165 debounce,
7166 cx,
7167 );
7168 Some(())
7169 }
7170
7171 fn show_edit_predictions_in_menu(&self) -> bool {
7172 match self.edit_prediction_settings {
7173 EditPredictionSettings::Disabled => false,
7174 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7175 }
7176 }
7177
7178 pub fn edit_predictions_enabled(&self) -> bool {
7179 match self.edit_prediction_settings {
7180 EditPredictionSettings::Disabled => false,
7181 EditPredictionSettings::Enabled { .. } => true,
7182 }
7183 }
7184
7185 fn edit_prediction_requires_modifier(&self) -> bool {
7186 match self.edit_prediction_settings {
7187 EditPredictionSettings::Disabled => false,
7188 EditPredictionSettings::Enabled {
7189 preview_requires_modifier,
7190 ..
7191 } => preview_requires_modifier,
7192 }
7193 }
7194
7195 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7196 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7197 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7198 self.discard_edit_prediction(false, cx);
7199 } else {
7200 let selection = self.selections.newest_anchor();
7201 let cursor = selection.head();
7202
7203 if let Some((buffer, cursor_buffer_position)) =
7204 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7205 {
7206 self.edit_prediction_settings =
7207 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7208 }
7209 }
7210 }
7211
7212 fn edit_prediction_settings_at_position(
7213 &self,
7214 buffer: &Entity<Buffer>,
7215 buffer_position: language::Anchor,
7216 cx: &App,
7217 ) -> EditPredictionSettings {
7218 if !self.mode.is_full()
7219 || !self.show_edit_predictions_override.unwrap_or(true)
7220 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7221 {
7222 return EditPredictionSettings::Disabled;
7223 }
7224
7225 let buffer = buffer.read(cx);
7226
7227 let file = buffer.file();
7228
7229 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7230 return EditPredictionSettings::Disabled;
7231 };
7232
7233 let by_provider = matches!(
7234 self.menu_edit_predictions_policy,
7235 MenuEditPredictionsPolicy::ByProvider
7236 );
7237
7238 let show_in_menu = by_provider
7239 && self
7240 .edit_prediction_provider
7241 .as_ref()
7242 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7243
7244 let preview_requires_modifier =
7245 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7246
7247 EditPredictionSettings::Enabled {
7248 show_in_menu,
7249 preview_requires_modifier,
7250 }
7251 }
7252
7253 fn should_show_edit_predictions(&self) -> bool {
7254 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7255 }
7256
7257 pub fn edit_prediction_preview_is_active(&self) -> bool {
7258 matches!(
7259 self.edit_prediction_preview,
7260 EditPredictionPreview::Active { .. }
7261 )
7262 }
7263
7264 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7265 let cursor = self.selections.newest_anchor().head();
7266 if let Some((buffer, cursor_position)) =
7267 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7268 {
7269 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7270 } else {
7271 false
7272 }
7273 }
7274
7275 pub fn supports_minimap(&self, cx: &App) -> bool {
7276 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7277 }
7278
7279 fn edit_predictions_enabled_in_buffer(
7280 &self,
7281 buffer: &Entity<Buffer>,
7282 buffer_position: language::Anchor,
7283 cx: &App,
7284 ) -> bool {
7285 maybe!({
7286 if self.read_only(cx) {
7287 return Some(false);
7288 }
7289 let provider = self.edit_prediction_provider()?;
7290 if !provider.is_enabled(buffer, buffer_position, cx) {
7291 return Some(false);
7292 }
7293 let buffer = buffer.read(cx);
7294 let Some(file) = buffer.file() else {
7295 return Some(true);
7296 };
7297 let settings = all_language_settings(Some(file), cx);
7298 Some(settings.edit_predictions_enabled_for_file(file, cx))
7299 })
7300 .unwrap_or(false)
7301 }
7302
7303 fn cycle_edit_prediction(
7304 &mut self,
7305 direction: Direction,
7306 window: &mut Window,
7307 cx: &mut Context<Self>,
7308 ) -> Option<()> {
7309 let provider = self.edit_prediction_provider()?;
7310 let cursor = self.selections.newest_anchor().head();
7311 let (buffer, cursor_buffer_position) =
7312 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7313 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7314 return None;
7315 }
7316
7317 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7318 self.update_visible_edit_prediction(window, cx);
7319
7320 Some(())
7321 }
7322
7323 pub fn show_edit_prediction(
7324 &mut self,
7325 _: &ShowEditPrediction,
7326 window: &mut Window,
7327 cx: &mut Context<Self>,
7328 ) {
7329 if !self.has_active_edit_prediction() {
7330 self.refresh_edit_prediction(false, true, window, cx);
7331 return;
7332 }
7333
7334 self.update_visible_edit_prediction(window, cx);
7335 }
7336
7337 pub fn display_cursor_names(
7338 &mut self,
7339 _: &DisplayCursorNames,
7340 window: &mut Window,
7341 cx: &mut Context<Self>,
7342 ) {
7343 self.show_cursor_names(window, cx);
7344 }
7345
7346 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7347 self.show_cursor_names = true;
7348 cx.notify();
7349 cx.spawn_in(window, async move |this, cx| {
7350 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7351 this.update(cx, |this, cx| {
7352 this.show_cursor_names = false;
7353 cx.notify()
7354 })
7355 .ok()
7356 })
7357 .detach();
7358 }
7359
7360 pub fn next_edit_prediction(
7361 &mut self,
7362 _: &NextEditPrediction,
7363 window: &mut Window,
7364 cx: &mut Context<Self>,
7365 ) {
7366 if self.has_active_edit_prediction() {
7367 self.cycle_edit_prediction(Direction::Next, window, cx);
7368 } else {
7369 let is_copilot_disabled = self
7370 .refresh_edit_prediction(false, true, window, cx)
7371 .is_none();
7372 if is_copilot_disabled {
7373 cx.propagate();
7374 }
7375 }
7376 }
7377
7378 pub fn previous_edit_prediction(
7379 &mut self,
7380 _: &PreviousEditPrediction,
7381 window: &mut Window,
7382 cx: &mut Context<Self>,
7383 ) {
7384 if self.has_active_edit_prediction() {
7385 self.cycle_edit_prediction(Direction::Prev, window, cx);
7386 } else {
7387 let is_copilot_disabled = self
7388 .refresh_edit_prediction(false, true, window, cx)
7389 .is_none();
7390 if is_copilot_disabled {
7391 cx.propagate();
7392 }
7393 }
7394 }
7395
7396 pub fn accept_edit_prediction(
7397 &mut self,
7398 _: &AcceptEditPrediction,
7399 window: &mut Window,
7400 cx: &mut Context<Self>,
7401 ) {
7402 if self.show_edit_predictions_in_menu() {
7403 self.hide_context_menu(window, cx);
7404 }
7405
7406 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7407 return;
7408 };
7409
7410 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7411
7412 match &active_edit_prediction.completion {
7413 EditPrediction::Move { target, .. } => {
7414 let target = *target;
7415
7416 if let Some(position_map) = &self.last_position_map {
7417 if position_map
7418 .visible_row_range
7419 .contains(&target.to_display_point(&position_map.snapshot).row())
7420 || !self.edit_prediction_requires_modifier()
7421 {
7422 self.unfold_ranges(&[target..target], true, false, cx);
7423 // Note that this is also done in vim's handler of the Tab action.
7424 self.change_selections(
7425 SelectionEffects::scroll(Autoscroll::newest()),
7426 window,
7427 cx,
7428 |selections| {
7429 selections.select_anchor_ranges([target..target]);
7430 },
7431 );
7432 self.clear_row_highlights::<EditPredictionPreview>();
7433
7434 self.edit_prediction_preview
7435 .set_previous_scroll_position(None);
7436 } else {
7437 self.edit_prediction_preview
7438 .set_previous_scroll_position(Some(
7439 position_map.snapshot.scroll_anchor,
7440 ));
7441
7442 self.highlight_rows::<EditPredictionPreview>(
7443 target..target,
7444 cx.theme().colors().editor_highlighted_line_background,
7445 RowHighlightOptions {
7446 autoscroll: true,
7447 ..Default::default()
7448 },
7449 cx,
7450 );
7451 self.request_autoscroll(Autoscroll::fit(), cx);
7452 }
7453 }
7454 }
7455 EditPrediction::Edit { edits, .. } => {
7456 if let Some(provider) = self.edit_prediction_provider() {
7457 provider.accept(cx);
7458 }
7459
7460 // Store the transaction ID and selections before applying the edit
7461 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7462
7463 let snapshot = self.buffer.read(cx).snapshot(cx);
7464 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7465
7466 self.buffer.update(cx, |buffer, cx| {
7467 buffer.edit(edits.iter().cloned(), None, cx)
7468 });
7469
7470 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7471 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7472 });
7473
7474 let selections = self.selections.disjoint_anchors_arc();
7475 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7476 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7477 if has_new_transaction {
7478 self.selection_history
7479 .insert_transaction(transaction_id_now, selections);
7480 }
7481 }
7482
7483 self.update_visible_edit_prediction(window, cx);
7484 if self.active_edit_prediction.is_none() {
7485 self.refresh_edit_prediction(true, true, window, cx);
7486 }
7487
7488 cx.notify();
7489 }
7490 }
7491
7492 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7493 }
7494
7495 pub fn accept_partial_edit_prediction(
7496 &mut self,
7497 _: &AcceptPartialEditPrediction,
7498 window: &mut Window,
7499 cx: &mut Context<Self>,
7500 ) {
7501 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7502 return;
7503 };
7504 if self.selections.count() != 1 {
7505 return;
7506 }
7507
7508 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7509
7510 match &active_edit_prediction.completion {
7511 EditPrediction::Move { target, .. } => {
7512 let target = *target;
7513 self.change_selections(
7514 SelectionEffects::scroll(Autoscroll::newest()),
7515 window,
7516 cx,
7517 |selections| {
7518 selections.select_anchor_ranges([target..target]);
7519 },
7520 );
7521 }
7522 EditPrediction::Edit { edits, .. } => {
7523 // Find an insertion that starts at the cursor position.
7524 let snapshot = self.buffer.read(cx).snapshot(cx);
7525 let cursor_offset = self.selections.newest::<usize>(cx).head();
7526 let insertion = edits.iter().find_map(|(range, text)| {
7527 let range = range.to_offset(&snapshot);
7528 if range.is_empty() && range.start == cursor_offset {
7529 Some(text)
7530 } else {
7531 None
7532 }
7533 });
7534
7535 if let Some(text) = insertion {
7536 let mut partial_completion = text
7537 .chars()
7538 .by_ref()
7539 .take_while(|c| c.is_alphabetic())
7540 .collect::<String>();
7541 if partial_completion.is_empty() {
7542 partial_completion = text
7543 .chars()
7544 .by_ref()
7545 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7546 .collect::<String>();
7547 }
7548
7549 cx.emit(EditorEvent::InputHandled {
7550 utf16_range_to_replace: None,
7551 text: partial_completion.clone().into(),
7552 });
7553
7554 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7555
7556 self.refresh_edit_prediction(true, true, window, cx);
7557 cx.notify();
7558 } else {
7559 self.accept_edit_prediction(&Default::default(), window, cx);
7560 }
7561 }
7562 }
7563 }
7564
7565 fn discard_edit_prediction(
7566 &mut self,
7567 should_report_edit_prediction_event: bool,
7568 cx: &mut Context<Self>,
7569 ) -> bool {
7570 if should_report_edit_prediction_event {
7571 let completion_id = self
7572 .active_edit_prediction
7573 .as_ref()
7574 .and_then(|active_completion| active_completion.completion_id.clone());
7575
7576 self.report_edit_prediction_event(completion_id, false, cx);
7577 }
7578
7579 if let Some(provider) = self.edit_prediction_provider() {
7580 provider.discard(cx);
7581 }
7582
7583 self.take_active_edit_prediction(cx)
7584 }
7585
7586 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7587 let Some(provider) = self.edit_prediction_provider() else {
7588 return;
7589 };
7590
7591 let Some((_, buffer, _)) = self
7592 .buffer
7593 .read(cx)
7594 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7595 else {
7596 return;
7597 };
7598
7599 let extension = buffer
7600 .read(cx)
7601 .file()
7602 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7603
7604 let event_type = match accepted {
7605 true => "Edit Prediction Accepted",
7606 false => "Edit Prediction Discarded",
7607 };
7608 telemetry::event!(
7609 event_type,
7610 provider = provider.name(),
7611 prediction_id = id,
7612 suggestion_accepted = accepted,
7613 file_extension = extension,
7614 );
7615 }
7616
7617 pub fn has_active_edit_prediction(&self) -> bool {
7618 self.active_edit_prediction.is_some()
7619 }
7620
7621 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7622 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7623 return false;
7624 };
7625
7626 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7627 self.clear_highlights::<EditPredictionHighlight>(cx);
7628 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7629 true
7630 }
7631
7632 /// Returns true when we're displaying the edit prediction popover below the cursor
7633 /// like we are not previewing and the LSP autocomplete menu is visible
7634 /// or we are in `when_holding_modifier` mode.
7635 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7636 if self.edit_prediction_preview_is_active()
7637 || !self.show_edit_predictions_in_menu()
7638 || !self.edit_predictions_enabled()
7639 {
7640 return false;
7641 }
7642
7643 if self.has_visible_completions_menu() {
7644 return true;
7645 }
7646
7647 has_completion && self.edit_prediction_requires_modifier()
7648 }
7649
7650 fn handle_modifiers_changed(
7651 &mut self,
7652 modifiers: Modifiers,
7653 position_map: &PositionMap,
7654 window: &mut Window,
7655 cx: &mut Context<Self>,
7656 ) {
7657 if self.show_edit_predictions_in_menu() {
7658 self.update_edit_prediction_preview(&modifiers, window, cx);
7659 }
7660
7661 self.update_selection_mode(&modifiers, position_map, window, cx);
7662
7663 let mouse_position = window.mouse_position();
7664 if !position_map.text_hitbox.is_hovered(window) {
7665 return;
7666 }
7667
7668 self.update_hovered_link(
7669 position_map.point_for_position(mouse_position),
7670 &position_map.snapshot,
7671 modifiers,
7672 window,
7673 cx,
7674 )
7675 }
7676
7677 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7678 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7679 if invert {
7680 match multi_cursor_setting {
7681 MultiCursorModifier::Alt => modifiers.alt,
7682 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7683 }
7684 } else {
7685 match multi_cursor_setting {
7686 MultiCursorModifier::Alt => modifiers.secondary(),
7687 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7688 }
7689 }
7690 }
7691
7692 fn columnar_selection_mode(
7693 modifiers: &Modifiers,
7694 cx: &mut Context<Self>,
7695 ) -> Option<ColumnarMode> {
7696 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7697 if Self::multi_cursor_modifier(false, modifiers, cx) {
7698 Some(ColumnarMode::FromMouse)
7699 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7700 Some(ColumnarMode::FromSelection)
7701 } else {
7702 None
7703 }
7704 } else {
7705 None
7706 }
7707 }
7708
7709 fn update_selection_mode(
7710 &mut self,
7711 modifiers: &Modifiers,
7712 position_map: &PositionMap,
7713 window: &mut Window,
7714 cx: &mut Context<Self>,
7715 ) {
7716 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7717 return;
7718 };
7719 if self.selections.pending_anchor().is_none() {
7720 return;
7721 }
7722
7723 let mouse_position = window.mouse_position();
7724 let point_for_position = position_map.point_for_position(mouse_position);
7725 let position = point_for_position.previous_valid;
7726
7727 self.select(
7728 SelectPhase::BeginColumnar {
7729 position,
7730 reset: false,
7731 mode,
7732 goal_column: point_for_position.exact_unclipped.column(),
7733 },
7734 window,
7735 cx,
7736 );
7737 }
7738
7739 fn update_edit_prediction_preview(
7740 &mut self,
7741 modifiers: &Modifiers,
7742 window: &mut Window,
7743 cx: &mut Context<Self>,
7744 ) {
7745 let mut modifiers_held = false;
7746 if let Some(accept_keystroke) = self
7747 .accept_edit_prediction_keybind(false, window, cx)
7748 .keystroke()
7749 {
7750 modifiers_held = modifiers_held
7751 || (accept_keystroke.modifiers() == modifiers
7752 && accept_keystroke.modifiers().modified());
7753 };
7754 if let Some(accept_partial_keystroke) = self
7755 .accept_edit_prediction_keybind(true, window, cx)
7756 .keystroke()
7757 {
7758 modifiers_held = modifiers_held
7759 || (accept_partial_keystroke.modifiers() == modifiers
7760 && accept_partial_keystroke.modifiers().modified());
7761 }
7762
7763 if modifiers_held {
7764 if matches!(
7765 self.edit_prediction_preview,
7766 EditPredictionPreview::Inactive { .. }
7767 ) {
7768 self.edit_prediction_preview = EditPredictionPreview::Active {
7769 previous_scroll_position: None,
7770 since: Instant::now(),
7771 };
7772
7773 self.update_visible_edit_prediction(window, cx);
7774 cx.notify();
7775 }
7776 } else if let EditPredictionPreview::Active {
7777 previous_scroll_position,
7778 since,
7779 } = self.edit_prediction_preview
7780 {
7781 if let (Some(previous_scroll_position), Some(position_map)) =
7782 (previous_scroll_position, self.last_position_map.as_ref())
7783 {
7784 self.set_scroll_position(
7785 previous_scroll_position
7786 .scroll_position(&position_map.snapshot.display_snapshot),
7787 window,
7788 cx,
7789 );
7790 }
7791
7792 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7793 released_too_fast: since.elapsed() < Duration::from_millis(200),
7794 };
7795 self.clear_row_highlights::<EditPredictionPreview>();
7796 self.update_visible_edit_prediction(window, cx);
7797 cx.notify();
7798 }
7799 }
7800
7801 fn update_visible_edit_prediction(
7802 &mut self,
7803 _window: &mut Window,
7804 cx: &mut Context<Self>,
7805 ) -> Option<()> {
7806 if DisableAiSettings::get_global(cx).disable_ai {
7807 return None;
7808 }
7809
7810 if self.ime_transaction.is_some() {
7811 self.discard_edit_prediction(false, cx);
7812 return None;
7813 }
7814
7815 let selection = self.selections.newest_anchor();
7816 let cursor = selection.head();
7817 let multibuffer = self.buffer.read(cx).snapshot(cx);
7818 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7819 let excerpt_id = cursor.excerpt_id;
7820
7821 let show_in_menu = self.show_edit_predictions_in_menu();
7822 let completions_menu_has_precedence = !show_in_menu
7823 && (self.context_menu.borrow().is_some()
7824 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7825
7826 if completions_menu_has_precedence
7827 || !offset_selection.is_empty()
7828 || self
7829 .active_edit_prediction
7830 .as_ref()
7831 .is_some_and(|completion| {
7832 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7833 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7834 !invalidation_range.contains(&offset_selection.head())
7835 })
7836 {
7837 self.discard_edit_prediction(false, cx);
7838 return None;
7839 }
7840
7841 self.take_active_edit_prediction(cx);
7842 let Some(provider) = self.edit_prediction_provider() else {
7843 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7844 return None;
7845 };
7846
7847 let (buffer, cursor_buffer_position) =
7848 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7849
7850 self.edit_prediction_settings =
7851 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7852
7853 if let EditPredictionSettings::Disabled = self.edit_prediction_settings {
7854 self.discard_edit_prediction(false, cx);
7855 return None;
7856 };
7857
7858 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7859
7860 if self.edit_prediction_indent_conflict {
7861 let cursor_point = cursor.to_point(&multibuffer);
7862
7863 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7864
7865 if let Some((_, indent)) = indents.iter().next()
7866 && indent.len == cursor_point.column
7867 {
7868 self.edit_prediction_indent_conflict = false;
7869 }
7870 }
7871
7872 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7873 let edits = edit_prediction
7874 .edits
7875 .into_iter()
7876 .flat_map(|(range, new_text)| {
7877 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7878 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7879 Some((start..end, new_text))
7880 })
7881 .collect::<Vec<_>>();
7882 if edits.is_empty() {
7883 return None;
7884 }
7885
7886 let first_edit_start = edits.first().unwrap().0.start;
7887 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7888 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7889
7890 let last_edit_end = edits.last().unwrap().0.end;
7891 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7892 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7893
7894 let cursor_row = cursor.to_point(&multibuffer).row;
7895
7896 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7897
7898 let mut inlay_ids = Vec::new();
7899 let invalidation_row_range;
7900 let move_invalidation_row_range = if cursor_row < edit_start_row {
7901 Some(cursor_row..edit_end_row)
7902 } else if cursor_row > edit_end_row {
7903 Some(edit_start_row..cursor_row)
7904 } else {
7905 None
7906 };
7907 let supports_jump = self
7908 .edit_prediction_provider
7909 .as_ref()
7910 .map(|provider| provider.provider.supports_jump_to_edit())
7911 .unwrap_or(true);
7912
7913 let is_move = supports_jump
7914 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7915 let completion = if is_move {
7916 invalidation_row_range =
7917 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7918 let target = first_edit_start;
7919 EditPrediction::Move { target, snapshot }
7920 } else {
7921 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7922 && !self.edit_predictions_hidden_for_vim_mode;
7923
7924 if show_completions_in_buffer {
7925 if edits
7926 .iter()
7927 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7928 {
7929 let mut inlays = Vec::new();
7930 for (range, new_text) in &edits {
7931 let inlay = Inlay::edit_prediction(
7932 post_inc(&mut self.next_inlay_id),
7933 range.start,
7934 new_text.as_str(),
7935 );
7936 inlay_ids.push(inlay.id);
7937 inlays.push(inlay);
7938 }
7939
7940 self.splice_inlays(&[], inlays, cx);
7941 } else {
7942 let background_color = cx.theme().status().deleted_background;
7943 self.highlight_text::<EditPredictionHighlight>(
7944 edits.iter().map(|(range, _)| range.clone()).collect(),
7945 HighlightStyle {
7946 background_color: Some(background_color),
7947 ..Default::default()
7948 },
7949 cx,
7950 );
7951 }
7952 }
7953
7954 invalidation_row_range = edit_start_row..edit_end_row;
7955
7956 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7957 if provider.show_tab_accept_marker() {
7958 EditDisplayMode::TabAccept
7959 } else {
7960 EditDisplayMode::Inline
7961 }
7962 } else {
7963 EditDisplayMode::DiffPopover
7964 };
7965
7966 EditPrediction::Edit {
7967 edits,
7968 edit_preview: edit_prediction.edit_preview,
7969 display_mode,
7970 snapshot,
7971 }
7972 };
7973
7974 let invalidation_range = multibuffer
7975 .anchor_before(Point::new(invalidation_row_range.start, 0))
7976 ..multibuffer.anchor_after(Point::new(
7977 invalidation_row_range.end,
7978 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7979 ));
7980
7981 self.stale_edit_prediction_in_menu = None;
7982 self.active_edit_prediction = Some(EditPredictionState {
7983 inlay_ids,
7984 completion,
7985 completion_id: edit_prediction.id,
7986 invalidation_range,
7987 });
7988
7989 cx.notify();
7990
7991 Some(())
7992 }
7993
7994 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7995 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7996 }
7997
7998 fn clear_tasks(&mut self) {
7999 self.tasks.clear()
8000 }
8001
8002 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8003 if self.tasks.insert(key, value).is_some() {
8004 // This case should hopefully be rare, but just in case...
8005 log::error!(
8006 "multiple different run targets found on a single line, only the last target will be rendered"
8007 )
8008 }
8009 }
8010
8011 /// Get all display points of breakpoints that will be rendered within editor
8012 ///
8013 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8014 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8015 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8016 fn active_breakpoints(
8017 &self,
8018 range: Range<DisplayRow>,
8019 window: &mut Window,
8020 cx: &mut Context<Self>,
8021 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8022 let mut breakpoint_display_points = HashMap::default();
8023
8024 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8025 return breakpoint_display_points;
8026 };
8027
8028 let snapshot = self.snapshot(window, cx);
8029
8030 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
8031 let Some(project) = self.project() else {
8032 return breakpoint_display_points;
8033 };
8034
8035 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8036 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8037
8038 for (buffer_snapshot, range, excerpt_id) in
8039 multi_buffer_snapshot.range_to_buffer_ranges(range)
8040 {
8041 let Some(buffer) = project
8042 .read(cx)
8043 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8044 else {
8045 continue;
8046 };
8047 let breakpoints = breakpoint_store.read(cx).breakpoints(
8048 &buffer,
8049 Some(
8050 buffer_snapshot.anchor_before(range.start)
8051 ..buffer_snapshot.anchor_after(range.end),
8052 ),
8053 buffer_snapshot,
8054 cx,
8055 );
8056 for (breakpoint, state) in breakpoints {
8057 let multi_buffer_anchor =
8058 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8059 let position = multi_buffer_anchor
8060 .to_point(multi_buffer_snapshot)
8061 .to_display_point(&snapshot);
8062
8063 breakpoint_display_points.insert(
8064 position.row(),
8065 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8066 );
8067 }
8068 }
8069
8070 breakpoint_display_points
8071 }
8072
8073 fn breakpoint_context_menu(
8074 &self,
8075 anchor: Anchor,
8076 window: &mut Window,
8077 cx: &mut Context<Self>,
8078 ) -> Entity<ui::ContextMenu> {
8079 let weak_editor = cx.weak_entity();
8080 let focus_handle = self.focus_handle(cx);
8081
8082 let row = self
8083 .buffer
8084 .read(cx)
8085 .snapshot(cx)
8086 .summary_for_anchor::<Point>(&anchor)
8087 .row;
8088
8089 let breakpoint = self
8090 .breakpoint_at_row(row, window, cx)
8091 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8092
8093 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8094 "Edit Log Breakpoint"
8095 } else {
8096 "Set Log Breakpoint"
8097 };
8098
8099 let condition_breakpoint_msg = if breakpoint
8100 .as_ref()
8101 .is_some_and(|bp| bp.1.condition.is_some())
8102 {
8103 "Edit Condition Breakpoint"
8104 } else {
8105 "Set Condition Breakpoint"
8106 };
8107
8108 let hit_condition_breakpoint_msg = if breakpoint
8109 .as_ref()
8110 .is_some_and(|bp| bp.1.hit_condition.is_some())
8111 {
8112 "Edit Hit Condition Breakpoint"
8113 } else {
8114 "Set Hit Condition Breakpoint"
8115 };
8116
8117 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8118 "Unset Breakpoint"
8119 } else {
8120 "Set Breakpoint"
8121 };
8122
8123 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8124
8125 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8126 BreakpointState::Enabled => Some("Disable"),
8127 BreakpointState::Disabled => Some("Enable"),
8128 });
8129
8130 let (anchor, breakpoint) =
8131 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8132
8133 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8134 menu.on_blur_subscription(Subscription::new(|| {}))
8135 .context(focus_handle)
8136 .when(run_to_cursor, |this| {
8137 let weak_editor = weak_editor.clone();
8138 this.entry("Run to cursor", None, move |window, cx| {
8139 weak_editor
8140 .update(cx, |editor, cx| {
8141 editor.change_selections(
8142 SelectionEffects::no_scroll(),
8143 window,
8144 cx,
8145 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8146 );
8147 })
8148 .ok();
8149
8150 window.dispatch_action(Box::new(RunToCursor), cx);
8151 })
8152 .separator()
8153 })
8154 .when_some(toggle_state_msg, |this, msg| {
8155 this.entry(msg, None, {
8156 let weak_editor = weak_editor.clone();
8157 let breakpoint = breakpoint.clone();
8158 move |_window, cx| {
8159 weak_editor
8160 .update(cx, |this, cx| {
8161 this.edit_breakpoint_at_anchor(
8162 anchor,
8163 breakpoint.as_ref().clone(),
8164 BreakpointEditAction::InvertState,
8165 cx,
8166 );
8167 })
8168 .log_err();
8169 }
8170 })
8171 })
8172 .entry(set_breakpoint_msg, None, {
8173 let weak_editor = weak_editor.clone();
8174 let breakpoint = breakpoint.clone();
8175 move |_window, cx| {
8176 weak_editor
8177 .update(cx, |this, cx| {
8178 this.edit_breakpoint_at_anchor(
8179 anchor,
8180 breakpoint.as_ref().clone(),
8181 BreakpointEditAction::Toggle,
8182 cx,
8183 );
8184 })
8185 .log_err();
8186 }
8187 })
8188 .entry(log_breakpoint_msg, None, {
8189 let breakpoint = breakpoint.clone();
8190 let weak_editor = weak_editor.clone();
8191 move |window, cx| {
8192 weak_editor
8193 .update(cx, |this, cx| {
8194 this.add_edit_breakpoint_block(
8195 anchor,
8196 breakpoint.as_ref(),
8197 BreakpointPromptEditAction::Log,
8198 window,
8199 cx,
8200 );
8201 })
8202 .log_err();
8203 }
8204 })
8205 .entry(condition_breakpoint_msg, None, {
8206 let breakpoint = breakpoint.clone();
8207 let weak_editor = weak_editor.clone();
8208 move |window, cx| {
8209 weak_editor
8210 .update(cx, |this, cx| {
8211 this.add_edit_breakpoint_block(
8212 anchor,
8213 breakpoint.as_ref(),
8214 BreakpointPromptEditAction::Condition,
8215 window,
8216 cx,
8217 );
8218 })
8219 .log_err();
8220 }
8221 })
8222 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8223 weak_editor
8224 .update(cx, |this, cx| {
8225 this.add_edit_breakpoint_block(
8226 anchor,
8227 breakpoint.as_ref(),
8228 BreakpointPromptEditAction::HitCondition,
8229 window,
8230 cx,
8231 );
8232 })
8233 .log_err();
8234 })
8235 })
8236 }
8237
8238 fn render_breakpoint(
8239 &self,
8240 position: Anchor,
8241 row: DisplayRow,
8242 breakpoint: &Breakpoint,
8243 state: Option<BreakpointSessionState>,
8244 cx: &mut Context<Self>,
8245 ) -> IconButton {
8246 let is_rejected = state.is_some_and(|s| !s.verified);
8247 // Is it a breakpoint that shows up when hovering over gutter?
8248 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8249 (false, false),
8250 |PhantomBreakpointIndicator {
8251 is_active,
8252 display_row,
8253 collides_with_existing_breakpoint,
8254 }| {
8255 (
8256 is_active && display_row == row,
8257 collides_with_existing_breakpoint,
8258 )
8259 },
8260 );
8261
8262 let (color, icon) = {
8263 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8264 (false, false) => ui::IconName::DebugBreakpoint,
8265 (true, false) => ui::IconName::DebugLogBreakpoint,
8266 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8267 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8268 };
8269
8270 let color = if is_phantom {
8271 Color::Hint
8272 } else if is_rejected {
8273 Color::Disabled
8274 } else {
8275 Color::Debugger
8276 };
8277
8278 (color, icon)
8279 };
8280
8281 let breakpoint = Arc::from(breakpoint.clone());
8282
8283 let alt_as_text = gpui::Keystroke {
8284 modifiers: Modifiers::secondary_key(),
8285 ..Default::default()
8286 };
8287 let primary_action_text = if breakpoint.is_disabled() {
8288 "Enable breakpoint"
8289 } else if is_phantom && !collides_with_existing {
8290 "Set breakpoint"
8291 } else {
8292 "Unset breakpoint"
8293 };
8294 let focus_handle = self.focus_handle.clone();
8295
8296 let meta = if is_rejected {
8297 SharedString::from("No executable code is associated with this line.")
8298 } else if collides_with_existing && !breakpoint.is_disabled() {
8299 SharedString::from(format!(
8300 "{alt_as_text}-click to disable,\nright-click for more options."
8301 ))
8302 } else {
8303 SharedString::from("Right-click for more options.")
8304 };
8305 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8306 .icon_size(IconSize::XSmall)
8307 .size(ui::ButtonSize::None)
8308 .when(is_rejected, |this| {
8309 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8310 })
8311 .icon_color(color)
8312 .style(ButtonStyle::Transparent)
8313 .on_click(cx.listener({
8314 move |editor, event: &ClickEvent, window, cx| {
8315 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8316 BreakpointEditAction::InvertState
8317 } else {
8318 BreakpointEditAction::Toggle
8319 };
8320
8321 window.focus(&editor.focus_handle(cx));
8322 editor.edit_breakpoint_at_anchor(
8323 position,
8324 breakpoint.as_ref().clone(),
8325 edit_action,
8326 cx,
8327 );
8328 }
8329 }))
8330 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8331 editor.set_breakpoint_context_menu(
8332 row,
8333 Some(position),
8334 event.position(),
8335 window,
8336 cx,
8337 );
8338 }))
8339 .tooltip(move |window, cx| {
8340 Tooltip::with_meta_in(
8341 primary_action_text,
8342 Some(&ToggleBreakpoint),
8343 meta.clone(),
8344 &focus_handle,
8345 window,
8346 cx,
8347 )
8348 })
8349 }
8350
8351 fn build_tasks_context(
8352 project: &Entity<Project>,
8353 buffer: &Entity<Buffer>,
8354 buffer_row: u32,
8355 tasks: &Arc<RunnableTasks>,
8356 cx: &mut Context<Self>,
8357 ) -> Task<Option<task::TaskContext>> {
8358 let position = Point::new(buffer_row, tasks.column);
8359 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8360 let location = Location {
8361 buffer: buffer.clone(),
8362 range: range_start..range_start,
8363 };
8364 // Fill in the environmental variables from the tree-sitter captures
8365 let mut captured_task_variables = TaskVariables::default();
8366 for (capture_name, value) in tasks.extra_variables.clone() {
8367 captured_task_variables.insert(
8368 task::VariableName::Custom(capture_name.into()),
8369 value.clone(),
8370 );
8371 }
8372 project.update(cx, |project, cx| {
8373 project.task_store().update(cx, |task_store, cx| {
8374 task_store.task_context_for_location(captured_task_variables, location, cx)
8375 })
8376 })
8377 }
8378
8379 pub fn spawn_nearest_task(
8380 &mut self,
8381 action: &SpawnNearestTask,
8382 window: &mut Window,
8383 cx: &mut Context<Self>,
8384 ) {
8385 let Some((workspace, _)) = self.workspace.clone() else {
8386 return;
8387 };
8388 let Some(project) = self.project.clone() else {
8389 return;
8390 };
8391
8392 // Try to find a closest, enclosing node using tree-sitter that has a task
8393 let Some((buffer, buffer_row, tasks)) = self
8394 .find_enclosing_node_task(cx)
8395 // Or find the task that's closest in row-distance.
8396 .or_else(|| self.find_closest_task(cx))
8397 else {
8398 return;
8399 };
8400
8401 let reveal_strategy = action.reveal;
8402 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8403 cx.spawn_in(window, async move |_, cx| {
8404 let context = task_context.await?;
8405 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8406
8407 let resolved = &mut resolved_task.resolved;
8408 resolved.reveal = reveal_strategy;
8409
8410 workspace
8411 .update_in(cx, |workspace, window, cx| {
8412 workspace.schedule_resolved_task(
8413 task_source_kind,
8414 resolved_task,
8415 false,
8416 window,
8417 cx,
8418 );
8419 })
8420 .ok()
8421 })
8422 .detach();
8423 }
8424
8425 fn find_closest_task(
8426 &mut self,
8427 cx: &mut Context<Self>,
8428 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8429 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8430
8431 let ((buffer_id, row), tasks) = self
8432 .tasks
8433 .iter()
8434 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8435
8436 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8437 let tasks = Arc::new(tasks.to_owned());
8438 Some((buffer, *row, tasks))
8439 }
8440
8441 fn find_enclosing_node_task(
8442 &mut self,
8443 cx: &mut Context<Self>,
8444 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8445 let snapshot = self.buffer.read(cx).snapshot(cx);
8446 let offset = self.selections.newest::<usize>(cx).head();
8447 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8448 let buffer_id = excerpt.buffer().remote_id();
8449
8450 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8451 let mut cursor = layer.node().walk();
8452
8453 while cursor.goto_first_child_for_byte(offset).is_some() {
8454 if cursor.node().end_byte() == offset {
8455 cursor.goto_next_sibling();
8456 }
8457 }
8458
8459 // Ascend to the smallest ancestor that contains the range and has a task.
8460 loop {
8461 let node = cursor.node();
8462 let node_range = node.byte_range();
8463 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8464
8465 // Check if this node contains our offset
8466 if node_range.start <= offset && node_range.end >= offset {
8467 // If it contains offset, check for task
8468 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8469 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8470 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8471 }
8472 }
8473
8474 if !cursor.goto_parent() {
8475 break;
8476 }
8477 }
8478 None
8479 }
8480
8481 fn render_run_indicator(
8482 &self,
8483 _style: &EditorStyle,
8484 is_active: bool,
8485 row: DisplayRow,
8486 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8487 cx: &mut Context<Self>,
8488 ) -> IconButton {
8489 let color = Color::Muted;
8490 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8491
8492 IconButton::new(
8493 ("run_indicator", row.0 as usize),
8494 ui::IconName::PlayOutlined,
8495 )
8496 .shape(ui::IconButtonShape::Square)
8497 .icon_size(IconSize::XSmall)
8498 .icon_color(color)
8499 .toggle_state(is_active)
8500 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8501 let quick_launch = match e {
8502 ClickEvent::Keyboard(_) => true,
8503 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8504 };
8505
8506 window.focus(&editor.focus_handle(cx));
8507 editor.toggle_code_actions(
8508 &ToggleCodeActions {
8509 deployed_from: Some(CodeActionSource::RunMenu(row)),
8510 quick_launch,
8511 },
8512 window,
8513 cx,
8514 );
8515 }))
8516 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8517 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8518 }))
8519 }
8520
8521 pub fn context_menu_visible(&self) -> bool {
8522 !self.edit_prediction_preview_is_active()
8523 && self
8524 .context_menu
8525 .borrow()
8526 .as_ref()
8527 .is_some_and(|menu| menu.visible())
8528 }
8529
8530 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8531 self.context_menu
8532 .borrow()
8533 .as_ref()
8534 .map(|menu| menu.origin())
8535 }
8536
8537 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8538 self.context_menu_options = Some(options);
8539 }
8540
8541 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8542 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8543
8544 fn render_edit_prediction_popover(
8545 &mut self,
8546 text_bounds: &Bounds<Pixels>,
8547 content_origin: gpui::Point<Pixels>,
8548 right_margin: Pixels,
8549 editor_snapshot: &EditorSnapshot,
8550 visible_row_range: Range<DisplayRow>,
8551 scroll_top: f32,
8552 scroll_bottom: f32,
8553 line_layouts: &[LineWithInvisibles],
8554 line_height: Pixels,
8555 scroll_pixel_position: gpui::Point<Pixels>,
8556 newest_selection_head: Option<DisplayPoint>,
8557 editor_width: Pixels,
8558 style: &EditorStyle,
8559 window: &mut Window,
8560 cx: &mut App,
8561 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8562 if self.mode().is_minimap() {
8563 return None;
8564 }
8565 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8566
8567 if self.edit_prediction_visible_in_cursor_popover(true) {
8568 return None;
8569 }
8570
8571 match &active_edit_prediction.completion {
8572 EditPrediction::Move { target, .. } => {
8573 let target_display_point = target.to_display_point(editor_snapshot);
8574
8575 if self.edit_prediction_requires_modifier() {
8576 if !self.edit_prediction_preview_is_active() {
8577 return None;
8578 }
8579
8580 self.render_edit_prediction_modifier_jump_popover(
8581 text_bounds,
8582 content_origin,
8583 visible_row_range,
8584 line_layouts,
8585 line_height,
8586 scroll_pixel_position,
8587 newest_selection_head,
8588 target_display_point,
8589 window,
8590 cx,
8591 )
8592 } else {
8593 self.render_edit_prediction_eager_jump_popover(
8594 text_bounds,
8595 content_origin,
8596 editor_snapshot,
8597 visible_row_range,
8598 scroll_top,
8599 scroll_bottom,
8600 line_height,
8601 scroll_pixel_position,
8602 target_display_point,
8603 editor_width,
8604 window,
8605 cx,
8606 )
8607 }
8608 }
8609 EditPrediction::Edit {
8610 display_mode: EditDisplayMode::Inline,
8611 ..
8612 } => None,
8613 EditPrediction::Edit {
8614 display_mode: EditDisplayMode::TabAccept,
8615 edits,
8616 ..
8617 } => {
8618 let range = &edits.first()?.0;
8619 let target_display_point = range.end.to_display_point(editor_snapshot);
8620
8621 self.render_edit_prediction_end_of_line_popover(
8622 "Accept",
8623 editor_snapshot,
8624 visible_row_range,
8625 target_display_point,
8626 line_height,
8627 scroll_pixel_position,
8628 content_origin,
8629 editor_width,
8630 window,
8631 cx,
8632 )
8633 }
8634 EditPrediction::Edit {
8635 edits,
8636 edit_preview,
8637 display_mode: EditDisplayMode::DiffPopover,
8638 snapshot,
8639 } => self.render_edit_prediction_diff_popover(
8640 text_bounds,
8641 content_origin,
8642 right_margin,
8643 editor_snapshot,
8644 visible_row_range,
8645 line_layouts,
8646 line_height,
8647 scroll_pixel_position,
8648 newest_selection_head,
8649 editor_width,
8650 style,
8651 edits,
8652 edit_preview,
8653 snapshot,
8654 window,
8655 cx,
8656 ),
8657 }
8658 }
8659
8660 fn render_edit_prediction_modifier_jump_popover(
8661 &mut self,
8662 text_bounds: &Bounds<Pixels>,
8663 content_origin: gpui::Point<Pixels>,
8664 visible_row_range: Range<DisplayRow>,
8665 line_layouts: &[LineWithInvisibles],
8666 line_height: Pixels,
8667 scroll_pixel_position: gpui::Point<Pixels>,
8668 newest_selection_head: Option<DisplayPoint>,
8669 target_display_point: DisplayPoint,
8670 window: &mut Window,
8671 cx: &mut App,
8672 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8673 let scrolled_content_origin =
8674 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8675
8676 const SCROLL_PADDING_Y: Pixels = px(12.);
8677
8678 if target_display_point.row() < visible_row_range.start {
8679 return self.render_edit_prediction_scroll_popover(
8680 |_| SCROLL_PADDING_Y,
8681 IconName::ArrowUp,
8682 visible_row_range,
8683 line_layouts,
8684 newest_selection_head,
8685 scrolled_content_origin,
8686 window,
8687 cx,
8688 );
8689 } else if target_display_point.row() >= visible_row_range.end {
8690 return self.render_edit_prediction_scroll_popover(
8691 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8692 IconName::ArrowDown,
8693 visible_row_range,
8694 line_layouts,
8695 newest_selection_head,
8696 scrolled_content_origin,
8697 window,
8698 cx,
8699 );
8700 }
8701
8702 const POLE_WIDTH: Pixels = px(2.);
8703
8704 let line_layout =
8705 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8706 let target_column = target_display_point.column() as usize;
8707
8708 let target_x = line_layout.x_for_index(target_column);
8709 let target_y =
8710 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8711
8712 let flag_on_right = target_x < text_bounds.size.width / 2.;
8713
8714 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8715 border_color.l += 0.001;
8716
8717 let mut element = v_flex()
8718 .items_end()
8719 .when(flag_on_right, |el| el.items_start())
8720 .child(if flag_on_right {
8721 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8722 .rounded_bl(px(0.))
8723 .rounded_tl(px(0.))
8724 .border_l_2()
8725 .border_color(border_color)
8726 } else {
8727 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8728 .rounded_br(px(0.))
8729 .rounded_tr(px(0.))
8730 .border_r_2()
8731 .border_color(border_color)
8732 })
8733 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8734 .into_any();
8735
8736 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8737
8738 let mut origin = scrolled_content_origin + point(target_x, target_y)
8739 - point(
8740 if flag_on_right {
8741 POLE_WIDTH
8742 } else {
8743 size.width - POLE_WIDTH
8744 },
8745 size.height - line_height,
8746 );
8747
8748 origin.x = origin.x.max(content_origin.x);
8749
8750 element.prepaint_at(origin, window, cx);
8751
8752 Some((element, origin))
8753 }
8754
8755 fn render_edit_prediction_scroll_popover(
8756 &mut self,
8757 to_y: impl Fn(Size<Pixels>) -> Pixels,
8758 scroll_icon: IconName,
8759 visible_row_range: Range<DisplayRow>,
8760 line_layouts: &[LineWithInvisibles],
8761 newest_selection_head: Option<DisplayPoint>,
8762 scrolled_content_origin: gpui::Point<Pixels>,
8763 window: &mut Window,
8764 cx: &mut App,
8765 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8766 let mut element = self
8767 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8768 .into_any();
8769
8770 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8771
8772 let cursor = newest_selection_head?;
8773 let cursor_row_layout =
8774 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8775 let cursor_column = cursor.column() as usize;
8776
8777 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8778
8779 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8780
8781 element.prepaint_at(origin, window, cx);
8782 Some((element, origin))
8783 }
8784
8785 fn render_edit_prediction_eager_jump_popover(
8786 &mut self,
8787 text_bounds: &Bounds<Pixels>,
8788 content_origin: gpui::Point<Pixels>,
8789 editor_snapshot: &EditorSnapshot,
8790 visible_row_range: Range<DisplayRow>,
8791 scroll_top: f32,
8792 scroll_bottom: f32,
8793 line_height: Pixels,
8794 scroll_pixel_position: gpui::Point<Pixels>,
8795 target_display_point: DisplayPoint,
8796 editor_width: Pixels,
8797 window: &mut Window,
8798 cx: &mut App,
8799 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8800 if target_display_point.row().as_f32() < scroll_top {
8801 let mut element = self
8802 .render_edit_prediction_line_popover(
8803 "Jump to Edit",
8804 Some(IconName::ArrowUp),
8805 window,
8806 cx,
8807 )?
8808 .into_any();
8809
8810 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8811 let offset = point(
8812 (text_bounds.size.width - size.width) / 2.,
8813 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8814 );
8815
8816 let origin = text_bounds.origin + offset;
8817 element.prepaint_at(origin, window, cx);
8818 Some((element, origin))
8819 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8820 let mut element = self
8821 .render_edit_prediction_line_popover(
8822 "Jump to Edit",
8823 Some(IconName::ArrowDown),
8824 window,
8825 cx,
8826 )?
8827 .into_any();
8828
8829 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8830 let offset = point(
8831 (text_bounds.size.width - size.width) / 2.,
8832 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8833 );
8834
8835 let origin = text_bounds.origin + offset;
8836 element.prepaint_at(origin, window, cx);
8837 Some((element, origin))
8838 } else {
8839 self.render_edit_prediction_end_of_line_popover(
8840 "Jump to Edit",
8841 editor_snapshot,
8842 visible_row_range,
8843 target_display_point,
8844 line_height,
8845 scroll_pixel_position,
8846 content_origin,
8847 editor_width,
8848 window,
8849 cx,
8850 )
8851 }
8852 }
8853
8854 fn render_edit_prediction_end_of_line_popover(
8855 self: &mut Editor,
8856 label: &'static str,
8857 editor_snapshot: &EditorSnapshot,
8858 visible_row_range: Range<DisplayRow>,
8859 target_display_point: DisplayPoint,
8860 line_height: Pixels,
8861 scroll_pixel_position: gpui::Point<Pixels>,
8862 content_origin: gpui::Point<Pixels>,
8863 editor_width: Pixels,
8864 window: &mut Window,
8865 cx: &mut App,
8866 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8867 let target_line_end = DisplayPoint::new(
8868 target_display_point.row(),
8869 editor_snapshot.line_len(target_display_point.row()),
8870 );
8871
8872 let mut element = self
8873 .render_edit_prediction_line_popover(label, None, window, cx)?
8874 .into_any();
8875
8876 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8877
8878 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8879
8880 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8881 let mut origin = start_point
8882 + line_origin
8883 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8884 origin.x = origin.x.max(content_origin.x);
8885
8886 let max_x = content_origin.x + editor_width - size.width;
8887
8888 if origin.x > max_x {
8889 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8890
8891 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8892 origin.y += offset;
8893 IconName::ArrowUp
8894 } else {
8895 origin.y -= offset;
8896 IconName::ArrowDown
8897 };
8898
8899 element = self
8900 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8901 .into_any();
8902
8903 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8904
8905 origin.x = content_origin.x + editor_width - size.width - px(2.);
8906 }
8907
8908 element.prepaint_at(origin, window, cx);
8909 Some((element, origin))
8910 }
8911
8912 fn render_edit_prediction_diff_popover(
8913 self: &Editor,
8914 text_bounds: &Bounds<Pixels>,
8915 content_origin: gpui::Point<Pixels>,
8916 right_margin: Pixels,
8917 editor_snapshot: &EditorSnapshot,
8918 visible_row_range: Range<DisplayRow>,
8919 line_layouts: &[LineWithInvisibles],
8920 line_height: Pixels,
8921 scroll_pixel_position: gpui::Point<Pixels>,
8922 newest_selection_head: Option<DisplayPoint>,
8923 editor_width: Pixels,
8924 style: &EditorStyle,
8925 edits: &Vec<(Range<Anchor>, String)>,
8926 edit_preview: &Option<language::EditPreview>,
8927 snapshot: &language::BufferSnapshot,
8928 window: &mut Window,
8929 cx: &mut App,
8930 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8931 let edit_start = edits
8932 .first()
8933 .unwrap()
8934 .0
8935 .start
8936 .to_display_point(editor_snapshot);
8937 let edit_end = edits
8938 .last()
8939 .unwrap()
8940 .0
8941 .end
8942 .to_display_point(editor_snapshot);
8943
8944 let is_visible = visible_row_range.contains(&edit_start.row())
8945 || visible_row_range.contains(&edit_end.row());
8946 if !is_visible {
8947 return None;
8948 }
8949
8950 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8951 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8952 } else {
8953 // Fallback for providers without edit_preview
8954 crate::edit_prediction_fallback_text(edits, cx)
8955 };
8956
8957 let styled_text = highlighted_edits.to_styled_text(&style.text);
8958 let line_count = highlighted_edits.text.lines().count();
8959
8960 const BORDER_WIDTH: Pixels = px(1.);
8961
8962 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8963 let has_keybind = keybind.is_some();
8964
8965 let mut element = h_flex()
8966 .items_start()
8967 .child(
8968 h_flex()
8969 .bg(cx.theme().colors().editor_background)
8970 .border(BORDER_WIDTH)
8971 .shadow_xs()
8972 .border_color(cx.theme().colors().border)
8973 .rounded_l_lg()
8974 .when(line_count > 1, |el| el.rounded_br_lg())
8975 .pr_1()
8976 .child(styled_text),
8977 )
8978 .child(
8979 h_flex()
8980 .h(line_height + BORDER_WIDTH * 2.)
8981 .px_1p5()
8982 .gap_1()
8983 // Workaround: For some reason, there's a gap if we don't do this
8984 .ml(-BORDER_WIDTH)
8985 .shadow(vec![gpui::BoxShadow {
8986 color: gpui::black().opacity(0.05),
8987 offset: point(px(1.), px(1.)),
8988 blur_radius: px(2.),
8989 spread_radius: px(0.),
8990 }])
8991 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8992 .border(BORDER_WIDTH)
8993 .border_color(cx.theme().colors().border)
8994 .rounded_r_lg()
8995 .id("edit_prediction_diff_popover_keybind")
8996 .when(!has_keybind, |el| {
8997 let status_colors = cx.theme().status();
8998
8999 el.bg(status_colors.error_background)
9000 .border_color(status_colors.error.opacity(0.6))
9001 .child(Icon::new(IconName::Info).color(Color::Error))
9002 .cursor_default()
9003 .hoverable_tooltip(move |_window, cx| {
9004 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9005 })
9006 })
9007 .children(keybind),
9008 )
9009 .into_any();
9010
9011 let longest_row =
9012 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9013 let longest_line_width = if visible_row_range.contains(&longest_row) {
9014 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9015 } else {
9016 layout_line(
9017 longest_row,
9018 editor_snapshot,
9019 style,
9020 editor_width,
9021 |_| false,
9022 window,
9023 cx,
9024 )
9025 .width
9026 };
9027
9028 let viewport_bounds =
9029 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9030 right: -right_margin,
9031 ..Default::default()
9032 });
9033
9034 let x_after_longest =
9035 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
9036 - scroll_pixel_position.x;
9037
9038 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9039
9040 // Fully visible if it can be displayed within the window (allow overlapping other
9041 // panes). However, this is only allowed if the popover starts within text_bounds.
9042 let can_position_to_the_right = x_after_longest < text_bounds.right()
9043 && x_after_longest + element_bounds.width < viewport_bounds.right();
9044
9045 let mut origin = if can_position_to_the_right {
9046 point(
9047 x_after_longest,
9048 text_bounds.origin.y + edit_start.row().as_f32() * line_height
9049 - scroll_pixel_position.y,
9050 )
9051 } else {
9052 let cursor_row = newest_selection_head.map(|head| head.row());
9053 let above_edit = edit_start
9054 .row()
9055 .0
9056 .checked_sub(line_count as u32)
9057 .map(DisplayRow);
9058 let below_edit = Some(edit_end.row() + 1);
9059 let above_cursor =
9060 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9061 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9062
9063 // Place the edit popover adjacent to the edit if there is a location
9064 // available that is onscreen and does not obscure the cursor. Otherwise,
9065 // place it adjacent to the cursor.
9066 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9067 .into_iter()
9068 .flatten()
9069 .find(|&start_row| {
9070 let end_row = start_row + line_count as u32;
9071 visible_row_range.contains(&start_row)
9072 && visible_row_range.contains(&end_row)
9073 && cursor_row
9074 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9075 })?;
9076
9077 content_origin
9078 + point(
9079 -scroll_pixel_position.x,
9080 row_target.as_f32() * line_height - scroll_pixel_position.y,
9081 )
9082 };
9083
9084 origin.x -= BORDER_WIDTH;
9085
9086 window.defer_draw(element, origin, 1);
9087
9088 // Do not return an element, since it will already be drawn due to defer_draw.
9089 None
9090 }
9091
9092 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9093 px(30.)
9094 }
9095
9096 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9097 if self.read_only(cx) {
9098 cx.theme().players().read_only()
9099 } else {
9100 self.style.as_ref().unwrap().local_player
9101 }
9102 }
9103
9104 fn render_edit_prediction_accept_keybind(
9105 &self,
9106 window: &mut Window,
9107 cx: &App,
9108 ) -> Option<AnyElement> {
9109 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9110 let accept_keystroke = accept_binding.keystroke()?;
9111
9112 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9113
9114 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9115 Color::Accent
9116 } else {
9117 Color::Muted
9118 };
9119
9120 h_flex()
9121 .px_0p5()
9122 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9123 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9124 .text_size(TextSize::XSmall.rems(cx))
9125 .child(h_flex().children(ui::render_modifiers(
9126 accept_keystroke.modifiers(),
9127 PlatformStyle::platform(),
9128 Some(modifiers_color),
9129 Some(IconSize::XSmall.rems().into()),
9130 true,
9131 )))
9132 .when(is_platform_style_mac, |parent| {
9133 parent.child(accept_keystroke.key().to_string())
9134 })
9135 .when(!is_platform_style_mac, |parent| {
9136 parent.child(
9137 Key::new(
9138 util::capitalize(accept_keystroke.key()),
9139 Some(Color::Default),
9140 )
9141 .size(Some(IconSize::XSmall.rems().into())),
9142 )
9143 })
9144 .into_any()
9145 .into()
9146 }
9147
9148 fn render_edit_prediction_line_popover(
9149 &self,
9150 label: impl Into<SharedString>,
9151 icon: Option<IconName>,
9152 window: &mut Window,
9153 cx: &App,
9154 ) -> Option<Stateful<Div>> {
9155 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9156
9157 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9158 let has_keybind = keybind.is_some();
9159
9160 let result = h_flex()
9161 .id("ep-line-popover")
9162 .py_0p5()
9163 .pl_1()
9164 .pr(padding_right)
9165 .gap_1()
9166 .rounded_md()
9167 .border_1()
9168 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9169 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9170 .shadow_xs()
9171 .when(!has_keybind, |el| {
9172 let status_colors = cx.theme().status();
9173
9174 el.bg(status_colors.error_background)
9175 .border_color(status_colors.error.opacity(0.6))
9176 .pl_2()
9177 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9178 .cursor_default()
9179 .hoverable_tooltip(move |_window, cx| {
9180 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9181 })
9182 })
9183 .children(keybind)
9184 .child(
9185 Label::new(label)
9186 .size(LabelSize::Small)
9187 .when(!has_keybind, |el| {
9188 el.color(cx.theme().status().error.into()).strikethrough()
9189 }),
9190 )
9191 .when(!has_keybind, |el| {
9192 el.child(
9193 h_flex().ml_1().child(
9194 Icon::new(IconName::Info)
9195 .size(IconSize::Small)
9196 .color(cx.theme().status().error.into()),
9197 ),
9198 )
9199 })
9200 .when_some(icon, |element, icon| {
9201 element.child(
9202 div()
9203 .mt(px(1.5))
9204 .child(Icon::new(icon).size(IconSize::Small)),
9205 )
9206 });
9207
9208 Some(result)
9209 }
9210
9211 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9212 let accent_color = cx.theme().colors().text_accent;
9213 let editor_bg_color = cx.theme().colors().editor_background;
9214 editor_bg_color.blend(accent_color.opacity(0.1))
9215 }
9216
9217 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9218 let accent_color = cx.theme().colors().text_accent;
9219 let editor_bg_color = cx.theme().colors().editor_background;
9220 editor_bg_color.blend(accent_color.opacity(0.6))
9221 }
9222 fn get_prediction_provider_icon_name(
9223 provider: &Option<RegisteredEditPredictionProvider>,
9224 ) -> IconName {
9225 match provider {
9226 Some(provider) => match provider.provider.name() {
9227 "copilot" => IconName::Copilot,
9228 "supermaven" => IconName::Supermaven,
9229 _ => IconName::ZedPredict,
9230 },
9231 None => IconName::ZedPredict,
9232 }
9233 }
9234
9235 fn render_edit_prediction_cursor_popover(
9236 &self,
9237 min_width: Pixels,
9238 max_width: Pixels,
9239 cursor_point: Point,
9240 style: &EditorStyle,
9241 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9242 _window: &Window,
9243 cx: &mut Context<Editor>,
9244 ) -> Option<AnyElement> {
9245 let provider = self.edit_prediction_provider.as_ref()?;
9246 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9247
9248 let is_refreshing = provider.provider.is_refreshing(cx);
9249
9250 fn pending_completion_container(icon: IconName) -> Div {
9251 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9252 }
9253
9254 let completion = match &self.active_edit_prediction {
9255 Some(prediction) => {
9256 if !self.has_visible_completions_menu() {
9257 const RADIUS: Pixels = px(6.);
9258 const BORDER_WIDTH: Pixels = px(1.);
9259
9260 return Some(
9261 h_flex()
9262 .elevation_2(cx)
9263 .border(BORDER_WIDTH)
9264 .border_color(cx.theme().colors().border)
9265 .when(accept_keystroke.is_none(), |el| {
9266 el.border_color(cx.theme().status().error)
9267 })
9268 .rounded(RADIUS)
9269 .rounded_tl(px(0.))
9270 .overflow_hidden()
9271 .child(div().px_1p5().child(match &prediction.completion {
9272 EditPrediction::Move { target, snapshot } => {
9273 use text::ToPoint as _;
9274 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9275 {
9276 Icon::new(IconName::ZedPredictDown)
9277 } else {
9278 Icon::new(IconName::ZedPredictUp)
9279 }
9280 }
9281 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9282 }))
9283 .child(
9284 h_flex()
9285 .gap_1()
9286 .py_1()
9287 .px_2()
9288 .rounded_r(RADIUS - BORDER_WIDTH)
9289 .border_l_1()
9290 .border_color(cx.theme().colors().border)
9291 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9292 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9293 el.child(
9294 Label::new("Hold")
9295 .size(LabelSize::Small)
9296 .when(accept_keystroke.is_none(), |el| {
9297 el.strikethrough()
9298 })
9299 .line_height_style(LineHeightStyle::UiLabel),
9300 )
9301 })
9302 .id("edit_prediction_cursor_popover_keybind")
9303 .when(accept_keystroke.is_none(), |el| {
9304 let status_colors = cx.theme().status();
9305
9306 el.bg(status_colors.error_background)
9307 .border_color(status_colors.error.opacity(0.6))
9308 .child(Icon::new(IconName::Info).color(Color::Error))
9309 .cursor_default()
9310 .hoverable_tooltip(move |_window, cx| {
9311 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9312 .into()
9313 })
9314 })
9315 .when_some(
9316 accept_keystroke.as_ref(),
9317 |el, accept_keystroke| {
9318 el.child(h_flex().children(ui::render_modifiers(
9319 accept_keystroke.modifiers(),
9320 PlatformStyle::platform(),
9321 Some(Color::Default),
9322 Some(IconSize::XSmall.rems().into()),
9323 false,
9324 )))
9325 },
9326 ),
9327 )
9328 .into_any(),
9329 );
9330 }
9331
9332 self.render_edit_prediction_cursor_popover_preview(
9333 prediction,
9334 cursor_point,
9335 style,
9336 cx,
9337 )?
9338 }
9339
9340 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9341 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9342 stale_completion,
9343 cursor_point,
9344 style,
9345 cx,
9346 )?,
9347
9348 None => pending_completion_container(provider_icon)
9349 .child(Label::new("...").size(LabelSize::Small)),
9350 },
9351
9352 None => pending_completion_container(provider_icon)
9353 .child(Label::new("...").size(LabelSize::Small)),
9354 };
9355
9356 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9357 completion
9358 .with_animation(
9359 "loading-completion",
9360 Animation::new(Duration::from_secs(2))
9361 .repeat()
9362 .with_easing(pulsating_between(0.4, 0.8)),
9363 |label, delta| label.opacity(delta),
9364 )
9365 .into_any_element()
9366 } else {
9367 completion.into_any_element()
9368 };
9369
9370 let has_completion = self.active_edit_prediction.is_some();
9371
9372 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9373 Some(
9374 h_flex()
9375 .min_w(min_width)
9376 .max_w(max_width)
9377 .flex_1()
9378 .elevation_2(cx)
9379 .border_color(cx.theme().colors().border)
9380 .child(
9381 div()
9382 .flex_1()
9383 .py_1()
9384 .px_2()
9385 .overflow_hidden()
9386 .child(completion),
9387 )
9388 .when_some(accept_keystroke, |el, accept_keystroke| {
9389 if !accept_keystroke.modifiers().modified() {
9390 return el;
9391 }
9392
9393 el.child(
9394 h_flex()
9395 .h_full()
9396 .border_l_1()
9397 .rounded_r_lg()
9398 .border_color(cx.theme().colors().border)
9399 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9400 .gap_1()
9401 .py_1()
9402 .px_2()
9403 .child(
9404 h_flex()
9405 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9406 .when(is_platform_style_mac, |parent| parent.gap_1())
9407 .child(h_flex().children(ui::render_modifiers(
9408 accept_keystroke.modifiers(),
9409 PlatformStyle::platform(),
9410 Some(if !has_completion {
9411 Color::Muted
9412 } else {
9413 Color::Default
9414 }),
9415 None,
9416 false,
9417 ))),
9418 )
9419 .child(Label::new("Preview").into_any_element())
9420 .opacity(if has_completion { 1.0 } else { 0.4 }),
9421 )
9422 })
9423 .into_any(),
9424 )
9425 }
9426
9427 fn render_edit_prediction_cursor_popover_preview(
9428 &self,
9429 completion: &EditPredictionState,
9430 cursor_point: Point,
9431 style: &EditorStyle,
9432 cx: &mut Context<Editor>,
9433 ) -> Option<Div> {
9434 use text::ToPoint as _;
9435
9436 fn render_relative_row_jump(
9437 prefix: impl Into<String>,
9438 current_row: u32,
9439 target_row: u32,
9440 ) -> Div {
9441 let (row_diff, arrow) = if target_row < current_row {
9442 (current_row - target_row, IconName::ArrowUp)
9443 } else {
9444 (target_row - current_row, IconName::ArrowDown)
9445 };
9446
9447 h_flex()
9448 .child(
9449 Label::new(format!("{}{}", prefix.into(), row_diff))
9450 .color(Color::Muted)
9451 .size(LabelSize::Small),
9452 )
9453 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9454 }
9455
9456 let supports_jump = self
9457 .edit_prediction_provider
9458 .as_ref()
9459 .map(|provider| provider.provider.supports_jump_to_edit())
9460 .unwrap_or(true);
9461
9462 match &completion.completion {
9463 EditPrediction::Move {
9464 target, snapshot, ..
9465 } => {
9466 if !supports_jump {
9467 return None;
9468 }
9469
9470 Some(
9471 h_flex()
9472 .px_2()
9473 .gap_2()
9474 .flex_1()
9475 .child(
9476 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9477 Icon::new(IconName::ZedPredictDown)
9478 } else {
9479 Icon::new(IconName::ZedPredictUp)
9480 },
9481 )
9482 .child(Label::new("Jump to Edit")),
9483 )
9484 }
9485
9486 EditPrediction::Edit {
9487 edits,
9488 edit_preview,
9489 snapshot,
9490 display_mode: _,
9491 } => {
9492 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9493
9494 let (highlighted_edits, has_more_lines) =
9495 if let Some(edit_preview) = edit_preview.as_ref() {
9496 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9497 .first_line_preview()
9498 } else {
9499 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9500 };
9501
9502 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9503 .with_default_highlights(&style.text, highlighted_edits.highlights);
9504
9505 let preview = h_flex()
9506 .gap_1()
9507 .min_w_16()
9508 .child(styled_text)
9509 .when(has_more_lines, |parent| parent.child("…"));
9510
9511 let left = if supports_jump && first_edit_row != cursor_point.row {
9512 render_relative_row_jump("", cursor_point.row, first_edit_row)
9513 .into_any_element()
9514 } else {
9515 let icon_name =
9516 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9517 Icon::new(icon_name).into_any_element()
9518 };
9519
9520 Some(
9521 h_flex()
9522 .h_full()
9523 .flex_1()
9524 .gap_2()
9525 .pr_1()
9526 .overflow_x_hidden()
9527 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9528 .child(left)
9529 .child(preview),
9530 )
9531 }
9532 }
9533 }
9534
9535 pub fn render_context_menu(
9536 &self,
9537 style: &EditorStyle,
9538 max_height_in_lines: u32,
9539 window: &mut Window,
9540 cx: &mut Context<Editor>,
9541 ) -> Option<AnyElement> {
9542 let menu = self.context_menu.borrow();
9543 let menu = menu.as_ref()?;
9544 if !menu.visible() {
9545 return None;
9546 };
9547 Some(menu.render(style, max_height_in_lines, window, cx))
9548 }
9549
9550 fn render_context_menu_aside(
9551 &mut self,
9552 max_size: Size<Pixels>,
9553 window: &mut Window,
9554 cx: &mut Context<Editor>,
9555 ) -> Option<AnyElement> {
9556 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9557 if menu.visible() {
9558 menu.render_aside(max_size, window, cx)
9559 } else {
9560 None
9561 }
9562 })
9563 }
9564
9565 fn hide_context_menu(
9566 &mut self,
9567 window: &mut Window,
9568 cx: &mut Context<Self>,
9569 ) -> Option<CodeContextMenu> {
9570 cx.notify();
9571 self.completion_tasks.clear();
9572 let context_menu = self.context_menu.borrow_mut().take();
9573 self.stale_edit_prediction_in_menu.take();
9574 self.update_visible_edit_prediction(window, cx);
9575 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9576 && let Some(completion_provider) = &self.completion_provider
9577 {
9578 completion_provider.selection_changed(None, window, cx);
9579 }
9580 context_menu
9581 }
9582
9583 fn show_snippet_choices(
9584 &mut self,
9585 choices: &Vec<String>,
9586 selection: Range<Anchor>,
9587 cx: &mut Context<Self>,
9588 ) {
9589 let Some((_, buffer, _)) = self
9590 .buffer()
9591 .read(cx)
9592 .excerpt_containing(selection.start, cx)
9593 else {
9594 return;
9595 };
9596 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9597 else {
9598 return;
9599 };
9600 if buffer != end_buffer {
9601 log::error!("expected anchor range to have matching buffer IDs");
9602 return;
9603 }
9604
9605 let id = post_inc(&mut self.next_completion_id);
9606 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9607 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9608 CompletionsMenu::new_snippet_choices(
9609 id,
9610 true,
9611 choices,
9612 selection,
9613 buffer,
9614 snippet_sort_order,
9615 ),
9616 ));
9617 }
9618
9619 pub fn insert_snippet(
9620 &mut self,
9621 insertion_ranges: &[Range<usize>],
9622 snippet: Snippet,
9623 window: &mut Window,
9624 cx: &mut Context<Self>,
9625 ) -> Result<()> {
9626 struct Tabstop<T> {
9627 is_end_tabstop: bool,
9628 ranges: Vec<Range<T>>,
9629 choices: Option<Vec<String>>,
9630 }
9631
9632 let tabstops = self.buffer.update(cx, |buffer, cx| {
9633 let snippet_text: Arc<str> = snippet.text.clone().into();
9634 let edits = insertion_ranges
9635 .iter()
9636 .cloned()
9637 .map(|range| (range, snippet_text.clone()));
9638 let autoindent_mode = AutoindentMode::Block {
9639 original_indent_columns: Vec::new(),
9640 };
9641 buffer.edit(edits, Some(autoindent_mode), cx);
9642
9643 let snapshot = &*buffer.read(cx);
9644 let snippet = &snippet;
9645 snippet
9646 .tabstops
9647 .iter()
9648 .map(|tabstop| {
9649 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9650 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9651 });
9652 let mut tabstop_ranges = tabstop
9653 .ranges
9654 .iter()
9655 .flat_map(|tabstop_range| {
9656 let mut delta = 0_isize;
9657 insertion_ranges.iter().map(move |insertion_range| {
9658 let insertion_start = insertion_range.start as isize + delta;
9659 delta +=
9660 snippet.text.len() as isize - insertion_range.len() as isize;
9661
9662 let start = ((insertion_start + tabstop_range.start) as usize)
9663 .min(snapshot.len());
9664 let end = ((insertion_start + tabstop_range.end) as usize)
9665 .min(snapshot.len());
9666 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9667 })
9668 })
9669 .collect::<Vec<_>>();
9670 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9671
9672 Tabstop {
9673 is_end_tabstop,
9674 ranges: tabstop_ranges,
9675 choices: tabstop.choices.clone(),
9676 }
9677 })
9678 .collect::<Vec<_>>()
9679 });
9680 if let Some(tabstop) = tabstops.first() {
9681 self.change_selections(Default::default(), window, cx, |s| {
9682 // Reverse order so that the first range is the newest created selection.
9683 // Completions will use it and autoscroll will prioritize it.
9684 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9685 });
9686
9687 if let Some(choices) = &tabstop.choices
9688 && let Some(selection) = tabstop.ranges.first()
9689 {
9690 self.show_snippet_choices(choices, selection.clone(), cx)
9691 }
9692
9693 // If we're already at the last tabstop and it's at the end of the snippet,
9694 // we're done, we don't need to keep the state around.
9695 if !tabstop.is_end_tabstop {
9696 let choices = tabstops
9697 .iter()
9698 .map(|tabstop| tabstop.choices.clone())
9699 .collect();
9700
9701 let ranges = tabstops
9702 .into_iter()
9703 .map(|tabstop| tabstop.ranges)
9704 .collect::<Vec<_>>();
9705
9706 self.snippet_stack.push(SnippetState {
9707 active_index: 0,
9708 ranges,
9709 choices,
9710 });
9711 }
9712
9713 // Check whether the just-entered snippet ends with an auto-closable bracket.
9714 if self.autoclose_regions.is_empty() {
9715 let snapshot = self.buffer.read(cx).snapshot(cx);
9716 let mut all_selections = self.selections.all::<Point>(cx);
9717 for selection in &mut all_selections {
9718 let selection_head = selection.head();
9719 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9720 continue;
9721 };
9722
9723 let mut bracket_pair = None;
9724 let max_lookup_length = scope
9725 .brackets()
9726 .map(|(pair, _)| {
9727 pair.start
9728 .as_str()
9729 .chars()
9730 .count()
9731 .max(pair.end.as_str().chars().count())
9732 })
9733 .max();
9734 if let Some(max_lookup_length) = max_lookup_length {
9735 let next_text = snapshot
9736 .chars_at(selection_head)
9737 .take(max_lookup_length)
9738 .collect::<String>();
9739 let prev_text = snapshot
9740 .reversed_chars_at(selection_head)
9741 .take(max_lookup_length)
9742 .collect::<String>();
9743
9744 for (pair, enabled) in scope.brackets() {
9745 if enabled
9746 && pair.close
9747 && prev_text.starts_with(pair.start.as_str())
9748 && next_text.starts_with(pair.end.as_str())
9749 {
9750 bracket_pair = Some(pair.clone());
9751 break;
9752 }
9753 }
9754 }
9755
9756 if let Some(pair) = bracket_pair {
9757 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9758 let autoclose_enabled =
9759 self.use_autoclose && snapshot_settings.use_autoclose;
9760 if autoclose_enabled {
9761 let start = snapshot.anchor_after(selection_head);
9762 let end = snapshot.anchor_after(selection_head);
9763 self.autoclose_regions.push(AutocloseRegion {
9764 selection_id: selection.id,
9765 range: start..end,
9766 pair,
9767 });
9768 }
9769 }
9770 }
9771 }
9772 }
9773 Ok(())
9774 }
9775
9776 pub fn move_to_next_snippet_tabstop(
9777 &mut self,
9778 window: &mut Window,
9779 cx: &mut Context<Self>,
9780 ) -> bool {
9781 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9782 }
9783
9784 pub fn move_to_prev_snippet_tabstop(
9785 &mut self,
9786 window: &mut Window,
9787 cx: &mut Context<Self>,
9788 ) -> bool {
9789 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9790 }
9791
9792 pub fn move_to_snippet_tabstop(
9793 &mut self,
9794 bias: Bias,
9795 window: &mut Window,
9796 cx: &mut Context<Self>,
9797 ) -> bool {
9798 if let Some(mut snippet) = self.snippet_stack.pop() {
9799 match bias {
9800 Bias::Left => {
9801 if snippet.active_index > 0 {
9802 snippet.active_index -= 1;
9803 } else {
9804 self.snippet_stack.push(snippet);
9805 return false;
9806 }
9807 }
9808 Bias::Right => {
9809 if snippet.active_index + 1 < snippet.ranges.len() {
9810 snippet.active_index += 1;
9811 } else {
9812 self.snippet_stack.push(snippet);
9813 return false;
9814 }
9815 }
9816 }
9817 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9818 self.change_selections(Default::default(), window, cx, |s| {
9819 // Reverse order so that the first range is the newest created selection.
9820 // Completions will use it and autoscroll will prioritize it.
9821 s.select_ranges(current_ranges.iter().rev().cloned())
9822 });
9823
9824 if let Some(choices) = &snippet.choices[snippet.active_index]
9825 && let Some(selection) = current_ranges.first()
9826 {
9827 self.show_snippet_choices(choices, selection.clone(), cx);
9828 }
9829
9830 // If snippet state is not at the last tabstop, push it back on the stack
9831 if snippet.active_index + 1 < snippet.ranges.len() {
9832 self.snippet_stack.push(snippet);
9833 }
9834 return true;
9835 }
9836 }
9837
9838 false
9839 }
9840
9841 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9842 self.transact(window, cx, |this, window, cx| {
9843 this.select_all(&SelectAll, window, cx);
9844 this.insert("", window, cx);
9845 });
9846 }
9847
9848 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9849 if self.read_only(cx) {
9850 return;
9851 }
9852 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9853 self.transact(window, cx, |this, window, cx| {
9854 this.select_autoclose_pair(window, cx);
9855 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9856 if !this.linked_edit_ranges.is_empty() {
9857 let selections = this.selections.all::<MultiBufferPoint>(cx);
9858 let snapshot = this.buffer.read(cx).snapshot(cx);
9859
9860 for selection in selections.iter() {
9861 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9862 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9863 if selection_start.buffer_id != selection_end.buffer_id {
9864 continue;
9865 }
9866 if let Some(ranges) =
9867 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9868 {
9869 for (buffer, entries) in ranges {
9870 linked_ranges.entry(buffer).or_default().extend(entries);
9871 }
9872 }
9873 }
9874 }
9875
9876 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9877 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9878 for selection in &mut selections {
9879 if selection.is_empty() {
9880 let old_head = selection.head();
9881 let mut new_head =
9882 movement::left(&display_map, old_head.to_display_point(&display_map))
9883 .to_point(&display_map);
9884 if let Some((buffer, line_buffer_range)) = display_map
9885 .buffer_snapshot
9886 .buffer_line_for_row(MultiBufferRow(old_head.row))
9887 {
9888 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9889 let indent_len = match indent_size.kind {
9890 IndentKind::Space => {
9891 buffer.settings_at(line_buffer_range.start, cx).tab_size
9892 }
9893 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9894 };
9895 if old_head.column <= indent_size.len && old_head.column > 0 {
9896 let indent_len = indent_len.get();
9897 new_head = cmp::min(
9898 new_head,
9899 MultiBufferPoint::new(
9900 old_head.row,
9901 ((old_head.column - 1) / indent_len) * indent_len,
9902 ),
9903 );
9904 }
9905 }
9906
9907 selection.set_head(new_head, SelectionGoal::None);
9908 }
9909 }
9910
9911 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9912 this.insert("", window, cx);
9913 let empty_str: Arc<str> = Arc::from("");
9914 for (buffer, edits) in linked_ranges {
9915 let snapshot = buffer.read(cx).snapshot();
9916 use text::ToPoint as TP;
9917
9918 let edits = edits
9919 .into_iter()
9920 .map(|range| {
9921 let end_point = TP::to_point(&range.end, &snapshot);
9922 let mut start_point = TP::to_point(&range.start, &snapshot);
9923
9924 if end_point == start_point {
9925 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9926 .saturating_sub(1);
9927 start_point =
9928 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9929 };
9930
9931 (start_point..end_point, empty_str.clone())
9932 })
9933 .sorted_by_key(|(range, _)| range.start)
9934 .collect::<Vec<_>>();
9935 buffer.update(cx, |this, cx| {
9936 this.edit(edits, None, cx);
9937 })
9938 }
9939 this.refresh_edit_prediction(true, false, window, cx);
9940 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9941 });
9942 }
9943
9944 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9945 if self.read_only(cx) {
9946 return;
9947 }
9948 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9949 self.transact(window, cx, |this, window, cx| {
9950 this.change_selections(Default::default(), window, cx, |s| {
9951 s.move_with(|map, selection| {
9952 if selection.is_empty() {
9953 let cursor = movement::right(map, selection.head());
9954 selection.end = cursor;
9955 selection.reversed = true;
9956 selection.goal = SelectionGoal::None;
9957 }
9958 })
9959 });
9960 this.insert("", window, cx);
9961 this.refresh_edit_prediction(true, false, window, cx);
9962 });
9963 }
9964
9965 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9966 if self.mode.is_single_line() {
9967 cx.propagate();
9968 return;
9969 }
9970
9971 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9972 if self.move_to_prev_snippet_tabstop(window, cx) {
9973 return;
9974 }
9975 self.outdent(&Outdent, window, cx);
9976 }
9977
9978 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9979 if self.mode.is_single_line() {
9980 cx.propagate();
9981 return;
9982 }
9983
9984 if self.move_to_next_snippet_tabstop(window, cx) {
9985 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9986 return;
9987 }
9988 if self.read_only(cx) {
9989 return;
9990 }
9991 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9992 let mut selections = self.selections.all_adjusted(cx);
9993 let buffer = self.buffer.read(cx);
9994 let snapshot = buffer.snapshot(cx);
9995 let rows_iter = selections.iter().map(|s| s.head().row);
9996 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9997
9998 let has_some_cursor_in_whitespace = selections
9999 .iter()
10000 .filter(|selection| selection.is_empty())
10001 .any(|selection| {
10002 let cursor = selection.head();
10003 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10004 cursor.column < current_indent.len
10005 });
10006
10007 let mut edits = Vec::new();
10008 let mut prev_edited_row = 0;
10009 let mut row_delta = 0;
10010 for selection in &mut selections {
10011 if selection.start.row != prev_edited_row {
10012 row_delta = 0;
10013 }
10014 prev_edited_row = selection.end.row;
10015
10016 // If the selection is non-empty, then increase the indentation of the selected lines.
10017 if !selection.is_empty() {
10018 row_delta =
10019 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10020 continue;
10021 }
10022
10023 let cursor = selection.head();
10024 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10025 if let Some(suggested_indent) =
10026 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10027 {
10028 // Don't do anything if already at suggested indent
10029 // and there is any other cursor which is not
10030 if has_some_cursor_in_whitespace
10031 && cursor.column == current_indent.len
10032 && current_indent.len == suggested_indent.len
10033 {
10034 continue;
10035 }
10036
10037 // Adjust line and move cursor to suggested indent
10038 // if cursor is not at suggested indent
10039 if cursor.column < suggested_indent.len
10040 && cursor.column <= current_indent.len
10041 && current_indent.len <= suggested_indent.len
10042 {
10043 selection.start = Point::new(cursor.row, suggested_indent.len);
10044 selection.end = selection.start;
10045 if row_delta == 0 {
10046 edits.extend(Buffer::edit_for_indent_size_adjustment(
10047 cursor.row,
10048 current_indent,
10049 suggested_indent,
10050 ));
10051 row_delta = suggested_indent.len - current_indent.len;
10052 }
10053 continue;
10054 }
10055
10056 // If current indent is more than suggested indent
10057 // only move cursor to current indent and skip indent
10058 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10059 selection.start = Point::new(cursor.row, current_indent.len);
10060 selection.end = selection.start;
10061 continue;
10062 }
10063 }
10064
10065 // Otherwise, insert a hard or soft tab.
10066 let settings = buffer.language_settings_at(cursor, cx);
10067 let tab_size = if settings.hard_tabs {
10068 IndentSize::tab()
10069 } else {
10070 let tab_size = settings.tab_size.get();
10071 let indent_remainder = snapshot
10072 .text_for_range(Point::new(cursor.row, 0)..cursor)
10073 .flat_map(str::chars)
10074 .fold(row_delta % tab_size, |counter: u32, c| {
10075 if c == '\t' {
10076 0
10077 } else {
10078 (counter + 1) % tab_size
10079 }
10080 });
10081
10082 let chars_to_next_tab_stop = tab_size - indent_remainder;
10083 IndentSize::spaces(chars_to_next_tab_stop)
10084 };
10085 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10086 selection.end = selection.start;
10087 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10088 row_delta += tab_size.len;
10089 }
10090
10091 self.transact(window, cx, |this, window, cx| {
10092 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10093 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10094 this.refresh_edit_prediction(true, false, window, cx);
10095 });
10096 }
10097
10098 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10099 if self.read_only(cx) {
10100 return;
10101 }
10102 if self.mode.is_single_line() {
10103 cx.propagate();
10104 return;
10105 }
10106
10107 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10108 let mut selections = self.selections.all::<Point>(cx);
10109 let mut prev_edited_row = 0;
10110 let mut row_delta = 0;
10111 let mut edits = Vec::new();
10112 let buffer = self.buffer.read(cx);
10113 let snapshot = buffer.snapshot(cx);
10114 for selection in &mut selections {
10115 if selection.start.row != prev_edited_row {
10116 row_delta = 0;
10117 }
10118 prev_edited_row = selection.end.row;
10119
10120 row_delta =
10121 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10122 }
10123
10124 self.transact(window, cx, |this, window, cx| {
10125 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10126 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10127 });
10128 }
10129
10130 fn indent_selection(
10131 buffer: &MultiBuffer,
10132 snapshot: &MultiBufferSnapshot,
10133 selection: &mut Selection<Point>,
10134 edits: &mut Vec<(Range<Point>, String)>,
10135 delta_for_start_row: u32,
10136 cx: &App,
10137 ) -> u32 {
10138 let settings = buffer.language_settings_at(selection.start, cx);
10139 let tab_size = settings.tab_size.get();
10140 let indent_kind = if settings.hard_tabs {
10141 IndentKind::Tab
10142 } else {
10143 IndentKind::Space
10144 };
10145 let mut start_row = selection.start.row;
10146 let mut end_row = selection.end.row + 1;
10147
10148 // If a selection ends at the beginning of a line, don't indent
10149 // that last line.
10150 if selection.end.column == 0 && selection.end.row > selection.start.row {
10151 end_row -= 1;
10152 }
10153
10154 // Avoid re-indenting a row that has already been indented by a
10155 // previous selection, but still update this selection's column
10156 // to reflect that indentation.
10157 if delta_for_start_row > 0 {
10158 start_row += 1;
10159 selection.start.column += delta_for_start_row;
10160 if selection.end.row == selection.start.row {
10161 selection.end.column += delta_for_start_row;
10162 }
10163 }
10164
10165 let mut delta_for_end_row = 0;
10166 let has_multiple_rows = start_row + 1 != end_row;
10167 for row in start_row..end_row {
10168 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10169 let indent_delta = match (current_indent.kind, indent_kind) {
10170 (IndentKind::Space, IndentKind::Space) => {
10171 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10172 IndentSize::spaces(columns_to_next_tab_stop)
10173 }
10174 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10175 (_, IndentKind::Tab) => IndentSize::tab(),
10176 };
10177
10178 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10179 0
10180 } else {
10181 selection.start.column
10182 };
10183 let row_start = Point::new(row, start);
10184 edits.push((
10185 row_start..row_start,
10186 indent_delta.chars().collect::<String>(),
10187 ));
10188
10189 // Update this selection's endpoints to reflect the indentation.
10190 if row == selection.start.row {
10191 selection.start.column += indent_delta.len;
10192 }
10193 if row == selection.end.row {
10194 selection.end.column += indent_delta.len;
10195 delta_for_end_row = indent_delta.len;
10196 }
10197 }
10198
10199 if selection.start.row == selection.end.row {
10200 delta_for_start_row + delta_for_end_row
10201 } else {
10202 delta_for_end_row
10203 }
10204 }
10205
10206 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10207 if self.read_only(cx) {
10208 return;
10209 }
10210 if self.mode.is_single_line() {
10211 cx.propagate();
10212 return;
10213 }
10214
10215 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10216 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10217 let selections = self.selections.all::<Point>(cx);
10218 let mut deletion_ranges = Vec::new();
10219 let mut last_outdent = None;
10220 {
10221 let buffer = self.buffer.read(cx);
10222 let snapshot = buffer.snapshot(cx);
10223 for selection in &selections {
10224 let settings = buffer.language_settings_at(selection.start, cx);
10225 let tab_size = settings.tab_size.get();
10226 let mut rows = selection.spanned_rows(false, &display_map);
10227
10228 // Avoid re-outdenting a row that has already been outdented by a
10229 // previous selection.
10230 if let Some(last_row) = last_outdent
10231 && last_row == rows.start
10232 {
10233 rows.start = rows.start.next_row();
10234 }
10235 let has_multiple_rows = rows.len() > 1;
10236 for row in rows.iter_rows() {
10237 let indent_size = snapshot.indent_size_for_line(row);
10238 if indent_size.len > 0 {
10239 let deletion_len = match indent_size.kind {
10240 IndentKind::Space => {
10241 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10242 if columns_to_prev_tab_stop == 0 {
10243 tab_size
10244 } else {
10245 columns_to_prev_tab_stop
10246 }
10247 }
10248 IndentKind::Tab => 1,
10249 };
10250 let start = if has_multiple_rows
10251 || deletion_len > selection.start.column
10252 || indent_size.len < selection.start.column
10253 {
10254 0
10255 } else {
10256 selection.start.column - deletion_len
10257 };
10258 deletion_ranges.push(
10259 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10260 );
10261 last_outdent = Some(row);
10262 }
10263 }
10264 }
10265 }
10266
10267 self.transact(window, cx, |this, window, cx| {
10268 this.buffer.update(cx, |buffer, cx| {
10269 let empty_str: Arc<str> = Arc::default();
10270 buffer.edit(
10271 deletion_ranges
10272 .into_iter()
10273 .map(|range| (range, empty_str.clone())),
10274 None,
10275 cx,
10276 );
10277 });
10278 let selections = this.selections.all::<usize>(cx);
10279 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10280 });
10281 }
10282
10283 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10284 if self.read_only(cx) {
10285 return;
10286 }
10287 if self.mode.is_single_line() {
10288 cx.propagate();
10289 return;
10290 }
10291
10292 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10293 let selections = self
10294 .selections
10295 .all::<usize>(cx)
10296 .into_iter()
10297 .map(|s| s.range());
10298
10299 self.transact(window, cx, |this, window, cx| {
10300 this.buffer.update(cx, |buffer, cx| {
10301 buffer.autoindent_ranges(selections, cx);
10302 });
10303 let selections = this.selections.all::<usize>(cx);
10304 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10305 });
10306 }
10307
10308 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10309 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10310 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10311 let selections = self.selections.all::<Point>(cx);
10312
10313 let mut new_cursors = Vec::new();
10314 let mut edit_ranges = Vec::new();
10315 let mut selections = selections.iter().peekable();
10316 while let Some(selection) = selections.next() {
10317 let mut rows = selection.spanned_rows(false, &display_map);
10318 let goal_display_column = selection.head().to_display_point(&display_map).column();
10319
10320 // Accumulate contiguous regions of rows that we want to delete.
10321 while let Some(next_selection) = selections.peek() {
10322 let next_rows = next_selection.spanned_rows(false, &display_map);
10323 if next_rows.start <= rows.end {
10324 rows.end = next_rows.end;
10325 selections.next().unwrap();
10326 } else {
10327 break;
10328 }
10329 }
10330
10331 let buffer = &display_map.buffer_snapshot;
10332 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10333 let edit_end;
10334 let cursor_buffer_row;
10335 if buffer.max_point().row >= rows.end.0 {
10336 // If there's a line after the range, delete the \n from the end of the row range
10337 // and position the cursor on the next line.
10338 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10339 cursor_buffer_row = rows.end;
10340 } else {
10341 // If there isn't a line after the range, delete the \n from the line before the
10342 // start of the row range and position the cursor there.
10343 edit_start = edit_start.saturating_sub(1);
10344 edit_end = buffer.len();
10345 cursor_buffer_row = rows.start.previous_row();
10346 }
10347
10348 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10349 *cursor.column_mut() =
10350 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10351
10352 new_cursors.push((
10353 selection.id,
10354 buffer.anchor_after(cursor.to_point(&display_map)),
10355 ));
10356 edit_ranges.push(edit_start..edit_end);
10357 }
10358
10359 self.transact(window, cx, |this, window, cx| {
10360 let buffer = this.buffer.update(cx, |buffer, cx| {
10361 let empty_str: Arc<str> = Arc::default();
10362 buffer.edit(
10363 edit_ranges
10364 .into_iter()
10365 .map(|range| (range, empty_str.clone())),
10366 None,
10367 cx,
10368 );
10369 buffer.snapshot(cx)
10370 });
10371 let new_selections = new_cursors
10372 .into_iter()
10373 .map(|(id, cursor)| {
10374 let cursor = cursor.to_point(&buffer);
10375 Selection {
10376 id,
10377 start: cursor,
10378 end: cursor,
10379 reversed: false,
10380 goal: SelectionGoal::None,
10381 }
10382 })
10383 .collect();
10384
10385 this.change_selections(Default::default(), window, cx, |s| {
10386 s.select(new_selections);
10387 });
10388 });
10389 }
10390
10391 pub fn join_lines_impl(
10392 &mut self,
10393 insert_whitespace: bool,
10394 window: &mut Window,
10395 cx: &mut Context<Self>,
10396 ) {
10397 if self.read_only(cx) {
10398 return;
10399 }
10400 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10401 for selection in self.selections.all::<Point>(cx) {
10402 let start = MultiBufferRow(selection.start.row);
10403 // Treat single line selections as if they include the next line. Otherwise this action
10404 // would do nothing for single line selections individual cursors.
10405 let end = if selection.start.row == selection.end.row {
10406 MultiBufferRow(selection.start.row + 1)
10407 } else {
10408 MultiBufferRow(selection.end.row)
10409 };
10410
10411 if let Some(last_row_range) = row_ranges.last_mut()
10412 && start <= last_row_range.end
10413 {
10414 last_row_range.end = end;
10415 continue;
10416 }
10417 row_ranges.push(start..end);
10418 }
10419
10420 let snapshot = self.buffer.read(cx).snapshot(cx);
10421 let mut cursor_positions = Vec::new();
10422 for row_range in &row_ranges {
10423 let anchor = snapshot.anchor_before(Point::new(
10424 row_range.end.previous_row().0,
10425 snapshot.line_len(row_range.end.previous_row()),
10426 ));
10427 cursor_positions.push(anchor..anchor);
10428 }
10429
10430 self.transact(window, cx, |this, window, cx| {
10431 for row_range in row_ranges.into_iter().rev() {
10432 for row in row_range.iter_rows().rev() {
10433 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10434 let next_line_row = row.next_row();
10435 let indent = snapshot.indent_size_for_line(next_line_row);
10436 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10437
10438 let replace =
10439 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10440 " "
10441 } else {
10442 ""
10443 };
10444
10445 this.buffer.update(cx, |buffer, cx| {
10446 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10447 });
10448 }
10449 }
10450
10451 this.change_selections(Default::default(), window, cx, |s| {
10452 s.select_anchor_ranges(cursor_positions)
10453 });
10454 });
10455 }
10456
10457 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10458 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10459 self.join_lines_impl(true, window, cx);
10460 }
10461
10462 pub fn sort_lines_case_sensitive(
10463 &mut self,
10464 _: &SortLinesCaseSensitive,
10465 window: &mut Window,
10466 cx: &mut Context<Self>,
10467 ) {
10468 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10469 }
10470
10471 pub fn sort_lines_by_length(
10472 &mut self,
10473 _: &SortLinesByLength,
10474 window: &mut Window,
10475 cx: &mut Context<Self>,
10476 ) {
10477 self.manipulate_immutable_lines(window, cx, |lines| {
10478 lines.sort_by_key(|&line| line.chars().count())
10479 })
10480 }
10481
10482 pub fn sort_lines_case_insensitive(
10483 &mut self,
10484 _: &SortLinesCaseInsensitive,
10485 window: &mut Window,
10486 cx: &mut Context<Self>,
10487 ) {
10488 self.manipulate_immutable_lines(window, cx, |lines| {
10489 lines.sort_by_key(|line| line.to_lowercase())
10490 })
10491 }
10492
10493 pub fn unique_lines_case_insensitive(
10494 &mut self,
10495 _: &UniqueLinesCaseInsensitive,
10496 window: &mut Window,
10497 cx: &mut Context<Self>,
10498 ) {
10499 self.manipulate_immutable_lines(window, cx, |lines| {
10500 let mut seen = HashSet::default();
10501 lines.retain(|line| seen.insert(line.to_lowercase()));
10502 })
10503 }
10504
10505 pub fn unique_lines_case_sensitive(
10506 &mut self,
10507 _: &UniqueLinesCaseSensitive,
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));
10514 })
10515 }
10516
10517 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10518 let snapshot = self.buffer.read(cx).snapshot(cx);
10519 for selection in self.selections.disjoint_anchors_arc().iter() {
10520 if snapshot
10521 .language_at(selection.start)
10522 .and_then(|lang| lang.config().wrap_characters.as_ref())
10523 .is_some()
10524 {
10525 return true;
10526 }
10527 }
10528 false
10529 }
10530
10531 fn wrap_selections_in_tag(
10532 &mut self,
10533 _: &WrapSelectionsInTag,
10534 window: &mut Window,
10535 cx: &mut Context<Self>,
10536 ) {
10537 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10538
10539 let snapshot = self.buffer.read(cx).snapshot(cx);
10540
10541 let mut edits = Vec::new();
10542 let mut boundaries = Vec::new();
10543
10544 for selection in self.selections.all::<Point>(cx).iter() {
10545 let Some(wrap_config) = snapshot
10546 .language_at(selection.start)
10547 .and_then(|lang| lang.config().wrap_characters.clone())
10548 else {
10549 continue;
10550 };
10551
10552 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10553 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10554
10555 let start_before = snapshot.anchor_before(selection.start);
10556 let end_after = snapshot.anchor_after(selection.end);
10557
10558 edits.push((start_before..start_before, open_tag));
10559 edits.push((end_after..end_after, close_tag));
10560
10561 boundaries.push((
10562 start_before,
10563 end_after,
10564 wrap_config.start_prefix.len(),
10565 wrap_config.end_suffix.len(),
10566 ));
10567 }
10568
10569 if edits.is_empty() {
10570 return;
10571 }
10572
10573 self.transact(window, cx, |this, window, cx| {
10574 let buffer = this.buffer.update(cx, |buffer, cx| {
10575 buffer.edit(edits, None, cx);
10576 buffer.snapshot(cx)
10577 });
10578
10579 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10580 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10581 boundaries.into_iter()
10582 {
10583 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10584 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10585 new_selections.push(open_offset..open_offset);
10586 new_selections.push(close_offset..close_offset);
10587 }
10588
10589 this.change_selections(Default::default(), window, cx, |s| {
10590 s.select_ranges(new_selections);
10591 });
10592
10593 this.request_autoscroll(Autoscroll::fit(), cx);
10594 });
10595 }
10596
10597 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10598 let Some(project) = self.project.clone() else {
10599 return;
10600 };
10601 self.reload(project, window, cx)
10602 .detach_and_notify_err(window, cx);
10603 }
10604
10605 pub fn restore_file(
10606 &mut self,
10607 _: &::git::RestoreFile,
10608 window: &mut Window,
10609 cx: &mut Context<Self>,
10610 ) {
10611 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10612 let mut buffer_ids = HashSet::default();
10613 let snapshot = self.buffer().read(cx).snapshot(cx);
10614 for selection in self.selections.all::<usize>(cx) {
10615 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10616 }
10617
10618 let buffer = self.buffer().read(cx);
10619 let ranges = buffer_ids
10620 .into_iter()
10621 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10622 .collect::<Vec<_>>();
10623
10624 self.restore_hunks_in_ranges(ranges, window, cx);
10625 }
10626
10627 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10628 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10629 let selections = self
10630 .selections
10631 .all(cx)
10632 .into_iter()
10633 .map(|s| s.range())
10634 .collect();
10635 self.restore_hunks_in_ranges(selections, window, cx);
10636 }
10637
10638 pub fn restore_hunks_in_ranges(
10639 &mut self,
10640 ranges: Vec<Range<Point>>,
10641 window: &mut Window,
10642 cx: &mut Context<Editor>,
10643 ) {
10644 let mut revert_changes = HashMap::default();
10645 let chunk_by = self
10646 .snapshot(window, cx)
10647 .hunks_for_ranges(ranges)
10648 .into_iter()
10649 .chunk_by(|hunk| hunk.buffer_id);
10650 for (buffer_id, hunks) in &chunk_by {
10651 let hunks = hunks.collect::<Vec<_>>();
10652 for hunk in &hunks {
10653 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10654 }
10655 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10656 }
10657 drop(chunk_by);
10658 if !revert_changes.is_empty() {
10659 self.transact(window, cx, |editor, window, cx| {
10660 editor.restore(revert_changes, window, cx);
10661 });
10662 }
10663 }
10664
10665 pub fn open_active_item_in_terminal(
10666 &mut self,
10667 _: &OpenInTerminal,
10668 window: &mut Window,
10669 cx: &mut Context<Self>,
10670 ) {
10671 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10672 let project_path = buffer.read(cx).project_path(cx)?;
10673 let project = self.project()?.read(cx);
10674 let entry = project.entry_for_path(&project_path, cx)?;
10675 let parent = match &entry.canonical_path {
10676 Some(canonical_path) => canonical_path.to_path_buf(),
10677 None => project.absolute_path(&project_path, cx)?,
10678 }
10679 .parent()?
10680 .to_path_buf();
10681 Some(parent)
10682 }) {
10683 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10684 }
10685 }
10686
10687 fn set_breakpoint_context_menu(
10688 &mut self,
10689 display_row: DisplayRow,
10690 position: Option<Anchor>,
10691 clicked_point: gpui::Point<Pixels>,
10692 window: &mut Window,
10693 cx: &mut Context<Self>,
10694 ) {
10695 let source = self
10696 .buffer
10697 .read(cx)
10698 .snapshot(cx)
10699 .anchor_before(Point::new(display_row.0, 0u32));
10700
10701 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10702
10703 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10704 self,
10705 source,
10706 clicked_point,
10707 context_menu,
10708 window,
10709 cx,
10710 );
10711 }
10712
10713 fn add_edit_breakpoint_block(
10714 &mut self,
10715 anchor: Anchor,
10716 breakpoint: &Breakpoint,
10717 edit_action: BreakpointPromptEditAction,
10718 window: &mut Window,
10719 cx: &mut Context<Self>,
10720 ) {
10721 let weak_editor = cx.weak_entity();
10722 let bp_prompt = cx.new(|cx| {
10723 BreakpointPromptEditor::new(
10724 weak_editor,
10725 anchor,
10726 breakpoint.clone(),
10727 edit_action,
10728 window,
10729 cx,
10730 )
10731 });
10732
10733 let height = bp_prompt.update(cx, |this, cx| {
10734 this.prompt
10735 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10736 });
10737 let cloned_prompt = bp_prompt.clone();
10738 let blocks = vec![BlockProperties {
10739 style: BlockStyle::Sticky,
10740 placement: BlockPlacement::Above(anchor),
10741 height: Some(height),
10742 render: Arc::new(move |cx| {
10743 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10744 cloned_prompt.clone().into_any_element()
10745 }),
10746 priority: 0,
10747 }];
10748
10749 let focus_handle = bp_prompt.focus_handle(cx);
10750 window.focus(&focus_handle);
10751
10752 let block_ids = self.insert_blocks(blocks, None, cx);
10753 bp_prompt.update(cx, |prompt, _| {
10754 prompt.add_block_ids(block_ids);
10755 });
10756 }
10757
10758 pub(crate) fn breakpoint_at_row(
10759 &self,
10760 row: u32,
10761 window: &mut Window,
10762 cx: &mut Context<Self>,
10763 ) -> Option<(Anchor, Breakpoint)> {
10764 let snapshot = self.snapshot(window, cx);
10765 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10766
10767 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10768 }
10769
10770 pub(crate) fn breakpoint_at_anchor(
10771 &self,
10772 breakpoint_position: Anchor,
10773 snapshot: &EditorSnapshot,
10774 cx: &mut Context<Self>,
10775 ) -> Option<(Anchor, Breakpoint)> {
10776 let buffer = self
10777 .buffer
10778 .read(cx)
10779 .buffer_for_anchor(breakpoint_position, cx)?;
10780
10781 let enclosing_excerpt = breakpoint_position.excerpt_id;
10782 let buffer_snapshot = buffer.read(cx).snapshot();
10783
10784 let row = buffer_snapshot
10785 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10786 .row;
10787
10788 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10789 let anchor_end = snapshot
10790 .buffer_snapshot
10791 .anchor_after(Point::new(row, line_len));
10792
10793 self.breakpoint_store
10794 .as_ref()?
10795 .read_with(cx, |breakpoint_store, cx| {
10796 breakpoint_store
10797 .breakpoints(
10798 &buffer,
10799 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10800 &buffer_snapshot,
10801 cx,
10802 )
10803 .next()
10804 .and_then(|(bp, _)| {
10805 let breakpoint_row = buffer_snapshot
10806 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10807 .row;
10808
10809 if breakpoint_row == row {
10810 snapshot
10811 .buffer_snapshot
10812 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10813 .map(|position| (position, bp.bp.clone()))
10814 } else {
10815 None
10816 }
10817 })
10818 })
10819 }
10820
10821 pub fn edit_log_breakpoint(
10822 &mut self,
10823 _: &EditLogBreakpoint,
10824 window: &mut Window,
10825 cx: &mut Context<Self>,
10826 ) {
10827 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10828 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10829 message: None,
10830 state: BreakpointState::Enabled,
10831 condition: None,
10832 hit_condition: None,
10833 });
10834
10835 self.add_edit_breakpoint_block(
10836 anchor,
10837 &breakpoint,
10838 BreakpointPromptEditAction::Log,
10839 window,
10840 cx,
10841 );
10842 }
10843 }
10844
10845 fn breakpoints_at_cursors(
10846 &self,
10847 window: &mut Window,
10848 cx: &mut Context<Self>,
10849 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10850 let snapshot = self.snapshot(window, cx);
10851 let cursors = self
10852 .selections
10853 .disjoint_anchors_arc()
10854 .iter()
10855 .map(|selection| {
10856 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10857
10858 let breakpoint_position = self
10859 .breakpoint_at_row(cursor_position.row, window, cx)
10860 .map(|bp| bp.0)
10861 .unwrap_or_else(|| {
10862 snapshot
10863 .display_snapshot
10864 .buffer_snapshot
10865 .anchor_after(Point::new(cursor_position.row, 0))
10866 });
10867
10868 let breakpoint = self
10869 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10870 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10871
10872 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10873 })
10874 // 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.
10875 .collect::<HashMap<Anchor, _>>();
10876
10877 cursors.into_iter().collect()
10878 }
10879
10880 pub fn enable_breakpoint(
10881 &mut self,
10882 _: &crate::actions::EnableBreakpoint,
10883 window: &mut Window,
10884 cx: &mut Context<Self>,
10885 ) {
10886 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10887 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10888 continue;
10889 };
10890 self.edit_breakpoint_at_anchor(
10891 anchor,
10892 breakpoint,
10893 BreakpointEditAction::InvertState,
10894 cx,
10895 );
10896 }
10897 }
10898
10899 pub fn disable_breakpoint(
10900 &mut self,
10901 _: &crate::actions::DisableBreakpoint,
10902 window: &mut Window,
10903 cx: &mut Context<Self>,
10904 ) {
10905 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10906 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10907 continue;
10908 };
10909 self.edit_breakpoint_at_anchor(
10910 anchor,
10911 breakpoint,
10912 BreakpointEditAction::InvertState,
10913 cx,
10914 );
10915 }
10916 }
10917
10918 pub fn toggle_breakpoint(
10919 &mut self,
10920 _: &crate::actions::ToggleBreakpoint,
10921 window: &mut Window,
10922 cx: &mut Context<Self>,
10923 ) {
10924 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10925 if let Some(breakpoint) = breakpoint {
10926 self.edit_breakpoint_at_anchor(
10927 anchor,
10928 breakpoint,
10929 BreakpointEditAction::Toggle,
10930 cx,
10931 );
10932 } else {
10933 self.edit_breakpoint_at_anchor(
10934 anchor,
10935 Breakpoint::new_standard(),
10936 BreakpointEditAction::Toggle,
10937 cx,
10938 );
10939 }
10940 }
10941 }
10942
10943 pub fn edit_breakpoint_at_anchor(
10944 &mut self,
10945 breakpoint_position: Anchor,
10946 breakpoint: Breakpoint,
10947 edit_action: BreakpointEditAction,
10948 cx: &mut Context<Self>,
10949 ) {
10950 let Some(breakpoint_store) = &self.breakpoint_store else {
10951 return;
10952 };
10953
10954 let Some(buffer) = self
10955 .buffer
10956 .read(cx)
10957 .buffer_for_anchor(breakpoint_position, cx)
10958 else {
10959 return;
10960 };
10961
10962 breakpoint_store.update(cx, |breakpoint_store, cx| {
10963 breakpoint_store.toggle_breakpoint(
10964 buffer,
10965 BreakpointWithPosition {
10966 position: breakpoint_position.text_anchor,
10967 bp: breakpoint,
10968 },
10969 edit_action,
10970 cx,
10971 );
10972 });
10973
10974 cx.notify();
10975 }
10976
10977 #[cfg(any(test, feature = "test-support"))]
10978 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10979 self.breakpoint_store.clone()
10980 }
10981
10982 pub fn prepare_restore_change(
10983 &self,
10984 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10985 hunk: &MultiBufferDiffHunk,
10986 cx: &mut App,
10987 ) -> Option<()> {
10988 if hunk.is_created_file() {
10989 return None;
10990 }
10991 let buffer = self.buffer.read(cx);
10992 let diff = buffer.diff_for(hunk.buffer_id)?;
10993 let buffer = buffer.buffer(hunk.buffer_id)?;
10994 let buffer = buffer.read(cx);
10995 let original_text = diff
10996 .read(cx)
10997 .base_text()
10998 .as_rope()
10999 .slice(hunk.diff_base_byte_range.clone());
11000 let buffer_snapshot = buffer.snapshot();
11001 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11002 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11003 probe
11004 .0
11005 .start
11006 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11007 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11008 }) {
11009 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11010 Some(())
11011 } else {
11012 None
11013 }
11014 }
11015
11016 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11017 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11018 }
11019
11020 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11021 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11022 }
11023
11024 fn manipulate_lines<M>(
11025 &mut self,
11026 window: &mut Window,
11027 cx: &mut Context<Self>,
11028 mut manipulate: M,
11029 ) where
11030 M: FnMut(&str) -> LineManipulationResult,
11031 {
11032 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11033
11034 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11035 let buffer = self.buffer.read(cx).snapshot(cx);
11036
11037 let mut edits = Vec::new();
11038
11039 let selections = self.selections.all::<Point>(cx);
11040 let mut selections = selections.iter().peekable();
11041 let mut contiguous_row_selections = Vec::new();
11042 let mut new_selections = Vec::new();
11043 let mut added_lines = 0;
11044 let mut removed_lines = 0;
11045
11046 while let Some(selection) = selections.next() {
11047 let (start_row, end_row) = consume_contiguous_rows(
11048 &mut contiguous_row_selections,
11049 selection,
11050 &display_map,
11051 &mut selections,
11052 );
11053
11054 let start_point = Point::new(start_row.0, 0);
11055 let end_point = Point::new(
11056 end_row.previous_row().0,
11057 buffer.line_len(end_row.previous_row()),
11058 );
11059 let text = buffer
11060 .text_for_range(start_point..end_point)
11061 .collect::<String>();
11062
11063 let LineManipulationResult {
11064 new_text,
11065 line_count_before,
11066 line_count_after,
11067 } = manipulate(&text);
11068
11069 edits.push((start_point..end_point, new_text));
11070
11071 // Selections must change based on added and removed line count
11072 let start_row =
11073 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11074 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11075 new_selections.push(Selection {
11076 id: selection.id,
11077 start: start_row,
11078 end: end_row,
11079 goal: SelectionGoal::None,
11080 reversed: selection.reversed,
11081 });
11082
11083 if line_count_after > line_count_before {
11084 added_lines += line_count_after - line_count_before;
11085 } else if line_count_before > line_count_after {
11086 removed_lines += line_count_before - line_count_after;
11087 }
11088 }
11089
11090 self.transact(window, cx, |this, window, cx| {
11091 let buffer = this.buffer.update(cx, |buffer, cx| {
11092 buffer.edit(edits, None, cx);
11093 buffer.snapshot(cx)
11094 });
11095
11096 // Recalculate offsets on newly edited buffer
11097 let new_selections = new_selections
11098 .iter()
11099 .map(|s| {
11100 let start_point = Point::new(s.start.0, 0);
11101 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11102 Selection {
11103 id: s.id,
11104 start: buffer.point_to_offset(start_point),
11105 end: buffer.point_to_offset(end_point),
11106 goal: s.goal,
11107 reversed: s.reversed,
11108 }
11109 })
11110 .collect();
11111
11112 this.change_selections(Default::default(), window, cx, |s| {
11113 s.select(new_selections);
11114 });
11115
11116 this.request_autoscroll(Autoscroll::fit(), cx);
11117 });
11118 }
11119
11120 fn manipulate_immutable_lines<Fn>(
11121 &mut self,
11122 window: &mut Window,
11123 cx: &mut Context<Self>,
11124 mut callback: Fn,
11125 ) where
11126 Fn: FnMut(&mut Vec<&str>),
11127 {
11128 self.manipulate_lines(window, cx, |text| {
11129 let mut lines: Vec<&str> = text.split('\n').collect();
11130 let line_count_before = lines.len();
11131
11132 callback(&mut lines);
11133
11134 LineManipulationResult {
11135 new_text: lines.join("\n"),
11136 line_count_before,
11137 line_count_after: lines.len(),
11138 }
11139 });
11140 }
11141
11142 fn manipulate_mutable_lines<Fn>(
11143 &mut self,
11144 window: &mut Window,
11145 cx: &mut Context<Self>,
11146 mut callback: Fn,
11147 ) where
11148 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11149 {
11150 self.manipulate_lines(window, cx, |text| {
11151 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11152 let line_count_before = lines.len();
11153
11154 callback(&mut lines);
11155
11156 LineManipulationResult {
11157 new_text: lines.join("\n"),
11158 line_count_before,
11159 line_count_after: lines.len(),
11160 }
11161 });
11162 }
11163
11164 pub fn convert_indentation_to_spaces(
11165 &mut self,
11166 _: &ConvertIndentationToSpaces,
11167 window: &mut Window,
11168 cx: &mut Context<Self>,
11169 ) {
11170 let settings = self.buffer.read(cx).language_settings(cx);
11171 let tab_size = settings.tab_size.get() as usize;
11172
11173 self.manipulate_mutable_lines(window, cx, |lines| {
11174 // Allocates a reasonably sized scratch buffer once for the whole loop
11175 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11176 // Avoids recomputing spaces that could be inserted many times
11177 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11178 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11179 .collect();
11180
11181 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11182 let mut chars = line.as_ref().chars();
11183 let mut col = 0;
11184 let mut changed = false;
11185
11186 for ch in chars.by_ref() {
11187 match ch {
11188 ' ' => {
11189 reindented_line.push(' ');
11190 col += 1;
11191 }
11192 '\t' => {
11193 // \t are converted to spaces depending on the current column
11194 let spaces_len = tab_size - (col % tab_size);
11195 reindented_line.extend(&space_cache[spaces_len - 1]);
11196 col += spaces_len;
11197 changed = true;
11198 }
11199 _ => {
11200 // If we dont append before break, the character is consumed
11201 reindented_line.push(ch);
11202 break;
11203 }
11204 }
11205 }
11206
11207 if !changed {
11208 reindented_line.clear();
11209 continue;
11210 }
11211 // Append the rest of the line and replace old reference with new one
11212 reindented_line.extend(chars);
11213 *line = Cow::Owned(reindented_line.clone());
11214 reindented_line.clear();
11215 }
11216 });
11217 }
11218
11219 pub fn convert_indentation_to_tabs(
11220 &mut self,
11221 _: &ConvertIndentationToTabs,
11222 window: &mut Window,
11223 cx: &mut Context<Self>,
11224 ) {
11225 let settings = self.buffer.read(cx).language_settings(cx);
11226 let tab_size = settings.tab_size.get() as usize;
11227
11228 self.manipulate_mutable_lines(window, cx, |lines| {
11229 // Allocates a reasonably sized buffer once for the whole loop
11230 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11231 // Avoids recomputing spaces that could be inserted many times
11232 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11233 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11234 .collect();
11235
11236 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11237 let mut chars = line.chars();
11238 let mut spaces_count = 0;
11239 let mut first_non_indent_char = None;
11240 let mut changed = false;
11241
11242 for ch in chars.by_ref() {
11243 match ch {
11244 ' ' => {
11245 // Keep track of spaces. Append \t when we reach tab_size
11246 spaces_count += 1;
11247 changed = true;
11248 if spaces_count == tab_size {
11249 reindented_line.push('\t');
11250 spaces_count = 0;
11251 }
11252 }
11253 '\t' => {
11254 reindented_line.push('\t');
11255 spaces_count = 0;
11256 }
11257 _ => {
11258 // Dont append it yet, we might have remaining spaces
11259 first_non_indent_char = Some(ch);
11260 break;
11261 }
11262 }
11263 }
11264
11265 if !changed {
11266 reindented_line.clear();
11267 continue;
11268 }
11269 // Remaining spaces that didn't make a full tab stop
11270 if spaces_count > 0 {
11271 reindented_line.extend(&space_cache[spaces_count - 1]);
11272 }
11273 // If we consume an extra character that was not indentation, add it back
11274 if let Some(extra_char) = first_non_indent_char {
11275 reindented_line.push(extra_char);
11276 }
11277 // Append the rest of the line and replace old reference with new one
11278 reindented_line.extend(chars);
11279 *line = Cow::Owned(reindented_line.clone());
11280 reindented_line.clear();
11281 }
11282 });
11283 }
11284
11285 pub fn convert_to_upper_case(
11286 &mut self,
11287 _: &ConvertToUpperCase,
11288 window: &mut Window,
11289 cx: &mut Context<Self>,
11290 ) {
11291 self.manipulate_text(window, cx, |text| text.to_uppercase())
11292 }
11293
11294 pub fn convert_to_lower_case(
11295 &mut self,
11296 _: &ConvertToLowerCase,
11297 window: &mut Window,
11298 cx: &mut Context<Self>,
11299 ) {
11300 self.manipulate_text(window, cx, |text| text.to_lowercase())
11301 }
11302
11303 pub fn convert_to_title_case(
11304 &mut self,
11305 _: &ConvertToTitleCase,
11306 window: &mut Window,
11307 cx: &mut Context<Self>,
11308 ) {
11309 self.manipulate_text(window, cx, |text| {
11310 text.split('\n')
11311 .map(|line| line.to_case(Case::Title))
11312 .join("\n")
11313 })
11314 }
11315
11316 pub fn convert_to_snake_case(
11317 &mut self,
11318 _: &ConvertToSnakeCase,
11319 window: &mut Window,
11320 cx: &mut Context<Self>,
11321 ) {
11322 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11323 }
11324
11325 pub fn convert_to_kebab_case(
11326 &mut self,
11327 _: &ConvertToKebabCase,
11328 window: &mut Window,
11329 cx: &mut Context<Self>,
11330 ) {
11331 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11332 }
11333
11334 pub fn convert_to_upper_camel_case(
11335 &mut self,
11336 _: &ConvertToUpperCamelCase,
11337 window: &mut Window,
11338 cx: &mut Context<Self>,
11339 ) {
11340 self.manipulate_text(window, cx, |text| {
11341 text.split('\n')
11342 .map(|line| line.to_case(Case::UpperCamel))
11343 .join("\n")
11344 })
11345 }
11346
11347 pub fn convert_to_lower_camel_case(
11348 &mut self,
11349 _: &ConvertToLowerCamelCase,
11350 window: &mut Window,
11351 cx: &mut Context<Self>,
11352 ) {
11353 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11354 }
11355
11356 pub fn convert_to_opposite_case(
11357 &mut self,
11358 _: &ConvertToOppositeCase,
11359 window: &mut Window,
11360 cx: &mut Context<Self>,
11361 ) {
11362 self.manipulate_text(window, cx, |text| {
11363 text.chars()
11364 .fold(String::with_capacity(text.len()), |mut t, c| {
11365 if c.is_uppercase() {
11366 t.extend(c.to_lowercase());
11367 } else {
11368 t.extend(c.to_uppercase());
11369 }
11370 t
11371 })
11372 })
11373 }
11374
11375 pub fn convert_to_sentence_case(
11376 &mut self,
11377 _: &ConvertToSentenceCase,
11378 window: &mut Window,
11379 cx: &mut Context<Self>,
11380 ) {
11381 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11382 }
11383
11384 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11385 self.manipulate_text(window, cx, |text| {
11386 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11387 if has_upper_case_characters {
11388 text.to_lowercase()
11389 } else {
11390 text.to_uppercase()
11391 }
11392 })
11393 }
11394
11395 pub fn convert_to_rot13(
11396 &mut self,
11397 _: &ConvertToRot13,
11398 window: &mut Window,
11399 cx: &mut Context<Self>,
11400 ) {
11401 self.manipulate_text(window, cx, |text| {
11402 text.chars()
11403 .map(|c| match c {
11404 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11405 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11406 _ => c,
11407 })
11408 .collect()
11409 })
11410 }
11411
11412 pub fn convert_to_rot47(
11413 &mut self,
11414 _: &ConvertToRot47,
11415 window: &mut Window,
11416 cx: &mut Context<Self>,
11417 ) {
11418 self.manipulate_text(window, cx, |text| {
11419 text.chars()
11420 .map(|c| {
11421 let code_point = c as u32;
11422 if code_point >= 33 && code_point <= 126 {
11423 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11424 }
11425 c
11426 })
11427 .collect()
11428 })
11429 }
11430
11431 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11432 where
11433 Fn: FnMut(&str) -> String,
11434 {
11435 let buffer = self.buffer.read(cx).snapshot(cx);
11436
11437 let mut new_selections = Vec::new();
11438 let mut edits = Vec::new();
11439 let mut selection_adjustment = 0i32;
11440
11441 for selection in self.selections.all_adjusted(cx) {
11442 let selection_is_empty = selection.is_empty();
11443
11444 let (start, end) = if selection_is_empty {
11445 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11446 (word_range.start, word_range.end)
11447 } else {
11448 (
11449 buffer.point_to_offset(selection.start),
11450 buffer.point_to_offset(selection.end),
11451 )
11452 };
11453
11454 let text = buffer.text_for_range(start..end).collect::<String>();
11455 let old_length = text.len() as i32;
11456 let text = callback(&text);
11457
11458 new_selections.push(Selection {
11459 start: (start as i32 - selection_adjustment) as usize,
11460 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11461 goal: SelectionGoal::None,
11462 id: selection.id,
11463 reversed: selection.reversed,
11464 });
11465
11466 selection_adjustment += old_length - text.len() as i32;
11467
11468 edits.push((start..end, text));
11469 }
11470
11471 self.transact(window, cx, |this, window, cx| {
11472 this.buffer.update(cx, |buffer, cx| {
11473 buffer.edit(edits, None, cx);
11474 });
11475
11476 this.change_selections(Default::default(), window, cx, |s| {
11477 s.select(new_selections);
11478 });
11479
11480 this.request_autoscroll(Autoscroll::fit(), cx);
11481 });
11482 }
11483
11484 pub fn move_selection_on_drop(
11485 &mut self,
11486 selection: &Selection<Anchor>,
11487 target: DisplayPoint,
11488 is_cut: bool,
11489 window: &mut Window,
11490 cx: &mut Context<Self>,
11491 ) {
11492 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11493 let buffer = &display_map.buffer_snapshot;
11494 let mut edits = Vec::new();
11495 let insert_point = display_map
11496 .clip_point(target, Bias::Left)
11497 .to_point(&display_map);
11498 let text = buffer
11499 .text_for_range(selection.start..selection.end)
11500 .collect::<String>();
11501 if is_cut {
11502 edits.push(((selection.start..selection.end), String::new()));
11503 }
11504 let insert_anchor = buffer.anchor_before(insert_point);
11505 edits.push(((insert_anchor..insert_anchor), text));
11506 let last_edit_start = insert_anchor.bias_left(buffer);
11507 let last_edit_end = insert_anchor.bias_right(buffer);
11508 self.transact(window, cx, |this, window, cx| {
11509 this.buffer.update(cx, |buffer, cx| {
11510 buffer.edit(edits, None, cx);
11511 });
11512 this.change_selections(Default::default(), window, cx, |s| {
11513 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11514 });
11515 });
11516 }
11517
11518 pub fn clear_selection_drag_state(&mut self) {
11519 self.selection_drag_state = SelectionDragState::None;
11520 }
11521
11522 pub fn duplicate(
11523 &mut self,
11524 upwards: bool,
11525 whole_lines: bool,
11526 window: &mut Window,
11527 cx: &mut Context<Self>,
11528 ) {
11529 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11530
11531 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11532 let buffer = &display_map.buffer_snapshot;
11533 let selections = self.selections.all::<Point>(cx);
11534
11535 let mut edits = Vec::new();
11536 let mut selections_iter = selections.iter().peekable();
11537 while let Some(selection) = selections_iter.next() {
11538 let mut rows = selection.spanned_rows(false, &display_map);
11539 // duplicate line-wise
11540 if whole_lines || selection.start == selection.end {
11541 // Avoid duplicating the same lines twice.
11542 while let Some(next_selection) = selections_iter.peek() {
11543 let next_rows = next_selection.spanned_rows(false, &display_map);
11544 if next_rows.start < rows.end {
11545 rows.end = next_rows.end;
11546 selections_iter.next().unwrap();
11547 } else {
11548 break;
11549 }
11550 }
11551
11552 // Copy the text from the selected row region and splice it either at the start
11553 // or end of the region.
11554 let start = Point::new(rows.start.0, 0);
11555 let end = Point::new(
11556 rows.end.previous_row().0,
11557 buffer.line_len(rows.end.previous_row()),
11558 );
11559 let text = buffer
11560 .text_for_range(start..end)
11561 .chain(Some("\n"))
11562 .collect::<String>();
11563 let insert_location = if upwards {
11564 Point::new(rows.end.0, 0)
11565 } else {
11566 start
11567 };
11568 edits.push((insert_location..insert_location, text));
11569 } else {
11570 // duplicate character-wise
11571 let start = selection.start;
11572 let end = selection.end;
11573 let text = buffer.text_for_range(start..end).collect::<String>();
11574 edits.push((selection.end..selection.end, text));
11575 }
11576 }
11577
11578 self.transact(window, cx, |this, _, cx| {
11579 this.buffer.update(cx, |buffer, cx| {
11580 buffer.edit(edits, None, cx);
11581 });
11582
11583 this.request_autoscroll(Autoscroll::fit(), cx);
11584 });
11585 }
11586
11587 pub fn duplicate_line_up(
11588 &mut self,
11589 _: &DuplicateLineUp,
11590 window: &mut Window,
11591 cx: &mut Context<Self>,
11592 ) {
11593 self.duplicate(true, true, window, cx);
11594 }
11595
11596 pub fn duplicate_line_down(
11597 &mut self,
11598 _: &DuplicateLineDown,
11599 window: &mut Window,
11600 cx: &mut Context<Self>,
11601 ) {
11602 self.duplicate(false, true, window, cx);
11603 }
11604
11605 pub fn duplicate_selection(
11606 &mut self,
11607 _: &DuplicateSelection,
11608 window: &mut Window,
11609 cx: &mut Context<Self>,
11610 ) {
11611 self.duplicate(false, false, window, cx);
11612 }
11613
11614 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11615 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11616 if self.mode.is_single_line() {
11617 cx.propagate();
11618 return;
11619 }
11620
11621 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11622 let buffer = self.buffer.read(cx).snapshot(cx);
11623
11624 let mut edits = Vec::new();
11625 let mut unfold_ranges = Vec::new();
11626 let mut refold_creases = Vec::new();
11627
11628 let selections = self.selections.all::<Point>(cx);
11629 let mut selections = selections.iter().peekable();
11630 let mut contiguous_row_selections = Vec::new();
11631 let mut new_selections = Vec::new();
11632
11633 while let Some(selection) = selections.next() {
11634 // Find all the selections that span a contiguous row range
11635 let (start_row, end_row) = consume_contiguous_rows(
11636 &mut contiguous_row_selections,
11637 selection,
11638 &display_map,
11639 &mut selections,
11640 );
11641
11642 // Move the text spanned by the row range to be before the line preceding the row range
11643 if start_row.0 > 0 {
11644 let range_to_move = Point::new(
11645 start_row.previous_row().0,
11646 buffer.line_len(start_row.previous_row()),
11647 )
11648 ..Point::new(
11649 end_row.previous_row().0,
11650 buffer.line_len(end_row.previous_row()),
11651 );
11652 let insertion_point = display_map
11653 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11654 .0;
11655
11656 // Don't move lines across excerpts
11657 if buffer
11658 .excerpt_containing(insertion_point..range_to_move.end)
11659 .is_some()
11660 {
11661 let text = buffer
11662 .text_for_range(range_to_move.clone())
11663 .flat_map(|s| s.chars())
11664 .skip(1)
11665 .chain(['\n'])
11666 .collect::<String>();
11667
11668 edits.push((
11669 buffer.anchor_after(range_to_move.start)
11670 ..buffer.anchor_before(range_to_move.end),
11671 String::new(),
11672 ));
11673 let insertion_anchor = buffer.anchor_after(insertion_point);
11674 edits.push((insertion_anchor..insertion_anchor, text));
11675
11676 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11677
11678 // Move selections up
11679 new_selections.extend(contiguous_row_selections.drain(..).map(
11680 |mut selection| {
11681 selection.start.row -= row_delta;
11682 selection.end.row -= row_delta;
11683 selection
11684 },
11685 ));
11686
11687 // Move folds up
11688 unfold_ranges.push(range_to_move.clone());
11689 for fold in display_map.folds_in_range(
11690 buffer.anchor_before(range_to_move.start)
11691 ..buffer.anchor_after(range_to_move.end),
11692 ) {
11693 let mut start = fold.range.start.to_point(&buffer);
11694 let mut end = fold.range.end.to_point(&buffer);
11695 start.row -= row_delta;
11696 end.row -= row_delta;
11697 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11698 }
11699 }
11700 }
11701
11702 // If we didn't move line(s), preserve the existing selections
11703 new_selections.append(&mut contiguous_row_selections);
11704 }
11705
11706 self.transact(window, cx, |this, window, cx| {
11707 this.unfold_ranges(&unfold_ranges, true, true, cx);
11708 this.buffer.update(cx, |buffer, cx| {
11709 for (range, text) in edits {
11710 buffer.edit([(range, text)], None, cx);
11711 }
11712 });
11713 this.fold_creases(refold_creases, true, window, cx);
11714 this.change_selections(Default::default(), window, cx, |s| {
11715 s.select(new_selections);
11716 })
11717 });
11718 }
11719
11720 pub fn move_line_down(
11721 &mut self,
11722 _: &MoveLineDown,
11723 window: &mut Window,
11724 cx: &mut Context<Self>,
11725 ) {
11726 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11727 if self.mode.is_single_line() {
11728 cx.propagate();
11729 return;
11730 }
11731
11732 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11733 let buffer = self.buffer.read(cx).snapshot(cx);
11734
11735 let mut edits = Vec::new();
11736 let mut unfold_ranges = Vec::new();
11737 let mut refold_creases = Vec::new();
11738
11739 let selections = self.selections.all::<Point>(cx);
11740 let mut selections = selections.iter().peekable();
11741 let mut contiguous_row_selections = Vec::new();
11742 let mut new_selections = Vec::new();
11743
11744 while let Some(selection) = selections.next() {
11745 // Find all the selections that span a contiguous row range
11746 let (start_row, end_row) = consume_contiguous_rows(
11747 &mut contiguous_row_selections,
11748 selection,
11749 &display_map,
11750 &mut selections,
11751 );
11752
11753 // Move the text spanned by the row range to be after the last line of the row range
11754 if end_row.0 <= buffer.max_point().row {
11755 let range_to_move =
11756 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11757 let insertion_point = display_map
11758 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11759 .0;
11760
11761 // Don't move lines across excerpt boundaries
11762 if buffer
11763 .excerpt_containing(range_to_move.start..insertion_point)
11764 .is_some()
11765 {
11766 let mut text = String::from("\n");
11767 text.extend(buffer.text_for_range(range_to_move.clone()));
11768 text.pop(); // Drop trailing newline
11769 edits.push((
11770 buffer.anchor_after(range_to_move.start)
11771 ..buffer.anchor_before(range_to_move.end),
11772 String::new(),
11773 ));
11774 let insertion_anchor = buffer.anchor_after(insertion_point);
11775 edits.push((insertion_anchor..insertion_anchor, text));
11776
11777 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11778
11779 // Move selections down
11780 new_selections.extend(contiguous_row_selections.drain(..).map(
11781 |mut selection| {
11782 selection.start.row += row_delta;
11783 selection.end.row += row_delta;
11784 selection
11785 },
11786 ));
11787
11788 // Move folds down
11789 unfold_ranges.push(range_to_move.clone());
11790 for fold in display_map.folds_in_range(
11791 buffer.anchor_before(range_to_move.start)
11792 ..buffer.anchor_after(range_to_move.end),
11793 ) {
11794 let mut start = fold.range.start.to_point(&buffer);
11795 let mut end = fold.range.end.to_point(&buffer);
11796 start.row += row_delta;
11797 end.row += row_delta;
11798 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11799 }
11800 }
11801 }
11802
11803 // If we didn't move line(s), preserve the existing selections
11804 new_selections.append(&mut contiguous_row_selections);
11805 }
11806
11807 self.transact(window, cx, |this, window, cx| {
11808 this.unfold_ranges(&unfold_ranges, true, true, cx);
11809 this.buffer.update(cx, |buffer, cx| {
11810 for (range, text) in edits {
11811 buffer.edit([(range, text)], None, cx);
11812 }
11813 });
11814 this.fold_creases(refold_creases, true, window, cx);
11815 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11816 });
11817 }
11818
11819 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11820 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11821 let text_layout_details = &self.text_layout_details(window);
11822 self.transact(window, cx, |this, window, cx| {
11823 let edits = this.change_selections(Default::default(), window, cx, |s| {
11824 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11825 s.move_with(|display_map, selection| {
11826 if !selection.is_empty() {
11827 return;
11828 }
11829
11830 let mut head = selection.head();
11831 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11832 if head.column() == display_map.line_len(head.row()) {
11833 transpose_offset = display_map
11834 .buffer_snapshot
11835 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11836 }
11837
11838 if transpose_offset == 0 {
11839 return;
11840 }
11841
11842 *head.column_mut() += 1;
11843 head = display_map.clip_point(head, Bias::Right);
11844 let goal = SelectionGoal::HorizontalPosition(
11845 display_map
11846 .x_for_display_point(head, text_layout_details)
11847 .into(),
11848 );
11849 selection.collapse_to(head, goal);
11850
11851 let transpose_start = display_map
11852 .buffer_snapshot
11853 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11854 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11855 let transpose_end = display_map
11856 .buffer_snapshot
11857 .clip_offset(transpose_offset + 1, Bias::Right);
11858 if let Some(ch) =
11859 display_map.buffer_snapshot.chars_at(transpose_start).next()
11860 {
11861 edits.push((transpose_start..transpose_offset, String::new()));
11862 edits.push((transpose_end..transpose_end, ch.to_string()));
11863 }
11864 }
11865 });
11866 edits
11867 });
11868 this.buffer
11869 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11870 let selections = this.selections.all::<usize>(cx);
11871 this.change_selections(Default::default(), window, cx, |s| {
11872 s.select(selections);
11873 });
11874 });
11875 }
11876
11877 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11878 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11879 if self.mode.is_single_line() {
11880 cx.propagate();
11881 return;
11882 }
11883
11884 self.rewrap_impl(RewrapOptions::default(), cx)
11885 }
11886
11887 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11888 let buffer = self.buffer.read(cx).snapshot(cx);
11889 let selections = self.selections.all::<Point>(cx);
11890
11891 #[derive(Clone, Debug, PartialEq)]
11892 enum CommentFormat {
11893 /// single line comment, with prefix for line
11894 Line(String),
11895 /// single line within a block comment, with prefix for line
11896 BlockLine(String),
11897 /// a single line of a block comment that includes the initial delimiter
11898 BlockCommentWithStart(BlockCommentConfig),
11899 /// a single line of a block comment that includes the ending delimiter
11900 BlockCommentWithEnd(BlockCommentConfig),
11901 }
11902
11903 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11904 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11905 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11906 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11907 .peekable();
11908
11909 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11910 row
11911 } else {
11912 return Vec::new();
11913 };
11914
11915 let language_settings = buffer.language_settings_at(selection.head(), cx);
11916 let language_scope = buffer.language_scope_at(selection.head());
11917
11918 let indent_and_prefix_for_row =
11919 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
11920 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11921 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
11922 &language_scope
11923 {
11924 let indent_end = Point::new(row, indent.len);
11925 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11926 let line_text_after_indent = buffer
11927 .text_for_range(indent_end..line_end)
11928 .collect::<String>();
11929
11930 let is_within_comment_override = buffer
11931 .language_scope_at(indent_end)
11932 .is_some_and(|scope| scope.override_name() == Some("comment"));
11933 let comment_delimiters = if is_within_comment_override {
11934 // we are within a comment syntax node, but we don't
11935 // yet know what kind of comment: block, doc or line
11936 match (
11937 language_scope.documentation_comment(),
11938 language_scope.block_comment(),
11939 ) {
11940 (Some(config), _) | (_, Some(config))
11941 if buffer.contains_str_at(indent_end, &config.start) =>
11942 {
11943 Some(CommentFormat::BlockCommentWithStart(config.clone()))
11944 }
11945 (Some(config), _) | (_, Some(config))
11946 if line_text_after_indent.ends_with(config.end.as_ref()) =>
11947 {
11948 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
11949 }
11950 (Some(config), _) | (_, Some(config))
11951 if buffer.contains_str_at(indent_end, &config.prefix) =>
11952 {
11953 Some(CommentFormat::BlockLine(config.prefix.to_string()))
11954 }
11955 (_, _) => language_scope
11956 .line_comment_prefixes()
11957 .iter()
11958 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11959 .map(|prefix| CommentFormat::Line(prefix.to_string())),
11960 }
11961 } else {
11962 // we not in an overridden comment node, but we may
11963 // be within a non-overridden line comment node
11964 language_scope
11965 .line_comment_prefixes()
11966 .iter()
11967 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11968 .map(|prefix| CommentFormat::Line(prefix.to_string()))
11969 };
11970
11971 let rewrap_prefix = language_scope
11972 .rewrap_prefixes()
11973 .iter()
11974 .find_map(|prefix_regex| {
11975 prefix_regex.find(&line_text_after_indent).map(|mat| {
11976 if mat.start() == 0 {
11977 Some(mat.as_str().to_string())
11978 } else {
11979 None
11980 }
11981 })
11982 })
11983 .flatten();
11984 (comment_delimiters, rewrap_prefix)
11985 } else {
11986 (None, None)
11987 };
11988 (indent, comment_prefix, rewrap_prefix)
11989 };
11990
11991 let mut ranges = Vec::new();
11992 let from_empty_selection = selection.is_empty();
11993
11994 let mut current_range_start = first_row;
11995 let mut prev_row = first_row;
11996 let (
11997 mut current_range_indent,
11998 mut current_range_comment_delimiters,
11999 mut current_range_rewrap_prefix,
12000 ) = indent_and_prefix_for_row(first_row);
12001
12002 for row in non_blank_rows_iter.skip(1) {
12003 let has_paragraph_break = row > prev_row + 1;
12004
12005 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12006 indent_and_prefix_for_row(row);
12007
12008 let has_indent_change = row_indent != current_range_indent;
12009 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12010
12011 let has_boundary_change = has_comment_change
12012 || row_rewrap_prefix.is_some()
12013 || (has_indent_change && current_range_comment_delimiters.is_some());
12014
12015 if has_paragraph_break || has_boundary_change {
12016 ranges.push((
12017 language_settings.clone(),
12018 Point::new(current_range_start, 0)
12019 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12020 current_range_indent,
12021 current_range_comment_delimiters.clone(),
12022 current_range_rewrap_prefix.clone(),
12023 from_empty_selection,
12024 ));
12025 current_range_start = row;
12026 current_range_indent = row_indent;
12027 current_range_comment_delimiters = row_comment_delimiters;
12028 current_range_rewrap_prefix = row_rewrap_prefix;
12029 }
12030 prev_row = row;
12031 }
12032
12033 ranges.push((
12034 language_settings.clone(),
12035 Point::new(current_range_start, 0)
12036 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12037 current_range_indent,
12038 current_range_comment_delimiters,
12039 current_range_rewrap_prefix,
12040 from_empty_selection,
12041 ));
12042
12043 ranges
12044 });
12045
12046 let mut edits = Vec::new();
12047 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12048
12049 for (
12050 language_settings,
12051 wrap_range,
12052 mut indent_size,
12053 comment_prefix,
12054 rewrap_prefix,
12055 from_empty_selection,
12056 ) in wrap_ranges
12057 {
12058 let mut start_row = wrap_range.start.row;
12059 let mut end_row = wrap_range.end.row;
12060
12061 // Skip selections that overlap with a range that has already been rewrapped.
12062 let selection_range = start_row..end_row;
12063 if rewrapped_row_ranges
12064 .iter()
12065 .any(|range| range.overlaps(&selection_range))
12066 {
12067 continue;
12068 }
12069
12070 let tab_size = language_settings.tab_size;
12071
12072 let (line_prefix, inside_comment) = match &comment_prefix {
12073 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12074 (Some(prefix.as_str()), true)
12075 }
12076 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12077 (Some(prefix.as_ref()), true)
12078 }
12079 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12080 start: _,
12081 end: _,
12082 prefix,
12083 tab_size,
12084 })) => {
12085 indent_size.len += tab_size;
12086 (Some(prefix.as_ref()), true)
12087 }
12088 None => (None, false),
12089 };
12090 let indent_prefix = indent_size.chars().collect::<String>();
12091 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12092
12093 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12094 RewrapBehavior::InComments => inside_comment,
12095 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12096 RewrapBehavior::Anywhere => true,
12097 };
12098
12099 let should_rewrap = options.override_language_settings
12100 || allow_rewrap_based_on_language
12101 || self.hard_wrap.is_some();
12102 if !should_rewrap {
12103 continue;
12104 }
12105
12106 if from_empty_selection {
12107 'expand_upwards: while start_row > 0 {
12108 let prev_row = start_row - 1;
12109 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12110 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12111 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12112 {
12113 start_row = prev_row;
12114 } else {
12115 break 'expand_upwards;
12116 }
12117 }
12118
12119 'expand_downwards: while end_row < buffer.max_point().row {
12120 let next_row = end_row + 1;
12121 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12122 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12123 && !buffer.is_line_blank(MultiBufferRow(next_row))
12124 {
12125 end_row = next_row;
12126 } else {
12127 break 'expand_downwards;
12128 }
12129 }
12130 }
12131
12132 let start = Point::new(start_row, 0);
12133 let start_offset = start.to_offset(&buffer);
12134 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12135 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12136 let mut first_line_delimiter = None;
12137 let mut last_line_delimiter = None;
12138 let Some(lines_without_prefixes) = selection_text
12139 .lines()
12140 .enumerate()
12141 .map(|(ix, line)| {
12142 let line_trimmed = line.trim_start();
12143 if rewrap_prefix.is_some() && ix > 0 {
12144 Ok(line_trimmed)
12145 } else if let Some(
12146 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12147 start,
12148 prefix,
12149 end,
12150 tab_size,
12151 })
12152 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12153 start,
12154 prefix,
12155 end,
12156 tab_size,
12157 }),
12158 ) = &comment_prefix
12159 {
12160 let line_trimmed = line_trimmed
12161 .strip_prefix(start.as_ref())
12162 .map(|s| {
12163 let mut indent_size = indent_size;
12164 indent_size.len -= tab_size;
12165 let indent_prefix: String = indent_size.chars().collect();
12166 first_line_delimiter = Some((indent_prefix, start));
12167 s.trim_start()
12168 })
12169 .unwrap_or(line_trimmed);
12170 let line_trimmed = line_trimmed
12171 .strip_suffix(end.as_ref())
12172 .map(|s| {
12173 last_line_delimiter = Some(end);
12174 s.trim_end()
12175 })
12176 .unwrap_or(line_trimmed);
12177 let line_trimmed = line_trimmed
12178 .strip_prefix(prefix.as_ref())
12179 .unwrap_or(line_trimmed);
12180 Ok(line_trimmed)
12181 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12182 line_trimmed.strip_prefix(prefix).with_context(|| {
12183 format!("line did not start with prefix {prefix:?}: {line:?}")
12184 })
12185 } else {
12186 line_trimmed
12187 .strip_prefix(&line_prefix.trim_start())
12188 .with_context(|| {
12189 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12190 })
12191 }
12192 })
12193 .collect::<Result<Vec<_>, _>>()
12194 .log_err()
12195 else {
12196 continue;
12197 };
12198
12199 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12200 buffer
12201 .language_settings_at(Point::new(start_row, 0), cx)
12202 .preferred_line_length as usize
12203 });
12204
12205 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12206 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12207 } else {
12208 line_prefix.clone()
12209 };
12210
12211 let wrapped_text = {
12212 let mut wrapped_text = wrap_with_prefix(
12213 line_prefix,
12214 subsequent_lines_prefix,
12215 lines_without_prefixes.join("\n"),
12216 wrap_column,
12217 tab_size,
12218 options.preserve_existing_whitespace,
12219 );
12220
12221 if let Some((indent, delimiter)) = first_line_delimiter {
12222 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12223 }
12224 if let Some(last_line) = last_line_delimiter {
12225 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12226 }
12227
12228 wrapped_text
12229 };
12230
12231 // TODO: should always use char-based diff while still supporting cursor behavior that
12232 // matches vim.
12233 let mut diff_options = DiffOptions::default();
12234 if options.override_language_settings {
12235 diff_options.max_word_diff_len = 0;
12236 diff_options.max_word_diff_line_count = 0;
12237 } else {
12238 diff_options.max_word_diff_len = usize::MAX;
12239 diff_options.max_word_diff_line_count = usize::MAX;
12240 }
12241
12242 for (old_range, new_text) in
12243 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12244 {
12245 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12246 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12247 edits.push((edit_start..edit_end, new_text));
12248 }
12249
12250 rewrapped_row_ranges.push(start_row..=end_row);
12251 }
12252
12253 self.buffer
12254 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12255 }
12256
12257 pub fn cut_common(
12258 &mut self,
12259 cut_no_selection_line: bool,
12260 window: &mut Window,
12261 cx: &mut Context<Self>,
12262 ) -> ClipboardItem {
12263 let mut text = String::new();
12264 let buffer = self.buffer.read(cx).snapshot(cx);
12265 let mut selections = self.selections.all::<Point>(cx);
12266 let mut clipboard_selections = Vec::with_capacity(selections.len());
12267 {
12268 let max_point = buffer.max_point();
12269 let mut is_first = true;
12270 for selection in &mut selections {
12271 let is_entire_line =
12272 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode;
12273 if is_entire_line {
12274 selection.start = Point::new(selection.start.row, 0);
12275 if !selection.is_empty() && selection.end.column == 0 {
12276 selection.end = cmp::min(max_point, selection.end);
12277 } else {
12278 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12279 }
12280 selection.goal = SelectionGoal::None;
12281 }
12282 if is_first {
12283 is_first = false;
12284 } else {
12285 text += "\n";
12286 }
12287 let mut len = 0;
12288 for chunk in buffer.text_for_range(selection.start..selection.end) {
12289 text.push_str(chunk);
12290 len += chunk.len();
12291 }
12292 clipboard_selections.push(ClipboardSelection {
12293 len,
12294 is_entire_line,
12295 first_line_indent: buffer
12296 .indent_size_for_line(MultiBufferRow(selection.start.row))
12297 .len,
12298 });
12299 }
12300 }
12301
12302 self.transact(window, cx, |this, window, cx| {
12303 this.change_selections(Default::default(), window, cx, |s| {
12304 s.select(selections);
12305 });
12306 this.insert("", window, cx);
12307 });
12308 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12309 }
12310
12311 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12312 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12313 let item = self.cut_common(true, window, cx);
12314 cx.write_to_clipboard(item);
12315 }
12316
12317 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12318 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12319 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12320 s.move_with(|snapshot, sel| {
12321 if sel.is_empty() {
12322 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12323 }
12324 if sel.is_empty() {
12325 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12326 }
12327 });
12328 });
12329 let item = self.cut_common(true, window, cx);
12330 cx.set_global(KillRing(item))
12331 }
12332
12333 pub fn kill_ring_yank(
12334 &mut self,
12335 _: &KillRingYank,
12336 window: &mut Window,
12337 cx: &mut Context<Self>,
12338 ) {
12339 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12340 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12341 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12342 (kill_ring.text().to_string(), kill_ring.metadata_json())
12343 } else {
12344 return;
12345 }
12346 } else {
12347 return;
12348 };
12349 self.do_paste(&text, metadata, false, window, cx);
12350 }
12351
12352 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12353 self.do_copy(true, cx);
12354 }
12355
12356 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12357 self.do_copy(false, cx);
12358 }
12359
12360 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12361 let selections = self.selections.all::<Point>(cx);
12362 let buffer = self.buffer.read(cx).read(cx);
12363 let mut text = String::new();
12364
12365 let mut clipboard_selections = Vec::with_capacity(selections.len());
12366 {
12367 let max_point = buffer.max_point();
12368 let mut is_first = true;
12369 for selection in &selections {
12370 let mut start = selection.start;
12371 let mut end = selection.end;
12372 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12373 if is_entire_line {
12374 start = Point::new(start.row, 0);
12375 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12376 }
12377
12378 let mut trimmed_selections = Vec::new();
12379 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12380 let row = MultiBufferRow(start.row);
12381 let first_indent = buffer.indent_size_for_line(row);
12382 if first_indent.len == 0 || start.column > first_indent.len {
12383 trimmed_selections.push(start..end);
12384 } else {
12385 trimmed_selections.push(
12386 Point::new(row.0, first_indent.len)
12387 ..Point::new(row.0, buffer.line_len(row)),
12388 );
12389 for row in start.row + 1..=end.row {
12390 let mut line_len = buffer.line_len(MultiBufferRow(row));
12391 if row == end.row {
12392 line_len = end.column;
12393 }
12394 if line_len == 0 {
12395 trimmed_selections
12396 .push(Point::new(row, 0)..Point::new(row, line_len));
12397 continue;
12398 }
12399 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12400 if row_indent_size.len >= first_indent.len {
12401 trimmed_selections.push(
12402 Point::new(row, first_indent.len)..Point::new(row, line_len),
12403 );
12404 } else {
12405 trimmed_selections.clear();
12406 trimmed_selections.push(start..end);
12407 break;
12408 }
12409 }
12410 }
12411 } else {
12412 trimmed_selections.push(start..end);
12413 }
12414
12415 for trimmed_range in trimmed_selections {
12416 if is_first {
12417 is_first = false;
12418 } else {
12419 text += "\n";
12420 }
12421 let mut len = 0;
12422 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12423 text.push_str(chunk);
12424 len += chunk.len();
12425 }
12426 clipboard_selections.push(ClipboardSelection {
12427 len,
12428 is_entire_line,
12429 first_line_indent: buffer
12430 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12431 .len,
12432 });
12433 }
12434 }
12435 }
12436
12437 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12438 text,
12439 clipboard_selections,
12440 ));
12441 }
12442
12443 pub fn do_paste(
12444 &mut self,
12445 text: &String,
12446 clipboard_selections: Option<Vec<ClipboardSelection>>,
12447 handle_entire_lines: bool,
12448 window: &mut Window,
12449 cx: &mut Context<Self>,
12450 ) {
12451 if self.read_only(cx) {
12452 return;
12453 }
12454
12455 let clipboard_text = Cow::Borrowed(text);
12456
12457 self.transact(window, cx, |this, window, cx| {
12458 let had_active_edit_prediction = this.has_active_edit_prediction();
12459
12460 if let Some(mut clipboard_selections) = clipboard_selections {
12461 let old_selections = this.selections.all::<usize>(cx);
12462 let all_selections_were_entire_line =
12463 clipboard_selections.iter().all(|s| s.is_entire_line);
12464 let first_selection_indent_column =
12465 clipboard_selections.first().map(|s| s.first_line_indent);
12466 if clipboard_selections.len() != old_selections.len() {
12467 clipboard_selections.drain(..);
12468 }
12469 let cursor_offset = this.selections.last::<usize>(cx).head();
12470 let mut auto_indent_on_paste = true;
12471
12472 this.buffer.update(cx, |buffer, cx| {
12473 let snapshot = buffer.read(cx);
12474 auto_indent_on_paste = snapshot
12475 .language_settings_at(cursor_offset, cx)
12476 .auto_indent_on_paste;
12477
12478 let mut start_offset = 0;
12479 let mut edits = Vec::new();
12480 let mut original_indent_columns = Vec::new();
12481 for (ix, selection) in old_selections.iter().enumerate() {
12482 let to_insert;
12483 let entire_line;
12484 let original_indent_column;
12485 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12486 let end_offset = start_offset + clipboard_selection.len;
12487 to_insert = &clipboard_text[start_offset..end_offset];
12488 entire_line = clipboard_selection.is_entire_line;
12489 start_offset = end_offset + 1;
12490 original_indent_column = Some(clipboard_selection.first_line_indent);
12491 } else {
12492 to_insert = clipboard_text.as_str();
12493 entire_line = all_selections_were_entire_line;
12494 original_indent_column = first_selection_indent_column
12495 }
12496
12497 // If the corresponding selection was empty when this slice of the
12498 // clipboard text was written, then the entire line containing the
12499 // selection was copied. If this selection is also currently empty,
12500 // then paste the line before the current line of the buffer.
12501 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12502 let column = selection.start.to_point(&snapshot).column as usize;
12503 let line_start = selection.start - column;
12504 line_start..line_start
12505 } else {
12506 selection.range()
12507 };
12508
12509 edits.push((range, to_insert));
12510 original_indent_columns.push(original_indent_column);
12511 }
12512 drop(snapshot);
12513
12514 buffer.edit(
12515 edits,
12516 if auto_indent_on_paste {
12517 Some(AutoindentMode::Block {
12518 original_indent_columns,
12519 })
12520 } else {
12521 None
12522 },
12523 cx,
12524 );
12525 });
12526
12527 let selections = this.selections.all::<usize>(cx);
12528 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12529 } else {
12530 this.insert(&clipboard_text, window, cx);
12531 }
12532
12533 let trigger_in_words =
12534 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12535
12536 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12537 });
12538 }
12539
12540 pub fn diff_clipboard_with_selection(
12541 &mut self,
12542 _: &DiffClipboardWithSelection,
12543 window: &mut Window,
12544 cx: &mut Context<Self>,
12545 ) {
12546 let selections = self.selections.all::<usize>(cx);
12547
12548 if selections.is_empty() {
12549 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12550 return;
12551 };
12552
12553 let clipboard_text = match cx.read_from_clipboard() {
12554 Some(item) => match item.entries().first() {
12555 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12556 _ => None,
12557 },
12558 None => None,
12559 };
12560
12561 let Some(clipboard_text) = clipboard_text else {
12562 log::warn!("Clipboard doesn't contain text.");
12563 return;
12564 };
12565
12566 window.dispatch_action(
12567 Box::new(DiffClipboardWithSelectionData {
12568 clipboard_text,
12569 editor: cx.entity(),
12570 }),
12571 cx,
12572 );
12573 }
12574
12575 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12576 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12577 if let Some(item) = cx.read_from_clipboard() {
12578 let entries = item.entries();
12579
12580 match entries.first() {
12581 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12582 // of all the pasted entries.
12583 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12584 .do_paste(
12585 clipboard_string.text(),
12586 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12587 true,
12588 window,
12589 cx,
12590 ),
12591 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12592 }
12593 }
12594 }
12595
12596 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12597 if self.read_only(cx) {
12598 return;
12599 }
12600
12601 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12602
12603 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12604 if let Some((selections, _)) =
12605 self.selection_history.transaction(transaction_id).cloned()
12606 {
12607 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12608 s.select_anchors(selections.to_vec());
12609 });
12610 } else {
12611 log::error!(
12612 "No entry in selection_history found for undo. \
12613 This may correspond to a bug where undo does not update the selection. \
12614 If this is occurring, please add details to \
12615 https://github.com/zed-industries/zed/issues/22692"
12616 );
12617 }
12618 self.request_autoscroll(Autoscroll::fit(), cx);
12619 self.unmark_text(window, cx);
12620 self.refresh_edit_prediction(true, false, window, cx);
12621 cx.emit(EditorEvent::Edited { transaction_id });
12622 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12623 }
12624 }
12625
12626 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12627 if self.read_only(cx) {
12628 return;
12629 }
12630
12631 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12632
12633 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12634 if let Some((_, Some(selections))) =
12635 self.selection_history.transaction(transaction_id).cloned()
12636 {
12637 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12638 s.select_anchors(selections.to_vec());
12639 });
12640 } else {
12641 log::error!(
12642 "No entry in selection_history found for redo. \
12643 This may correspond to a bug where undo does not update the selection. \
12644 If this is occurring, please add details to \
12645 https://github.com/zed-industries/zed/issues/22692"
12646 );
12647 }
12648 self.request_autoscroll(Autoscroll::fit(), cx);
12649 self.unmark_text(window, cx);
12650 self.refresh_edit_prediction(true, false, window, cx);
12651 cx.emit(EditorEvent::Edited { transaction_id });
12652 }
12653 }
12654
12655 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12656 self.buffer
12657 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12658 }
12659
12660 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12661 self.buffer
12662 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12663 }
12664
12665 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12666 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12667 self.change_selections(Default::default(), window, cx, |s| {
12668 s.move_with(|map, selection| {
12669 let cursor = if selection.is_empty() {
12670 movement::left(map, selection.start)
12671 } else {
12672 selection.start
12673 };
12674 selection.collapse_to(cursor, SelectionGoal::None);
12675 });
12676 })
12677 }
12678
12679 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12680 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12681 self.change_selections(Default::default(), window, cx, |s| {
12682 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12683 })
12684 }
12685
12686 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12687 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12688 self.change_selections(Default::default(), window, cx, |s| {
12689 s.move_with(|map, selection| {
12690 let cursor = if selection.is_empty() {
12691 movement::right(map, selection.end)
12692 } else {
12693 selection.end
12694 };
12695 selection.collapse_to(cursor, SelectionGoal::None)
12696 });
12697 })
12698 }
12699
12700 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12701 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12702 self.change_selections(Default::default(), window, cx, |s| {
12703 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12704 })
12705 }
12706
12707 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12708 if self.take_rename(true, window, cx).is_some() {
12709 return;
12710 }
12711
12712 if self.mode.is_single_line() {
12713 cx.propagate();
12714 return;
12715 }
12716
12717 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12718
12719 let text_layout_details = &self.text_layout_details(window);
12720 let selection_count = self.selections.count();
12721 let first_selection = self.selections.first_anchor();
12722
12723 self.change_selections(Default::default(), window, cx, |s| {
12724 s.move_with(|map, selection| {
12725 if !selection.is_empty() {
12726 selection.goal = SelectionGoal::None;
12727 }
12728 let (cursor, goal) = movement::up(
12729 map,
12730 selection.start,
12731 selection.goal,
12732 false,
12733 text_layout_details,
12734 );
12735 selection.collapse_to(cursor, goal);
12736 });
12737 });
12738
12739 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12740 {
12741 cx.propagate();
12742 }
12743 }
12744
12745 pub fn move_up_by_lines(
12746 &mut self,
12747 action: &MoveUpByLines,
12748 window: &mut Window,
12749 cx: &mut Context<Self>,
12750 ) {
12751 if self.take_rename(true, window, cx).is_some() {
12752 return;
12753 }
12754
12755 if self.mode.is_single_line() {
12756 cx.propagate();
12757 return;
12758 }
12759
12760 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12761
12762 let text_layout_details = &self.text_layout_details(window);
12763
12764 self.change_selections(Default::default(), window, cx, |s| {
12765 s.move_with(|map, selection| {
12766 if !selection.is_empty() {
12767 selection.goal = SelectionGoal::None;
12768 }
12769 let (cursor, goal) = movement::up_by_rows(
12770 map,
12771 selection.start,
12772 action.lines,
12773 selection.goal,
12774 false,
12775 text_layout_details,
12776 );
12777 selection.collapse_to(cursor, goal);
12778 });
12779 })
12780 }
12781
12782 pub fn move_down_by_lines(
12783 &mut self,
12784 action: &MoveDownByLines,
12785 window: &mut Window,
12786 cx: &mut Context<Self>,
12787 ) {
12788 if self.take_rename(true, window, cx).is_some() {
12789 return;
12790 }
12791
12792 if self.mode.is_single_line() {
12793 cx.propagate();
12794 return;
12795 }
12796
12797 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12798
12799 let text_layout_details = &self.text_layout_details(window);
12800
12801 self.change_selections(Default::default(), window, cx, |s| {
12802 s.move_with(|map, selection| {
12803 if !selection.is_empty() {
12804 selection.goal = SelectionGoal::None;
12805 }
12806 let (cursor, goal) = movement::down_by_rows(
12807 map,
12808 selection.start,
12809 action.lines,
12810 selection.goal,
12811 false,
12812 text_layout_details,
12813 );
12814 selection.collapse_to(cursor, goal);
12815 });
12816 })
12817 }
12818
12819 pub fn select_down_by_lines(
12820 &mut self,
12821 action: &SelectDownByLines,
12822 window: &mut Window,
12823 cx: &mut Context<Self>,
12824 ) {
12825 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12826 let text_layout_details = &self.text_layout_details(window);
12827 self.change_selections(Default::default(), window, cx, |s| {
12828 s.move_heads_with(|map, head, goal| {
12829 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12830 })
12831 })
12832 }
12833
12834 pub fn select_up_by_lines(
12835 &mut self,
12836 action: &SelectUpByLines,
12837 window: &mut Window,
12838 cx: &mut Context<Self>,
12839 ) {
12840 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12841 let text_layout_details = &self.text_layout_details(window);
12842 self.change_selections(Default::default(), window, cx, |s| {
12843 s.move_heads_with(|map, head, goal| {
12844 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12845 })
12846 })
12847 }
12848
12849 pub fn select_page_up(
12850 &mut self,
12851 _: &SelectPageUp,
12852 window: &mut Window,
12853 cx: &mut Context<Self>,
12854 ) {
12855 let Some(row_count) = self.visible_row_count() else {
12856 return;
12857 };
12858
12859 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12860
12861 let text_layout_details = &self.text_layout_details(window);
12862
12863 self.change_selections(Default::default(), window, cx, |s| {
12864 s.move_heads_with(|map, head, goal| {
12865 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12866 })
12867 })
12868 }
12869
12870 pub fn move_page_up(
12871 &mut self,
12872 action: &MovePageUp,
12873 window: &mut Window,
12874 cx: &mut Context<Self>,
12875 ) {
12876 if self.take_rename(true, window, cx).is_some() {
12877 return;
12878 }
12879
12880 if self
12881 .context_menu
12882 .borrow_mut()
12883 .as_mut()
12884 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12885 .unwrap_or(false)
12886 {
12887 return;
12888 }
12889
12890 if matches!(self.mode, EditorMode::SingleLine) {
12891 cx.propagate();
12892 return;
12893 }
12894
12895 let Some(row_count) = self.visible_row_count() else {
12896 return;
12897 };
12898
12899 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12900
12901 let effects = if action.center_cursor {
12902 SelectionEffects::scroll(Autoscroll::center())
12903 } else {
12904 SelectionEffects::default()
12905 };
12906
12907 let text_layout_details = &self.text_layout_details(window);
12908
12909 self.change_selections(effects, window, cx, |s| {
12910 s.move_with(|map, selection| {
12911 if !selection.is_empty() {
12912 selection.goal = SelectionGoal::None;
12913 }
12914 let (cursor, goal) = movement::up_by_rows(
12915 map,
12916 selection.end,
12917 row_count,
12918 selection.goal,
12919 false,
12920 text_layout_details,
12921 );
12922 selection.collapse_to(cursor, goal);
12923 });
12924 });
12925 }
12926
12927 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12928 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12929 let text_layout_details = &self.text_layout_details(window);
12930 self.change_selections(Default::default(), window, cx, |s| {
12931 s.move_heads_with(|map, head, goal| {
12932 movement::up(map, head, goal, false, text_layout_details)
12933 })
12934 })
12935 }
12936
12937 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12938 self.take_rename(true, window, cx);
12939
12940 if self.mode.is_single_line() {
12941 cx.propagate();
12942 return;
12943 }
12944
12945 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12946
12947 let text_layout_details = &self.text_layout_details(window);
12948 let selection_count = self.selections.count();
12949 let first_selection = self.selections.first_anchor();
12950
12951 self.change_selections(Default::default(), window, cx, |s| {
12952 s.move_with(|map, selection| {
12953 if !selection.is_empty() {
12954 selection.goal = SelectionGoal::None;
12955 }
12956 let (cursor, goal) = movement::down(
12957 map,
12958 selection.end,
12959 selection.goal,
12960 false,
12961 text_layout_details,
12962 );
12963 selection.collapse_to(cursor, goal);
12964 });
12965 });
12966
12967 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12968 {
12969 cx.propagate();
12970 }
12971 }
12972
12973 pub fn select_page_down(
12974 &mut self,
12975 _: &SelectPageDown,
12976 window: &mut Window,
12977 cx: &mut Context<Self>,
12978 ) {
12979 let Some(row_count) = self.visible_row_count() else {
12980 return;
12981 };
12982
12983 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12984
12985 let text_layout_details = &self.text_layout_details(window);
12986
12987 self.change_selections(Default::default(), window, cx, |s| {
12988 s.move_heads_with(|map, head, goal| {
12989 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12990 })
12991 })
12992 }
12993
12994 pub fn move_page_down(
12995 &mut self,
12996 action: &MovePageDown,
12997 window: &mut Window,
12998 cx: &mut Context<Self>,
12999 ) {
13000 if self.take_rename(true, window, cx).is_some() {
13001 return;
13002 }
13003
13004 if self
13005 .context_menu
13006 .borrow_mut()
13007 .as_mut()
13008 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13009 .unwrap_or(false)
13010 {
13011 return;
13012 }
13013
13014 if matches!(self.mode, EditorMode::SingleLine) {
13015 cx.propagate();
13016 return;
13017 }
13018
13019 let Some(row_count) = self.visible_row_count() else {
13020 return;
13021 };
13022
13023 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13024
13025 let effects = if action.center_cursor {
13026 SelectionEffects::scroll(Autoscroll::center())
13027 } else {
13028 SelectionEffects::default()
13029 };
13030
13031 let text_layout_details = &self.text_layout_details(window);
13032 self.change_selections(effects, window, cx, |s| {
13033 s.move_with(|map, selection| {
13034 if !selection.is_empty() {
13035 selection.goal = SelectionGoal::None;
13036 }
13037 let (cursor, goal) = movement::down_by_rows(
13038 map,
13039 selection.end,
13040 row_count,
13041 selection.goal,
13042 false,
13043 text_layout_details,
13044 );
13045 selection.collapse_to(cursor, goal);
13046 });
13047 });
13048 }
13049
13050 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13051 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13052 let text_layout_details = &self.text_layout_details(window);
13053 self.change_selections(Default::default(), window, cx, |s| {
13054 s.move_heads_with(|map, head, goal| {
13055 movement::down(map, head, goal, false, text_layout_details)
13056 })
13057 });
13058 }
13059
13060 pub fn context_menu_first(
13061 &mut self,
13062 _: &ContextMenuFirst,
13063 window: &mut Window,
13064 cx: &mut Context<Self>,
13065 ) {
13066 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13067 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13068 }
13069 }
13070
13071 pub fn context_menu_prev(
13072 &mut self,
13073 _: &ContextMenuPrevious,
13074 window: &mut Window,
13075 cx: &mut Context<Self>,
13076 ) {
13077 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13078 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13079 }
13080 }
13081
13082 pub fn context_menu_next(
13083 &mut self,
13084 _: &ContextMenuNext,
13085 window: &mut Window,
13086 cx: &mut Context<Self>,
13087 ) {
13088 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13089 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13090 }
13091 }
13092
13093 pub fn context_menu_last(
13094 &mut self,
13095 _: &ContextMenuLast,
13096 window: &mut Window,
13097 cx: &mut Context<Self>,
13098 ) {
13099 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13100 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13101 }
13102 }
13103
13104 pub fn signature_help_prev(
13105 &mut self,
13106 _: &SignatureHelpPrevious,
13107 _: &mut Window,
13108 cx: &mut Context<Self>,
13109 ) {
13110 if let Some(popover) = self.signature_help_state.popover_mut() {
13111 if popover.current_signature == 0 {
13112 popover.current_signature = popover.signatures.len() - 1;
13113 } else {
13114 popover.current_signature -= 1;
13115 }
13116 cx.notify();
13117 }
13118 }
13119
13120 pub fn signature_help_next(
13121 &mut self,
13122 _: &SignatureHelpNext,
13123 _: &mut Window,
13124 cx: &mut Context<Self>,
13125 ) {
13126 if let Some(popover) = self.signature_help_state.popover_mut() {
13127 if popover.current_signature + 1 == popover.signatures.len() {
13128 popover.current_signature = 0;
13129 } else {
13130 popover.current_signature += 1;
13131 }
13132 cx.notify();
13133 }
13134 }
13135
13136 pub fn move_to_previous_word_start(
13137 &mut self,
13138 _: &MoveToPreviousWordStart,
13139 window: &mut Window,
13140 cx: &mut Context<Self>,
13141 ) {
13142 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13143 self.change_selections(Default::default(), window, cx, |s| {
13144 s.move_cursors_with(|map, head, _| {
13145 (
13146 movement::previous_word_start(map, head),
13147 SelectionGoal::None,
13148 )
13149 });
13150 })
13151 }
13152
13153 pub fn move_to_previous_subword_start(
13154 &mut self,
13155 _: &MoveToPreviousSubwordStart,
13156 window: &mut Window,
13157 cx: &mut Context<Self>,
13158 ) {
13159 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13160 self.change_selections(Default::default(), window, cx, |s| {
13161 s.move_cursors_with(|map, head, _| {
13162 (
13163 movement::previous_subword_start(map, head),
13164 SelectionGoal::None,
13165 )
13166 });
13167 })
13168 }
13169
13170 pub fn select_to_previous_word_start(
13171 &mut self,
13172 _: &SelectToPreviousWordStart,
13173 window: &mut Window,
13174 cx: &mut Context<Self>,
13175 ) {
13176 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13177 self.change_selections(Default::default(), window, cx, |s| {
13178 s.move_heads_with(|map, head, _| {
13179 (
13180 movement::previous_word_start(map, head),
13181 SelectionGoal::None,
13182 )
13183 });
13184 })
13185 }
13186
13187 pub fn select_to_previous_subword_start(
13188 &mut self,
13189 _: &SelectToPreviousSubwordStart,
13190 window: &mut Window,
13191 cx: &mut Context<Self>,
13192 ) {
13193 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13194 self.change_selections(Default::default(), window, cx, |s| {
13195 s.move_heads_with(|map, head, _| {
13196 (
13197 movement::previous_subword_start(map, head),
13198 SelectionGoal::None,
13199 )
13200 });
13201 })
13202 }
13203
13204 pub fn delete_to_previous_word_start(
13205 &mut self,
13206 action: &DeleteToPreviousWordStart,
13207 window: &mut Window,
13208 cx: &mut Context<Self>,
13209 ) {
13210 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13211 self.transact(window, cx, |this, window, cx| {
13212 this.select_autoclose_pair(window, cx);
13213 this.change_selections(Default::default(), window, cx, |s| {
13214 s.move_with(|map, selection| {
13215 if selection.is_empty() {
13216 let mut cursor = if action.ignore_newlines {
13217 movement::previous_word_start(map, selection.head())
13218 } else {
13219 movement::previous_word_start_or_newline(map, selection.head())
13220 };
13221 cursor = movement::adjust_greedy_deletion(
13222 map,
13223 selection.head(),
13224 cursor,
13225 action.ignore_brackets,
13226 );
13227 selection.set_head(cursor, SelectionGoal::None);
13228 }
13229 });
13230 });
13231 this.insert("", window, cx);
13232 });
13233 }
13234
13235 pub fn delete_to_previous_subword_start(
13236 &mut self,
13237 _: &DeleteToPreviousSubwordStart,
13238 window: &mut Window,
13239 cx: &mut Context<Self>,
13240 ) {
13241 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13242 self.transact(window, cx, |this, window, cx| {
13243 this.select_autoclose_pair(window, cx);
13244 this.change_selections(Default::default(), window, cx, |s| {
13245 s.move_with(|map, selection| {
13246 if selection.is_empty() {
13247 let mut cursor = movement::previous_subword_start(map, selection.head());
13248 cursor =
13249 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13250 selection.set_head(cursor, SelectionGoal::None);
13251 }
13252 });
13253 });
13254 this.insert("", window, cx);
13255 });
13256 }
13257
13258 pub fn move_to_next_word_end(
13259 &mut self,
13260 _: &MoveToNextWordEnd,
13261 window: &mut Window,
13262 cx: &mut Context<Self>,
13263 ) {
13264 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13265 self.change_selections(Default::default(), window, cx, |s| {
13266 s.move_cursors_with(|map, head, _| {
13267 (movement::next_word_end(map, head), SelectionGoal::None)
13268 });
13269 })
13270 }
13271
13272 pub fn move_to_next_subword_end(
13273 &mut self,
13274 _: &MoveToNextSubwordEnd,
13275 window: &mut Window,
13276 cx: &mut Context<Self>,
13277 ) {
13278 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13279 self.change_selections(Default::default(), window, cx, |s| {
13280 s.move_cursors_with(|map, head, _| {
13281 (movement::next_subword_end(map, head), SelectionGoal::None)
13282 });
13283 })
13284 }
13285
13286 pub fn select_to_next_word_end(
13287 &mut self,
13288 _: &SelectToNextWordEnd,
13289 window: &mut Window,
13290 cx: &mut Context<Self>,
13291 ) {
13292 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13293 self.change_selections(Default::default(), window, cx, |s| {
13294 s.move_heads_with(|map, head, _| {
13295 (movement::next_word_end(map, head), SelectionGoal::None)
13296 });
13297 })
13298 }
13299
13300 pub fn select_to_next_subword_end(
13301 &mut self,
13302 _: &SelectToNextSubwordEnd,
13303 window: &mut Window,
13304 cx: &mut Context<Self>,
13305 ) {
13306 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13307 self.change_selections(Default::default(), window, cx, |s| {
13308 s.move_heads_with(|map, head, _| {
13309 (movement::next_subword_end(map, head), SelectionGoal::None)
13310 });
13311 })
13312 }
13313
13314 pub fn delete_to_next_word_end(
13315 &mut self,
13316 action: &DeleteToNextWordEnd,
13317 window: &mut Window,
13318 cx: &mut Context<Self>,
13319 ) {
13320 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13321 self.transact(window, cx, |this, window, cx| {
13322 this.change_selections(Default::default(), window, cx, |s| {
13323 s.move_with(|map, selection| {
13324 if selection.is_empty() {
13325 let mut cursor = if action.ignore_newlines {
13326 movement::next_word_end(map, selection.head())
13327 } else {
13328 movement::next_word_end_or_newline(map, selection.head())
13329 };
13330 cursor = movement::adjust_greedy_deletion(
13331 map,
13332 selection.head(),
13333 cursor,
13334 action.ignore_brackets,
13335 );
13336 selection.set_head(cursor, SelectionGoal::None);
13337 }
13338 });
13339 });
13340 this.insert("", window, cx);
13341 });
13342 }
13343
13344 pub fn delete_to_next_subword_end(
13345 &mut self,
13346 _: &DeleteToNextSubwordEnd,
13347 window: &mut Window,
13348 cx: &mut Context<Self>,
13349 ) {
13350 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13351 self.transact(window, cx, |this, window, cx| {
13352 this.change_selections(Default::default(), window, cx, |s| {
13353 s.move_with(|map, selection| {
13354 if selection.is_empty() {
13355 let mut cursor = movement::next_subword_end(map, selection.head());
13356 cursor =
13357 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13358 selection.set_head(cursor, SelectionGoal::None);
13359 }
13360 });
13361 });
13362 this.insert("", window, cx);
13363 });
13364 }
13365
13366 pub fn move_to_beginning_of_line(
13367 &mut self,
13368 action: &MoveToBeginningOfLine,
13369 window: &mut Window,
13370 cx: &mut Context<Self>,
13371 ) {
13372 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13373 self.change_selections(Default::default(), window, cx, |s| {
13374 s.move_cursors_with(|map, head, _| {
13375 (
13376 movement::indented_line_beginning(
13377 map,
13378 head,
13379 action.stop_at_soft_wraps,
13380 action.stop_at_indent,
13381 ),
13382 SelectionGoal::None,
13383 )
13384 });
13385 })
13386 }
13387
13388 pub fn select_to_beginning_of_line(
13389 &mut self,
13390 action: &SelectToBeginningOfLine,
13391 window: &mut Window,
13392 cx: &mut Context<Self>,
13393 ) {
13394 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13395 self.change_selections(Default::default(), window, cx, |s| {
13396 s.move_heads_with(|map, head, _| {
13397 (
13398 movement::indented_line_beginning(
13399 map,
13400 head,
13401 action.stop_at_soft_wraps,
13402 action.stop_at_indent,
13403 ),
13404 SelectionGoal::None,
13405 )
13406 });
13407 });
13408 }
13409
13410 pub fn delete_to_beginning_of_line(
13411 &mut self,
13412 action: &DeleteToBeginningOfLine,
13413 window: &mut Window,
13414 cx: &mut Context<Self>,
13415 ) {
13416 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13417 self.transact(window, cx, |this, window, cx| {
13418 this.change_selections(Default::default(), window, cx, |s| {
13419 s.move_with(|_, selection| {
13420 selection.reversed = true;
13421 });
13422 });
13423
13424 this.select_to_beginning_of_line(
13425 &SelectToBeginningOfLine {
13426 stop_at_soft_wraps: false,
13427 stop_at_indent: action.stop_at_indent,
13428 },
13429 window,
13430 cx,
13431 );
13432 this.backspace(&Backspace, window, cx);
13433 });
13434 }
13435
13436 pub fn move_to_end_of_line(
13437 &mut self,
13438 action: &MoveToEndOfLine,
13439 window: &mut Window,
13440 cx: &mut Context<Self>,
13441 ) {
13442 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13443 self.change_selections(Default::default(), window, cx, |s| {
13444 s.move_cursors_with(|map, head, _| {
13445 (
13446 movement::line_end(map, head, action.stop_at_soft_wraps),
13447 SelectionGoal::None,
13448 )
13449 });
13450 })
13451 }
13452
13453 pub fn select_to_end_of_line(
13454 &mut self,
13455 action: &SelectToEndOfLine,
13456 window: &mut Window,
13457 cx: &mut Context<Self>,
13458 ) {
13459 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13460 self.change_selections(Default::default(), window, cx, |s| {
13461 s.move_heads_with(|map, head, _| {
13462 (
13463 movement::line_end(map, head, action.stop_at_soft_wraps),
13464 SelectionGoal::None,
13465 )
13466 });
13467 })
13468 }
13469
13470 pub fn delete_to_end_of_line(
13471 &mut self,
13472 _: &DeleteToEndOfLine,
13473 window: &mut Window,
13474 cx: &mut Context<Self>,
13475 ) {
13476 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13477 self.transact(window, cx, |this, window, cx| {
13478 this.select_to_end_of_line(
13479 &SelectToEndOfLine {
13480 stop_at_soft_wraps: false,
13481 },
13482 window,
13483 cx,
13484 );
13485 this.delete(&Delete, window, cx);
13486 });
13487 }
13488
13489 pub fn cut_to_end_of_line(
13490 &mut self,
13491 action: &CutToEndOfLine,
13492 window: &mut Window,
13493 cx: &mut Context<Self>,
13494 ) {
13495 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13496 self.transact(window, cx, |this, window, cx| {
13497 this.select_to_end_of_line(
13498 &SelectToEndOfLine {
13499 stop_at_soft_wraps: false,
13500 },
13501 window,
13502 cx,
13503 );
13504 if !action.stop_at_newlines {
13505 this.change_selections(Default::default(), window, cx, |s| {
13506 s.move_with(|_, sel| {
13507 if sel.is_empty() {
13508 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13509 }
13510 });
13511 });
13512 }
13513 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13514 let item = this.cut_common(false, window, cx);
13515 cx.write_to_clipboard(item);
13516 });
13517 }
13518
13519 pub fn move_to_start_of_paragraph(
13520 &mut self,
13521 _: &MoveToStartOfParagraph,
13522 window: &mut Window,
13523 cx: &mut Context<Self>,
13524 ) {
13525 if matches!(self.mode, EditorMode::SingleLine) {
13526 cx.propagate();
13527 return;
13528 }
13529 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13530 self.change_selections(Default::default(), window, cx, |s| {
13531 s.move_with(|map, selection| {
13532 selection.collapse_to(
13533 movement::start_of_paragraph(map, selection.head(), 1),
13534 SelectionGoal::None,
13535 )
13536 });
13537 })
13538 }
13539
13540 pub fn move_to_end_of_paragraph(
13541 &mut self,
13542 _: &MoveToEndOfParagraph,
13543 window: &mut Window,
13544 cx: &mut Context<Self>,
13545 ) {
13546 if matches!(self.mode, EditorMode::SingleLine) {
13547 cx.propagate();
13548 return;
13549 }
13550 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13551 self.change_selections(Default::default(), window, cx, |s| {
13552 s.move_with(|map, selection| {
13553 selection.collapse_to(
13554 movement::end_of_paragraph(map, selection.head(), 1),
13555 SelectionGoal::None,
13556 )
13557 });
13558 })
13559 }
13560
13561 pub fn select_to_start_of_paragraph(
13562 &mut self,
13563 _: &SelectToStartOfParagraph,
13564 window: &mut Window,
13565 cx: &mut Context<Self>,
13566 ) {
13567 if matches!(self.mode, EditorMode::SingleLine) {
13568 cx.propagate();
13569 return;
13570 }
13571 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13572 self.change_selections(Default::default(), window, cx, |s| {
13573 s.move_heads_with(|map, head, _| {
13574 (
13575 movement::start_of_paragraph(map, head, 1),
13576 SelectionGoal::None,
13577 )
13578 });
13579 })
13580 }
13581
13582 pub fn select_to_end_of_paragraph(
13583 &mut self,
13584 _: &SelectToEndOfParagraph,
13585 window: &mut Window,
13586 cx: &mut Context<Self>,
13587 ) {
13588 if matches!(self.mode, EditorMode::SingleLine) {
13589 cx.propagate();
13590 return;
13591 }
13592 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13593 self.change_selections(Default::default(), window, cx, |s| {
13594 s.move_heads_with(|map, head, _| {
13595 (
13596 movement::end_of_paragraph(map, head, 1),
13597 SelectionGoal::None,
13598 )
13599 });
13600 })
13601 }
13602
13603 pub fn move_to_start_of_excerpt(
13604 &mut self,
13605 _: &MoveToStartOfExcerpt,
13606 window: &mut Window,
13607 cx: &mut Context<Self>,
13608 ) {
13609 if matches!(self.mode, EditorMode::SingleLine) {
13610 cx.propagate();
13611 return;
13612 }
13613 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13614 self.change_selections(Default::default(), window, cx, |s| {
13615 s.move_with(|map, selection| {
13616 selection.collapse_to(
13617 movement::start_of_excerpt(
13618 map,
13619 selection.head(),
13620 workspace::searchable::Direction::Prev,
13621 ),
13622 SelectionGoal::None,
13623 )
13624 });
13625 })
13626 }
13627
13628 pub fn move_to_start_of_next_excerpt(
13629 &mut self,
13630 _: &MoveToStartOfNextExcerpt,
13631 window: &mut Window,
13632 cx: &mut Context<Self>,
13633 ) {
13634 if matches!(self.mode, EditorMode::SingleLine) {
13635 cx.propagate();
13636 return;
13637 }
13638
13639 self.change_selections(Default::default(), window, cx, |s| {
13640 s.move_with(|map, selection| {
13641 selection.collapse_to(
13642 movement::start_of_excerpt(
13643 map,
13644 selection.head(),
13645 workspace::searchable::Direction::Next,
13646 ),
13647 SelectionGoal::None,
13648 )
13649 });
13650 })
13651 }
13652
13653 pub fn move_to_end_of_excerpt(
13654 &mut self,
13655 _: &MoveToEndOfExcerpt,
13656 window: &mut Window,
13657 cx: &mut Context<Self>,
13658 ) {
13659 if matches!(self.mode, EditorMode::SingleLine) {
13660 cx.propagate();
13661 return;
13662 }
13663 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13664 self.change_selections(Default::default(), window, cx, |s| {
13665 s.move_with(|map, selection| {
13666 selection.collapse_to(
13667 movement::end_of_excerpt(
13668 map,
13669 selection.head(),
13670 workspace::searchable::Direction::Next,
13671 ),
13672 SelectionGoal::None,
13673 )
13674 });
13675 })
13676 }
13677
13678 pub fn move_to_end_of_previous_excerpt(
13679 &mut self,
13680 _: &MoveToEndOfPreviousExcerpt,
13681 window: &mut Window,
13682 cx: &mut Context<Self>,
13683 ) {
13684 if matches!(self.mode, EditorMode::SingleLine) {
13685 cx.propagate();
13686 return;
13687 }
13688 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13689 self.change_selections(Default::default(), window, cx, |s| {
13690 s.move_with(|map, selection| {
13691 selection.collapse_to(
13692 movement::end_of_excerpt(
13693 map,
13694 selection.head(),
13695 workspace::searchable::Direction::Prev,
13696 ),
13697 SelectionGoal::None,
13698 )
13699 });
13700 })
13701 }
13702
13703 pub fn select_to_start_of_excerpt(
13704 &mut self,
13705 _: &SelectToStartOfExcerpt,
13706 window: &mut Window,
13707 cx: &mut Context<Self>,
13708 ) {
13709 if matches!(self.mode, EditorMode::SingleLine) {
13710 cx.propagate();
13711 return;
13712 }
13713 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13714 self.change_selections(Default::default(), window, cx, |s| {
13715 s.move_heads_with(|map, head, _| {
13716 (
13717 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13718 SelectionGoal::None,
13719 )
13720 });
13721 })
13722 }
13723
13724 pub fn select_to_start_of_next_excerpt(
13725 &mut self,
13726 _: &SelectToStartOfNextExcerpt,
13727 window: &mut Window,
13728 cx: &mut Context<Self>,
13729 ) {
13730 if matches!(self.mode, EditorMode::SingleLine) {
13731 cx.propagate();
13732 return;
13733 }
13734 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13735 self.change_selections(Default::default(), window, cx, |s| {
13736 s.move_heads_with(|map, head, _| {
13737 (
13738 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13739 SelectionGoal::None,
13740 )
13741 });
13742 })
13743 }
13744
13745 pub fn select_to_end_of_excerpt(
13746 &mut self,
13747 _: &SelectToEndOfExcerpt,
13748 window: &mut Window,
13749 cx: &mut Context<Self>,
13750 ) {
13751 if matches!(self.mode, EditorMode::SingleLine) {
13752 cx.propagate();
13753 return;
13754 }
13755 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13756 self.change_selections(Default::default(), window, cx, |s| {
13757 s.move_heads_with(|map, head, _| {
13758 (
13759 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13760 SelectionGoal::None,
13761 )
13762 });
13763 })
13764 }
13765
13766 pub fn select_to_end_of_previous_excerpt(
13767 &mut self,
13768 _: &SelectToEndOfPreviousExcerpt,
13769 window: &mut Window,
13770 cx: &mut Context<Self>,
13771 ) {
13772 if matches!(self.mode, EditorMode::SingleLine) {
13773 cx.propagate();
13774 return;
13775 }
13776 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13777 self.change_selections(Default::default(), window, cx, |s| {
13778 s.move_heads_with(|map, head, _| {
13779 (
13780 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13781 SelectionGoal::None,
13782 )
13783 });
13784 })
13785 }
13786
13787 pub fn move_to_beginning(
13788 &mut self,
13789 _: &MoveToBeginning,
13790 window: &mut Window,
13791 cx: &mut Context<Self>,
13792 ) {
13793 if matches!(self.mode, EditorMode::SingleLine) {
13794 cx.propagate();
13795 return;
13796 }
13797 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13798 self.change_selections(Default::default(), window, cx, |s| {
13799 s.select_ranges(vec![0..0]);
13800 });
13801 }
13802
13803 pub fn select_to_beginning(
13804 &mut self,
13805 _: &SelectToBeginning,
13806 window: &mut Window,
13807 cx: &mut Context<Self>,
13808 ) {
13809 let mut selection = self.selections.last::<Point>(cx);
13810 selection.set_head(Point::zero(), SelectionGoal::None);
13811 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13812 self.change_selections(Default::default(), window, cx, |s| {
13813 s.select(vec![selection]);
13814 });
13815 }
13816
13817 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13818 if matches!(self.mode, EditorMode::SingleLine) {
13819 cx.propagate();
13820 return;
13821 }
13822 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13823 let cursor = self.buffer.read(cx).read(cx).len();
13824 self.change_selections(Default::default(), window, cx, |s| {
13825 s.select_ranges(vec![cursor..cursor])
13826 });
13827 }
13828
13829 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13830 self.nav_history = nav_history;
13831 }
13832
13833 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13834 self.nav_history.as_ref()
13835 }
13836
13837 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13838 self.push_to_nav_history(
13839 self.selections.newest_anchor().head(),
13840 None,
13841 false,
13842 true,
13843 cx,
13844 );
13845 }
13846
13847 fn push_to_nav_history(
13848 &mut self,
13849 cursor_anchor: Anchor,
13850 new_position: Option<Point>,
13851 is_deactivate: bool,
13852 always: bool,
13853 cx: &mut Context<Self>,
13854 ) {
13855 if let Some(nav_history) = self.nav_history.as_mut() {
13856 let buffer = self.buffer.read(cx).read(cx);
13857 let cursor_position = cursor_anchor.to_point(&buffer);
13858 let scroll_state = self.scroll_manager.anchor();
13859 let scroll_top_row = scroll_state.top_row(&buffer);
13860 drop(buffer);
13861
13862 if let Some(new_position) = new_position {
13863 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13864 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13865 return;
13866 }
13867 }
13868
13869 nav_history.push(
13870 Some(NavigationData {
13871 cursor_anchor,
13872 cursor_position,
13873 scroll_anchor: scroll_state,
13874 scroll_top_row,
13875 }),
13876 cx,
13877 );
13878 cx.emit(EditorEvent::PushedToNavHistory {
13879 anchor: cursor_anchor,
13880 is_deactivate,
13881 })
13882 }
13883 }
13884
13885 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13886 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13887 let buffer = self.buffer.read(cx).snapshot(cx);
13888 let mut selection = self.selections.first::<usize>(cx);
13889 selection.set_head(buffer.len(), SelectionGoal::None);
13890 self.change_selections(Default::default(), window, cx, |s| {
13891 s.select(vec![selection]);
13892 });
13893 }
13894
13895 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13896 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13897 let end = self.buffer.read(cx).read(cx).len();
13898 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13899 s.select_ranges(vec![0..end]);
13900 });
13901 }
13902
13903 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13904 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13905 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13906 let mut selections = self.selections.all::<Point>(cx);
13907 let max_point = display_map.buffer_snapshot.max_point();
13908 for selection in &mut selections {
13909 let rows = selection.spanned_rows(true, &display_map);
13910 selection.start = Point::new(rows.start.0, 0);
13911 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13912 selection.reversed = false;
13913 }
13914 self.change_selections(Default::default(), window, cx, |s| {
13915 s.select(selections);
13916 });
13917 }
13918
13919 pub fn split_selection_into_lines(
13920 &mut self,
13921 action: &SplitSelectionIntoLines,
13922 window: &mut Window,
13923 cx: &mut Context<Self>,
13924 ) {
13925 let selections = self
13926 .selections
13927 .all::<Point>(cx)
13928 .into_iter()
13929 .map(|selection| selection.start..selection.end)
13930 .collect::<Vec<_>>();
13931 self.unfold_ranges(&selections, true, true, cx);
13932
13933 let mut new_selection_ranges = Vec::new();
13934 {
13935 let buffer = self.buffer.read(cx).read(cx);
13936 for selection in selections {
13937 for row in selection.start.row..selection.end.row {
13938 let line_start = Point::new(row, 0);
13939 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13940
13941 if action.keep_selections {
13942 // Keep the selection range for each line
13943 let selection_start = if row == selection.start.row {
13944 selection.start
13945 } else {
13946 line_start
13947 };
13948 new_selection_ranges.push(selection_start..line_end);
13949 } else {
13950 // Collapse to cursor at end of line
13951 new_selection_ranges.push(line_end..line_end);
13952 }
13953 }
13954
13955 let is_multiline_selection = selection.start.row != selection.end.row;
13956 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13957 // so this action feels more ergonomic when paired with other selection operations
13958 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13959 if !should_skip_last {
13960 if action.keep_selections {
13961 if is_multiline_selection {
13962 let line_start = Point::new(selection.end.row, 0);
13963 new_selection_ranges.push(line_start..selection.end);
13964 } else {
13965 new_selection_ranges.push(selection.start..selection.end);
13966 }
13967 } else {
13968 new_selection_ranges.push(selection.end..selection.end);
13969 }
13970 }
13971 }
13972 }
13973 self.change_selections(Default::default(), window, cx, |s| {
13974 s.select_ranges(new_selection_ranges);
13975 });
13976 }
13977
13978 pub fn add_selection_above(
13979 &mut self,
13980 _: &AddSelectionAbove,
13981 window: &mut Window,
13982 cx: &mut Context<Self>,
13983 ) {
13984 self.add_selection(true, window, cx);
13985 }
13986
13987 pub fn add_selection_below(
13988 &mut self,
13989 _: &AddSelectionBelow,
13990 window: &mut Window,
13991 cx: &mut Context<Self>,
13992 ) {
13993 self.add_selection(false, window, cx);
13994 }
13995
13996 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13997 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13998
13999 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14000 let all_selections = self.selections.all::<Point>(cx);
14001 let text_layout_details = self.text_layout_details(window);
14002
14003 let (mut columnar_selections, new_selections_to_columnarize) = {
14004 if let Some(state) = self.add_selections_state.as_ref() {
14005 let columnar_selection_ids: HashSet<_> = state
14006 .groups
14007 .iter()
14008 .flat_map(|group| group.stack.iter())
14009 .copied()
14010 .collect();
14011
14012 all_selections
14013 .into_iter()
14014 .partition(|s| columnar_selection_ids.contains(&s.id))
14015 } else {
14016 (Vec::new(), all_selections)
14017 }
14018 };
14019
14020 let mut state = self
14021 .add_selections_state
14022 .take()
14023 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14024
14025 for selection in new_selections_to_columnarize {
14026 let range = selection.display_range(&display_map).sorted();
14027 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14028 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14029 let positions = start_x.min(end_x)..start_x.max(end_x);
14030 let mut stack = Vec::new();
14031 for row in range.start.row().0..=range.end.row().0 {
14032 if let Some(selection) = self.selections.build_columnar_selection(
14033 &display_map,
14034 DisplayRow(row),
14035 &positions,
14036 selection.reversed,
14037 &text_layout_details,
14038 ) {
14039 stack.push(selection.id);
14040 columnar_selections.push(selection);
14041 }
14042 }
14043 if !stack.is_empty() {
14044 if above {
14045 stack.reverse();
14046 }
14047 state.groups.push(AddSelectionsGroup { above, stack });
14048 }
14049 }
14050
14051 let mut final_selections = Vec::new();
14052 let end_row = if above {
14053 DisplayRow(0)
14054 } else {
14055 display_map.max_point().row()
14056 };
14057
14058 let mut last_added_item_per_group = HashMap::default();
14059 for group in state.groups.iter_mut() {
14060 if let Some(last_id) = group.stack.last() {
14061 last_added_item_per_group.insert(*last_id, group);
14062 }
14063 }
14064
14065 for selection in columnar_selections {
14066 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14067 if above == group.above {
14068 let range = selection.display_range(&display_map).sorted();
14069 debug_assert_eq!(range.start.row(), range.end.row());
14070 let mut row = range.start.row();
14071 let positions =
14072 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14073 px(start)..px(end)
14074 } else {
14075 let start_x =
14076 display_map.x_for_display_point(range.start, &text_layout_details);
14077 let end_x =
14078 display_map.x_for_display_point(range.end, &text_layout_details);
14079 start_x.min(end_x)..start_x.max(end_x)
14080 };
14081
14082 let mut maybe_new_selection = None;
14083 while row != end_row {
14084 if above {
14085 row.0 -= 1;
14086 } else {
14087 row.0 += 1;
14088 }
14089 if let Some(new_selection) = self.selections.build_columnar_selection(
14090 &display_map,
14091 row,
14092 &positions,
14093 selection.reversed,
14094 &text_layout_details,
14095 ) {
14096 maybe_new_selection = Some(new_selection);
14097 break;
14098 }
14099 }
14100
14101 if let Some(new_selection) = maybe_new_selection {
14102 group.stack.push(new_selection.id);
14103 if above {
14104 final_selections.push(new_selection);
14105 final_selections.push(selection);
14106 } else {
14107 final_selections.push(selection);
14108 final_selections.push(new_selection);
14109 }
14110 } else {
14111 final_selections.push(selection);
14112 }
14113 } else {
14114 group.stack.pop();
14115 }
14116 } else {
14117 final_selections.push(selection);
14118 }
14119 }
14120
14121 self.change_selections(Default::default(), window, cx, |s| {
14122 s.select(final_selections);
14123 });
14124
14125 let final_selection_ids: HashSet<_> = self
14126 .selections
14127 .all::<Point>(cx)
14128 .iter()
14129 .map(|s| s.id)
14130 .collect();
14131 state.groups.retain_mut(|group| {
14132 // selections might get merged above so we remove invalid items from stacks
14133 group.stack.retain(|id| final_selection_ids.contains(id));
14134
14135 // single selection in stack can be treated as initial state
14136 group.stack.len() > 1
14137 });
14138
14139 if !state.groups.is_empty() {
14140 self.add_selections_state = Some(state);
14141 }
14142 }
14143
14144 fn select_match_ranges(
14145 &mut self,
14146 range: Range<usize>,
14147 reversed: bool,
14148 replace_newest: bool,
14149 auto_scroll: Option<Autoscroll>,
14150 window: &mut Window,
14151 cx: &mut Context<Editor>,
14152 ) {
14153 self.unfold_ranges(
14154 std::slice::from_ref(&range),
14155 false,
14156 auto_scroll.is_some(),
14157 cx,
14158 );
14159 let effects = if let Some(scroll) = auto_scroll {
14160 SelectionEffects::scroll(scroll)
14161 } else {
14162 SelectionEffects::no_scroll()
14163 };
14164 self.change_selections(effects, window, cx, |s| {
14165 if replace_newest {
14166 s.delete(s.newest_anchor().id);
14167 }
14168 if reversed {
14169 s.insert_range(range.end..range.start);
14170 } else {
14171 s.insert_range(range);
14172 }
14173 });
14174 }
14175
14176 pub fn select_next_match_internal(
14177 &mut self,
14178 display_map: &DisplaySnapshot,
14179 replace_newest: bool,
14180 autoscroll: Option<Autoscroll>,
14181 window: &mut Window,
14182 cx: &mut Context<Self>,
14183 ) -> Result<()> {
14184 let buffer = &display_map.buffer_snapshot;
14185 let mut selections = self.selections.all::<usize>(cx);
14186 if let Some(mut select_next_state) = self.select_next_state.take() {
14187 let query = &select_next_state.query;
14188 if !select_next_state.done {
14189 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14190 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14191 let mut next_selected_range = None;
14192
14193 let bytes_after_last_selection =
14194 buffer.bytes_in_range(last_selection.end..buffer.len());
14195 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14196 let query_matches = query
14197 .stream_find_iter(bytes_after_last_selection)
14198 .map(|result| (last_selection.end, result))
14199 .chain(
14200 query
14201 .stream_find_iter(bytes_before_first_selection)
14202 .map(|result| (0, result)),
14203 );
14204
14205 for (start_offset, query_match) in query_matches {
14206 let query_match = query_match.unwrap(); // can only fail due to I/O
14207 let offset_range =
14208 start_offset + query_match.start()..start_offset + query_match.end();
14209
14210 if !select_next_state.wordwise
14211 || (!buffer.is_inside_word(offset_range.start, None)
14212 && !buffer.is_inside_word(offset_range.end, None))
14213 {
14214 // TODO: This is n^2, because we might check all the selections
14215 if !selections
14216 .iter()
14217 .any(|selection| selection.range().overlaps(&offset_range))
14218 {
14219 next_selected_range = Some(offset_range);
14220 break;
14221 }
14222 }
14223 }
14224
14225 if let Some(next_selected_range) = next_selected_range {
14226 self.select_match_ranges(
14227 next_selected_range,
14228 last_selection.reversed,
14229 replace_newest,
14230 autoscroll,
14231 window,
14232 cx,
14233 );
14234 } else {
14235 select_next_state.done = true;
14236 }
14237 }
14238
14239 self.select_next_state = Some(select_next_state);
14240 } else {
14241 let mut only_carets = true;
14242 let mut same_text_selected = true;
14243 let mut selected_text = None;
14244
14245 let mut selections_iter = selections.iter().peekable();
14246 while let Some(selection) = selections_iter.next() {
14247 if selection.start != selection.end {
14248 only_carets = false;
14249 }
14250
14251 if same_text_selected {
14252 if selected_text.is_none() {
14253 selected_text =
14254 Some(buffer.text_for_range(selection.range()).collect::<String>());
14255 }
14256
14257 if let Some(next_selection) = selections_iter.peek() {
14258 if next_selection.range().len() == selection.range().len() {
14259 let next_selected_text = buffer
14260 .text_for_range(next_selection.range())
14261 .collect::<String>();
14262 if Some(next_selected_text) != selected_text {
14263 same_text_selected = false;
14264 selected_text = None;
14265 }
14266 } else {
14267 same_text_selected = false;
14268 selected_text = None;
14269 }
14270 }
14271 }
14272 }
14273
14274 if only_carets {
14275 for selection in &mut selections {
14276 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14277 selection.start = word_range.start;
14278 selection.end = word_range.end;
14279 selection.goal = SelectionGoal::None;
14280 selection.reversed = false;
14281 self.select_match_ranges(
14282 selection.start..selection.end,
14283 selection.reversed,
14284 replace_newest,
14285 autoscroll,
14286 window,
14287 cx,
14288 );
14289 }
14290
14291 if selections.len() == 1 {
14292 let selection = selections
14293 .last()
14294 .expect("ensured that there's only one selection");
14295 let query = buffer
14296 .text_for_range(selection.start..selection.end)
14297 .collect::<String>();
14298 let is_empty = query.is_empty();
14299 let select_state = SelectNextState {
14300 query: AhoCorasick::new(&[query])?,
14301 wordwise: true,
14302 done: is_empty,
14303 };
14304 self.select_next_state = Some(select_state);
14305 } else {
14306 self.select_next_state = None;
14307 }
14308 } else if let Some(selected_text) = selected_text {
14309 self.select_next_state = Some(SelectNextState {
14310 query: AhoCorasick::new(&[selected_text])?,
14311 wordwise: false,
14312 done: false,
14313 });
14314 self.select_next_match_internal(
14315 display_map,
14316 replace_newest,
14317 autoscroll,
14318 window,
14319 cx,
14320 )?;
14321 }
14322 }
14323 Ok(())
14324 }
14325
14326 pub fn select_all_matches(
14327 &mut self,
14328 _action: &SelectAllMatches,
14329 window: &mut Window,
14330 cx: &mut Context<Self>,
14331 ) -> Result<()> {
14332 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14333
14334 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14335
14336 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14337 let Some(select_next_state) = self.select_next_state.as_mut() else {
14338 return Ok(());
14339 };
14340 if select_next_state.done {
14341 return Ok(());
14342 }
14343
14344 let mut new_selections = Vec::new();
14345
14346 let reversed = self.selections.oldest::<usize>(cx).reversed;
14347 let buffer = &display_map.buffer_snapshot;
14348 let query_matches = select_next_state
14349 .query
14350 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14351
14352 for query_match in query_matches.into_iter() {
14353 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14354 let offset_range = if reversed {
14355 query_match.end()..query_match.start()
14356 } else {
14357 query_match.start()..query_match.end()
14358 };
14359
14360 if !select_next_state.wordwise
14361 || (!buffer.is_inside_word(offset_range.start, None)
14362 && !buffer.is_inside_word(offset_range.end, None))
14363 {
14364 new_selections.push(offset_range.start..offset_range.end);
14365 }
14366 }
14367
14368 select_next_state.done = true;
14369
14370 if new_selections.is_empty() {
14371 log::error!("bug: new_selections is empty in select_all_matches");
14372 return Ok(());
14373 }
14374
14375 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14376 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14377 selections.select_ranges(new_selections)
14378 });
14379
14380 Ok(())
14381 }
14382
14383 pub fn select_next(
14384 &mut self,
14385 action: &SelectNext,
14386 window: &mut Window,
14387 cx: &mut Context<Self>,
14388 ) -> Result<()> {
14389 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14390 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14391 self.select_next_match_internal(
14392 &display_map,
14393 action.replace_newest,
14394 Some(Autoscroll::newest()),
14395 window,
14396 cx,
14397 )?;
14398 Ok(())
14399 }
14400
14401 pub fn select_previous(
14402 &mut self,
14403 action: &SelectPrevious,
14404 window: &mut Window,
14405 cx: &mut Context<Self>,
14406 ) -> Result<()> {
14407 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14408 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14409 let buffer = &display_map.buffer_snapshot;
14410 let mut selections = self.selections.all::<usize>(cx);
14411 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14412 let query = &select_prev_state.query;
14413 if !select_prev_state.done {
14414 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14415 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14416 let mut next_selected_range = None;
14417 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14418 let bytes_before_last_selection =
14419 buffer.reversed_bytes_in_range(0..last_selection.start);
14420 let bytes_after_first_selection =
14421 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14422 let query_matches = query
14423 .stream_find_iter(bytes_before_last_selection)
14424 .map(|result| (last_selection.start, result))
14425 .chain(
14426 query
14427 .stream_find_iter(bytes_after_first_selection)
14428 .map(|result| (buffer.len(), result)),
14429 );
14430 for (end_offset, query_match) in query_matches {
14431 let query_match = query_match.unwrap(); // can only fail due to I/O
14432 let offset_range =
14433 end_offset - query_match.end()..end_offset - query_match.start();
14434
14435 if !select_prev_state.wordwise
14436 || (!buffer.is_inside_word(offset_range.start, None)
14437 && !buffer.is_inside_word(offset_range.end, None))
14438 {
14439 next_selected_range = Some(offset_range);
14440 break;
14441 }
14442 }
14443
14444 if let Some(next_selected_range) = next_selected_range {
14445 self.select_match_ranges(
14446 next_selected_range,
14447 last_selection.reversed,
14448 action.replace_newest,
14449 Some(Autoscroll::newest()),
14450 window,
14451 cx,
14452 );
14453 } else {
14454 select_prev_state.done = true;
14455 }
14456 }
14457
14458 self.select_prev_state = Some(select_prev_state);
14459 } else {
14460 let mut only_carets = true;
14461 let mut same_text_selected = true;
14462 let mut selected_text = None;
14463
14464 let mut selections_iter = selections.iter().peekable();
14465 while let Some(selection) = selections_iter.next() {
14466 if selection.start != selection.end {
14467 only_carets = false;
14468 }
14469
14470 if same_text_selected {
14471 if selected_text.is_none() {
14472 selected_text =
14473 Some(buffer.text_for_range(selection.range()).collect::<String>());
14474 }
14475
14476 if let Some(next_selection) = selections_iter.peek() {
14477 if next_selection.range().len() == selection.range().len() {
14478 let next_selected_text = buffer
14479 .text_for_range(next_selection.range())
14480 .collect::<String>();
14481 if Some(next_selected_text) != selected_text {
14482 same_text_selected = false;
14483 selected_text = None;
14484 }
14485 } else {
14486 same_text_selected = false;
14487 selected_text = None;
14488 }
14489 }
14490 }
14491 }
14492
14493 if only_carets {
14494 for selection in &mut selections {
14495 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14496 selection.start = word_range.start;
14497 selection.end = word_range.end;
14498 selection.goal = SelectionGoal::None;
14499 selection.reversed = false;
14500 self.select_match_ranges(
14501 selection.start..selection.end,
14502 selection.reversed,
14503 action.replace_newest,
14504 Some(Autoscroll::newest()),
14505 window,
14506 cx,
14507 );
14508 }
14509 if selections.len() == 1 {
14510 let selection = selections
14511 .last()
14512 .expect("ensured that there's only one selection");
14513 let query = buffer
14514 .text_for_range(selection.start..selection.end)
14515 .collect::<String>();
14516 let is_empty = query.is_empty();
14517 let select_state = SelectNextState {
14518 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14519 wordwise: true,
14520 done: is_empty,
14521 };
14522 self.select_prev_state = Some(select_state);
14523 } else {
14524 self.select_prev_state = None;
14525 }
14526 } else if let Some(selected_text) = selected_text {
14527 self.select_prev_state = Some(SelectNextState {
14528 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14529 wordwise: false,
14530 done: false,
14531 });
14532 self.select_previous(action, window, cx)?;
14533 }
14534 }
14535 Ok(())
14536 }
14537
14538 pub fn find_next_match(
14539 &mut self,
14540 _: &FindNextMatch,
14541 window: &mut Window,
14542 cx: &mut Context<Self>,
14543 ) -> Result<()> {
14544 let selections = self.selections.disjoint_anchors_arc();
14545 match selections.first() {
14546 Some(first) if selections.len() >= 2 => {
14547 self.change_selections(Default::default(), window, cx, |s| {
14548 s.select_ranges([first.range()]);
14549 });
14550 }
14551 _ => self.select_next(
14552 &SelectNext {
14553 replace_newest: true,
14554 },
14555 window,
14556 cx,
14557 )?,
14558 }
14559 Ok(())
14560 }
14561
14562 pub fn find_previous_match(
14563 &mut self,
14564 _: &FindPreviousMatch,
14565 window: &mut Window,
14566 cx: &mut Context<Self>,
14567 ) -> Result<()> {
14568 let selections = self.selections.disjoint_anchors_arc();
14569 match selections.last() {
14570 Some(last) if selections.len() >= 2 => {
14571 self.change_selections(Default::default(), window, cx, |s| {
14572 s.select_ranges([last.range()]);
14573 });
14574 }
14575 _ => self.select_previous(
14576 &SelectPrevious {
14577 replace_newest: true,
14578 },
14579 window,
14580 cx,
14581 )?,
14582 }
14583 Ok(())
14584 }
14585
14586 pub fn toggle_comments(
14587 &mut self,
14588 action: &ToggleComments,
14589 window: &mut Window,
14590 cx: &mut Context<Self>,
14591 ) {
14592 if self.read_only(cx) {
14593 return;
14594 }
14595 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14596 let text_layout_details = &self.text_layout_details(window);
14597 self.transact(window, cx, |this, window, cx| {
14598 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14599 let mut edits = Vec::new();
14600 let mut selection_edit_ranges = Vec::new();
14601 let mut last_toggled_row = None;
14602 let snapshot = this.buffer.read(cx).read(cx);
14603 let empty_str: Arc<str> = Arc::default();
14604 let mut suffixes_inserted = Vec::new();
14605 let ignore_indent = action.ignore_indent;
14606
14607 fn comment_prefix_range(
14608 snapshot: &MultiBufferSnapshot,
14609 row: MultiBufferRow,
14610 comment_prefix: &str,
14611 comment_prefix_whitespace: &str,
14612 ignore_indent: bool,
14613 ) -> Range<Point> {
14614 let indent_size = if ignore_indent {
14615 0
14616 } else {
14617 snapshot.indent_size_for_line(row).len
14618 };
14619
14620 let start = Point::new(row.0, indent_size);
14621
14622 let mut line_bytes = snapshot
14623 .bytes_in_range(start..snapshot.max_point())
14624 .flatten()
14625 .copied();
14626
14627 // If this line currently begins with the line comment prefix, then record
14628 // the range containing the prefix.
14629 if line_bytes
14630 .by_ref()
14631 .take(comment_prefix.len())
14632 .eq(comment_prefix.bytes())
14633 {
14634 // Include any whitespace that matches the comment prefix.
14635 let matching_whitespace_len = line_bytes
14636 .zip(comment_prefix_whitespace.bytes())
14637 .take_while(|(a, b)| a == b)
14638 .count() as u32;
14639 let end = Point::new(
14640 start.row,
14641 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14642 );
14643 start..end
14644 } else {
14645 start..start
14646 }
14647 }
14648
14649 fn comment_suffix_range(
14650 snapshot: &MultiBufferSnapshot,
14651 row: MultiBufferRow,
14652 comment_suffix: &str,
14653 comment_suffix_has_leading_space: bool,
14654 ) -> Range<Point> {
14655 let end = Point::new(row.0, snapshot.line_len(row));
14656 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14657
14658 let mut line_end_bytes = snapshot
14659 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14660 .flatten()
14661 .copied();
14662
14663 let leading_space_len = if suffix_start_column > 0
14664 && line_end_bytes.next() == Some(b' ')
14665 && comment_suffix_has_leading_space
14666 {
14667 1
14668 } else {
14669 0
14670 };
14671
14672 // If this line currently begins with the line comment prefix, then record
14673 // the range containing the prefix.
14674 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14675 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14676 start..end
14677 } else {
14678 end..end
14679 }
14680 }
14681
14682 // TODO: Handle selections that cross excerpts
14683 for selection in &mut selections {
14684 let start_column = snapshot
14685 .indent_size_for_line(MultiBufferRow(selection.start.row))
14686 .len;
14687 let language = if let Some(language) =
14688 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14689 {
14690 language
14691 } else {
14692 continue;
14693 };
14694
14695 selection_edit_ranges.clear();
14696
14697 // If multiple selections contain a given row, avoid processing that
14698 // row more than once.
14699 let mut start_row = MultiBufferRow(selection.start.row);
14700 if last_toggled_row == Some(start_row) {
14701 start_row = start_row.next_row();
14702 }
14703 let end_row =
14704 if selection.end.row > selection.start.row && selection.end.column == 0 {
14705 MultiBufferRow(selection.end.row - 1)
14706 } else {
14707 MultiBufferRow(selection.end.row)
14708 };
14709 last_toggled_row = Some(end_row);
14710
14711 if start_row > end_row {
14712 continue;
14713 }
14714
14715 // If the language has line comments, toggle those.
14716 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14717
14718 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14719 if ignore_indent {
14720 full_comment_prefixes = full_comment_prefixes
14721 .into_iter()
14722 .map(|s| Arc::from(s.trim_end()))
14723 .collect();
14724 }
14725
14726 if !full_comment_prefixes.is_empty() {
14727 let first_prefix = full_comment_prefixes
14728 .first()
14729 .expect("prefixes is non-empty");
14730 let prefix_trimmed_lengths = full_comment_prefixes
14731 .iter()
14732 .map(|p| p.trim_end_matches(' ').len())
14733 .collect::<SmallVec<[usize; 4]>>();
14734
14735 let mut all_selection_lines_are_comments = true;
14736
14737 for row in start_row.0..=end_row.0 {
14738 let row = MultiBufferRow(row);
14739 if start_row < end_row && snapshot.is_line_blank(row) {
14740 continue;
14741 }
14742
14743 let prefix_range = full_comment_prefixes
14744 .iter()
14745 .zip(prefix_trimmed_lengths.iter().copied())
14746 .map(|(prefix, trimmed_prefix_len)| {
14747 comment_prefix_range(
14748 snapshot.deref(),
14749 row,
14750 &prefix[..trimmed_prefix_len],
14751 &prefix[trimmed_prefix_len..],
14752 ignore_indent,
14753 )
14754 })
14755 .max_by_key(|range| range.end.column - range.start.column)
14756 .expect("prefixes is non-empty");
14757
14758 if prefix_range.is_empty() {
14759 all_selection_lines_are_comments = false;
14760 }
14761
14762 selection_edit_ranges.push(prefix_range);
14763 }
14764
14765 if all_selection_lines_are_comments {
14766 edits.extend(
14767 selection_edit_ranges
14768 .iter()
14769 .cloned()
14770 .map(|range| (range, empty_str.clone())),
14771 );
14772 } else {
14773 let min_column = selection_edit_ranges
14774 .iter()
14775 .map(|range| range.start.column)
14776 .min()
14777 .unwrap_or(0);
14778 edits.extend(selection_edit_ranges.iter().map(|range| {
14779 let position = Point::new(range.start.row, min_column);
14780 (position..position, first_prefix.clone())
14781 }));
14782 }
14783 } else if let Some(BlockCommentConfig {
14784 start: full_comment_prefix,
14785 end: comment_suffix,
14786 ..
14787 }) = language.block_comment()
14788 {
14789 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14790 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14791 let prefix_range = comment_prefix_range(
14792 snapshot.deref(),
14793 start_row,
14794 comment_prefix,
14795 comment_prefix_whitespace,
14796 ignore_indent,
14797 );
14798 let suffix_range = comment_suffix_range(
14799 snapshot.deref(),
14800 end_row,
14801 comment_suffix.trim_start_matches(' '),
14802 comment_suffix.starts_with(' '),
14803 );
14804
14805 if prefix_range.is_empty() || suffix_range.is_empty() {
14806 edits.push((
14807 prefix_range.start..prefix_range.start,
14808 full_comment_prefix.clone(),
14809 ));
14810 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14811 suffixes_inserted.push((end_row, comment_suffix.len()));
14812 } else {
14813 edits.push((prefix_range, empty_str.clone()));
14814 edits.push((suffix_range, empty_str.clone()));
14815 }
14816 } else {
14817 continue;
14818 }
14819 }
14820
14821 drop(snapshot);
14822 this.buffer.update(cx, |buffer, cx| {
14823 buffer.edit(edits, None, cx);
14824 });
14825
14826 // Adjust selections so that they end before any comment suffixes that
14827 // were inserted.
14828 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14829 let mut selections = this.selections.all::<Point>(cx);
14830 let snapshot = this.buffer.read(cx).read(cx);
14831 for selection in &mut selections {
14832 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14833 match row.cmp(&MultiBufferRow(selection.end.row)) {
14834 Ordering::Less => {
14835 suffixes_inserted.next();
14836 continue;
14837 }
14838 Ordering::Greater => break,
14839 Ordering::Equal => {
14840 if selection.end.column == snapshot.line_len(row) {
14841 if selection.is_empty() {
14842 selection.start.column -= suffix_len as u32;
14843 }
14844 selection.end.column -= suffix_len as u32;
14845 }
14846 break;
14847 }
14848 }
14849 }
14850 }
14851
14852 drop(snapshot);
14853 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14854
14855 let selections = this.selections.all::<Point>(cx);
14856 let selections_on_single_row = selections.windows(2).all(|selections| {
14857 selections[0].start.row == selections[1].start.row
14858 && selections[0].end.row == selections[1].end.row
14859 && selections[0].start.row == selections[0].end.row
14860 });
14861 let selections_selecting = selections
14862 .iter()
14863 .any(|selection| selection.start != selection.end);
14864 let advance_downwards = action.advance_downwards
14865 && selections_on_single_row
14866 && !selections_selecting
14867 && !matches!(this.mode, EditorMode::SingleLine);
14868
14869 if advance_downwards {
14870 let snapshot = this.buffer.read(cx).snapshot(cx);
14871
14872 this.change_selections(Default::default(), window, cx, |s| {
14873 s.move_cursors_with(|display_snapshot, display_point, _| {
14874 let mut point = display_point.to_point(display_snapshot);
14875 point.row += 1;
14876 point = snapshot.clip_point(point, Bias::Left);
14877 let display_point = point.to_display_point(display_snapshot);
14878 let goal = SelectionGoal::HorizontalPosition(
14879 display_snapshot
14880 .x_for_display_point(display_point, text_layout_details)
14881 .into(),
14882 );
14883 (display_point, goal)
14884 })
14885 });
14886 }
14887 });
14888 }
14889
14890 pub fn select_enclosing_symbol(
14891 &mut self,
14892 _: &SelectEnclosingSymbol,
14893 window: &mut Window,
14894 cx: &mut Context<Self>,
14895 ) {
14896 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14897
14898 let buffer = self.buffer.read(cx).snapshot(cx);
14899 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14900
14901 fn update_selection(
14902 selection: &Selection<usize>,
14903 buffer_snap: &MultiBufferSnapshot,
14904 ) -> Option<Selection<usize>> {
14905 let cursor = selection.head();
14906 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14907 for symbol in symbols.iter().rev() {
14908 let start = symbol.range.start.to_offset(buffer_snap);
14909 let end = symbol.range.end.to_offset(buffer_snap);
14910 let new_range = start..end;
14911 if start < selection.start || end > selection.end {
14912 return Some(Selection {
14913 id: selection.id,
14914 start: new_range.start,
14915 end: new_range.end,
14916 goal: SelectionGoal::None,
14917 reversed: selection.reversed,
14918 });
14919 }
14920 }
14921 None
14922 }
14923
14924 let mut selected_larger_symbol = false;
14925 let new_selections = old_selections
14926 .iter()
14927 .map(|selection| match update_selection(selection, &buffer) {
14928 Some(new_selection) => {
14929 if new_selection.range() != selection.range() {
14930 selected_larger_symbol = true;
14931 }
14932 new_selection
14933 }
14934 None => selection.clone(),
14935 })
14936 .collect::<Vec<_>>();
14937
14938 if selected_larger_symbol {
14939 self.change_selections(Default::default(), window, cx, |s| {
14940 s.select(new_selections);
14941 });
14942 }
14943 }
14944
14945 pub fn select_larger_syntax_node(
14946 &mut self,
14947 _: &SelectLargerSyntaxNode,
14948 window: &mut Window,
14949 cx: &mut Context<Self>,
14950 ) {
14951 let Some(visible_row_count) = self.visible_row_count() else {
14952 return;
14953 };
14954 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14955 if old_selections.is_empty() {
14956 return;
14957 }
14958
14959 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14960
14961 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14962 let buffer = self.buffer.read(cx).snapshot(cx);
14963
14964 let mut selected_larger_node = false;
14965 let mut new_selections = old_selections
14966 .iter()
14967 .map(|selection| {
14968 let old_range = selection.start..selection.end;
14969
14970 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14971 // manually select word at selection
14972 if ["string_content", "inline"].contains(&node.kind()) {
14973 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
14974 // ignore if word is already selected
14975 if !word_range.is_empty() && old_range != word_range {
14976 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
14977 // only select word if start and end point belongs to same word
14978 if word_range == last_word_range {
14979 selected_larger_node = true;
14980 return Selection {
14981 id: selection.id,
14982 start: word_range.start,
14983 end: word_range.end,
14984 goal: SelectionGoal::None,
14985 reversed: selection.reversed,
14986 };
14987 }
14988 }
14989 }
14990 }
14991
14992 let mut new_range = old_range.clone();
14993 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
14994 {
14995 new_range = match containing_range {
14996 MultiOrSingleBufferOffsetRange::Single(_) => break,
14997 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14998 };
14999 if !node.is_named() {
15000 continue;
15001 }
15002 if !display_map.intersects_fold(new_range.start)
15003 && !display_map.intersects_fold(new_range.end)
15004 {
15005 break;
15006 }
15007 }
15008
15009 selected_larger_node |= new_range != old_range;
15010 Selection {
15011 id: selection.id,
15012 start: new_range.start,
15013 end: new_range.end,
15014 goal: SelectionGoal::None,
15015 reversed: selection.reversed,
15016 }
15017 })
15018 .collect::<Vec<_>>();
15019
15020 if !selected_larger_node {
15021 return; // don't put this call in the history
15022 }
15023
15024 // scroll based on transformation done to the last selection created by the user
15025 let (last_old, last_new) = old_selections
15026 .last()
15027 .zip(new_selections.last().cloned())
15028 .expect("old_selections isn't empty");
15029
15030 // revert selection
15031 let is_selection_reversed = {
15032 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15033 new_selections.last_mut().expect("checked above").reversed =
15034 should_newest_selection_be_reversed;
15035 should_newest_selection_be_reversed
15036 };
15037
15038 if selected_larger_node {
15039 self.select_syntax_node_history.disable_clearing = true;
15040 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15041 s.select(new_selections.clone());
15042 });
15043 self.select_syntax_node_history.disable_clearing = false;
15044 }
15045
15046 let start_row = last_new.start.to_display_point(&display_map).row().0;
15047 let end_row = last_new.end.to_display_point(&display_map).row().0;
15048 let selection_height = end_row - start_row + 1;
15049 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15050
15051 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15052 let scroll_behavior = if fits_on_the_screen {
15053 self.request_autoscroll(Autoscroll::fit(), cx);
15054 SelectSyntaxNodeScrollBehavior::FitSelection
15055 } else if is_selection_reversed {
15056 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15057 SelectSyntaxNodeScrollBehavior::CursorTop
15058 } else {
15059 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15060 SelectSyntaxNodeScrollBehavior::CursorBottom
15061 };
15062
15063 self.select_syntax_node_history.push((
15064 old_selections,
15065 scroll_behavior,
15066 is_selection_reversed,
15067 ));
15068 }
15069
15070 pub fn select_smaller_syntax_node(
15071 &mut self,
15072 _: &SelectSmallerSyntaxNode,
15073 window: &mut Window,
15074 cx: &mut Context<Self>,
15075 ) {
15076 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15077
15078 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15079 self.select_syntax_node_history.pop()
15080 {
15081 if let Some(selection) = selections.last_mut() {
15082 selection.reversed = is_selection_reversed;
15083 }
15084
15085 self.select_syntax_node_history.disable_clearing = true;
15086 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15087 s.select(selections.to_vec());
15088 });
15089 self.select_syntax_node_history.disable_clearing = false;
15090
15091 match scroll_behavior {
15092 SelectSyntaxNodeScrollBehavior::CursorTop => {
15093 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15094 }
15095 SelectSyntaxNodeScrollBehavior::FitSelection => {
15096 self.request_autoscroll(Autoscroll::fit(), cx);
15097 }
15098 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15099 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15100 }
15101 }
15102 }
15103 }
15104
15105 pub fn unwrap_syntax_node(
15106 &mut self,
15107 _: &UnwrapSyntaxNode,
15108 window: &mut Window,
15109 cx: &mut Context<Self>,
15110 ) {
15111 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15112
15113 let buffer = self.buffer.read(cx).snapshot(cx);
15114 let selections = self
15115 .selections
15116 .all::<usize>(cx)
15117 .into_iter()
15118 // subtracting the offset requires sorting
15119 .sorted_by_key(|i| i.start);
15120
15121 let full_edits = selections
15122 .into_iter()
15123 .filter_map(|selection| {
15124 let child = if selection.is_empty()
15125 && let Some((_, ancestor_range)) =
15126 buffer.syntax_ancestor(selection.start..selection.end)
15127 {
15128 match ancestor_range {
15129 MultiOrSingleBufferOffsetRange::Single(range) => range,
15130 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15131 }
15132 } else {
15133 selection.range()
15134 };
15135
15136 let mut parent = child.clone();
15137 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15138 parent = match ancestor_range {
15139 MultiOrSingleBufferOffsetRange::Single(range) => range,
15140 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15141 };
15142 if parent.start < child.start || parent.end > child.end {
15143 break;
15144 }
15145 }
15146
15147 if parent == child {
15148 return None;
15149 }
15150 let text = buffer.text_for_range(child).collect::<String>();
15151 Some((selection.id, parent, text))
15152 })
15153 .collect::<Vec<_>>();
15154 if full_edits.is_empty() {
15155 return;
15156 }
15157
15158 self.transact(window, cx, |this, window, cx| {
15159 this.buffer.update(cx, |buffer, cx| {
15160 buffer.edit(
15161 full_edits
15162 .iter()
15163 .map(|(_, p, t)| (p.clone(), t.clone()))
15164 .collect::<Vec<_>>(),
15165 None,
15166 cx,
15167 );
15168 });
15169 this.change_selections(Default::default(), window, cx, |s| {
15170 let mut offset = 0;
15171 let mut selections = vec![];
15172 for (id, parent, text) in full_edits {
15173 let start = parent.start - offset;
15174 offset += parent.len() - text.len();
15175 selections.push(Selection {
15176 id,
15177 start,
15178 end: start + text.len(),
15179 reversed: false,
15180 goal: Default::default(),
15181 });
15182 }
15183 s.select(selections);
15184 });
15185 });
15186 }
15187
15188 pub fn select_next_syntax_node(
15189 &mut self,
15190 _: &SelectNextSyntaxNode,
15191 window: &mut Window,
15192 cx: &mut Context<Self>,
15193 ) {
15194 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15195 if old_selections.is_empty() {
15196 return;
15197 }
15198
15199 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15200
15201 let buffer = self.buffer.read(cx).snapshot(cx);
15202 let mut selected_sibling = false;
15203
15204 let new_selections = old_selections
15205 .iter()
15206 .map(|selection| {
15207 let old_range = selection.start..selection.end;
15208
15209 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15210 let new_range = node.byte_range();
15211 selected_sibling = true;
15212 Selection {
15213 id: selection.id,
15214 start: new_range.start,
15215 end: new_range.end,
15216 goal: SelectionGoal::None,
15217 reversed: selection.reversed,
15218 }
15219 } else {
15220 selection.clone()
15221 }
15222 })
15223 .collect::<Vec<_>>();
15224
15225 if selected_sibling {
15226 self.change_selections(
15227 SelectionEffects::scroll(Autoscroll::fit()),
15228 window,
15229 cx,
15230 |s| {
15231 s.select(new_selections);
15232 },
15233 );
15234 }
15235 }
15236
15237 pub fn select_prev_syntax_node(
15238 &mut self,
15239 _: &SelectPreviousSyntaxNode,
15240 window: &mut Window,
15241 cx: &mut Context<Self>,
15242 ) {
15243 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15244 if old_selections.is_empty() {
15245 return;
15246 }
15247
15248 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15249
15250 let buffer = self.buffer.read(cx).snapshot(cx);
15251 let mut selected_sibling = false;
15252
15253 let new_selections = old_selections
15254 .iter()
15255 .map(|selection| {
15256 let old_range = selection.start..selection.end;
15257
15258 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15259 let new_range = node.byte_range();
15260 selected_sibling = true;
15261 Selection {
15262 id: selection.id,
15263 start: new_range.start,
15264 end: new_range.end,
15265 goal: SelectionGoal::None,
15266 reversed: selection.reversed,
15267 }
15268 } else {
15269 selection.clone()
15270 }
15271 })
15272 .collect::<Vec<_>>();
15273
15274 if selected_sibling {
15275 self.change_selections(
15276 SelectionEffects::scroll(Autoscroll::fit()),
15277 window,
15278 cx,
15279 |s| {
15280 s.select(new_selections);
15281 },
15282 );
15283 }
15284 }
15285
15286 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15287 if !EditorSettings::get_global(cx).gutter.runnables {
15288 self.clear_tasks();
15289 return Task::ready(());
15290 }
15291 let project = self.project().map(Entity::downgrade);
15292 let task_sources = self.lsp_task_sources(cx);
15293 let multi_buffer = self.buffer.downgrade();
15294 cx.spawn_in(window, async move |editor, cx| {
15295 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15296 let Some(project) = project.and_then(|p| p.upgrade()) else {
15297 return;
15298 };
15299 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15300 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15301 }) else {
15302 return;
15303 };
15304
15305 let hide_runnables = project
15306 .update(cx, |project, _| project.is_via_collab())
15307 .unwrap_or(true);
15308 if hide_runnables {
15309 return;
15310 }
15311 let new_rows =
15312 cx.background_spawn({
15313 let snapshot = display_snapshot.clone();
15314 async move {
15315 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15316 }
15317 })
15318 .await;
15319 let Ok(lsp_tasks) =
15320 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15321 else {
15322 return;
15323 };
15324 let lsp_tasks = lsp_tasks.await;
15325
15326 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15327 lsp_tasks
15328 .into_iter()
15329 .flat_map(|(kind, tasks)| {
15330 tasks.into_iter().filter_map(move |(location, task)| {
15331 Some((kind.clone(), location?, task))
15332 })
15333 })
15334 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15335 let buffer = location.target.buffer;
15336 let buffer_snapshot = buffer.read(cx).snapshot();
15337 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
15338 |(excerpt_id, snapshot, _)| {
15339 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15340 display_snapshot
15341 .buffer_snapshot
15342 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15343 } else {
15344 None
15345 }
15346 },
15347 );
15348 if let Some(offset) = offset {
15349 let task_buffer_range =
15350 location.target.range.to_point(&buffer_snapshot);
15351 let context_buffer_range =
15352 task_buffer_range.to_offset(&buffer_snapshot);
15353 let context_range = BufferOffset(context_buffer_range.start)
15354 ..BufferOffset(context_buffer_range.end);
15355
15356 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15357 .or_insert_with(|| RunnableTasks {
15358 templates: Vec::new(),
15359 offset,
15360 column: task_buffer_range.start.column,
15361 extra_variables: HashMap::default(),
15362 context_range,
15363 })
15364 .templates
15365 .push((kind, task.original_task().clone()));
15366 }
15367
15368 acc
15369 })
15370 }) else {
15371 return;
15372 };
15373
15374 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15375 buffer.language_settings(cx).tasks.prefer_lsp
15376 }) else {
15377 return;
15378 };
15379
15380 let rows = Self::runnable_rows(
15381 project,
15382 display_snapshot,
15383 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15384 new_rows,
15385 cx.clone(),
15386 )
15387 .await;
15388 editor
15389 .update(cx, |editor, _| {
15390 editor.clear_tasks();
15391 for (key, mut value) in rows {
15392 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15393 value.templates.extend(lsp_tasks.templates);
15394 }
15395
15396 editor.insert_tasks(key, value);
15397 }
15398 for (key, value) in lsp_tasks_by_rows {
15399 editor.insert_tasks(key, value);
15400 }
15401 })
15402 .ok();
15403 })
15404 }
15405 fn fetch_runnable_ranges(
15406 snapshot: &DisplaySnapshot,
15407 range: Range<Anchor>,
15408 ) -> Vec<language::RunnableRange> {
15409 snapshot.buffer_snapshot.runnable_ranges(range).collect()
15410 }
15411
15412 fn runnable_rows(
15413 project: Entity<Project>,
15414 snapshot: DisplaySnapshot,
15415 prefer_lsp: bool,
15416 runnable_ranges: Vec<RunnableRange>,
15417 cx: AsyncWindowContext,
15418 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15419 cx.spawn(async move |cx| {
15420 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15421 for mut runnable in runnable_ranges {
15422 let Some(tasks) = cx
15423 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15424 .ok()
15425 else {
15426 continue;
15427 };
15428 let mut tasks = tasks.await;
15429
15430 if prefer_lsp {
15431 tasks.retain(|(task_kind, _)| {
15432 !matches!(task_kind, TaskSourceKind::Language { .. })
15433 });
15434 }
15435 if tasks.is_empty() {
15436 continue;
15437 }
15438
15439 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15440 let Some(row) = snapshot
15441 .buffer_snapshot
15442 .buffer_line_for_row(MultiBufferRow(point.row))
15443 .map(|(_, range)| range.start.row)
15444 else {
15445 continue;
15446 };
15447
15448 let context_range =
15449 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15450 runnable_rows.push((
15451 (runnable.buffer_id, row),
15452 RunnableTasks {
15453 templates: tasks,
15454 offset: snapshot
15455 .buffer_snapshot
15456 .anchor_before(runnable.run_range.start),
15457 context_range,
15458 column: point.column,
15459 extra_variables: runnable.extra_captures,
15460 },
15461 ));
15462 }
15463 runnable_rows
15464 })
15465 }
15466
15467 fn templates_with_tags(
15468 project: &Entity<Project>,
15469 runnable: &mut Runnable,
15470 cx: &mut App,
15471 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15472 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15473 let (worktree_id, file) = project
15474 .buffer_for_id(runnable.buffer, cx)
15475 .and_then(|buffer| buffer.read(cx).file())
15476 .map(|file| (file.worktree_id(cx), file.clone()))
15477 .unzip();
15478
15479 (
15480 project.task_store().read(cx).task_inventory().cloned(),
15481 worktree_id,
15482 file,
15483 )
15484 });
15485
15486 let tags = mem::take(&mut runnable.tags);
15487 let language = runnable.language.clone();
15488 cx.spawn(async move |cx| {
15489 let mut templates_with_tags = Vec::new();
15490 if let Some(inventory) = inventory {
15491 for RunnableTag(tag) in tags {
15492 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15493 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15494 }) else {
15495 return templates_with_tags;
15496 };
15497 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15498 move |(_, template)| {
15499 template.tags.iter().any(|source_tag| source_tag == &tag)
15500 },
15501 ));
15502 }
15503 }
15504 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15505
15506 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15507 // Strongest source wins; if we have worktree tag binding, prefer that to
15508 // global and language bindings;
15509 // if we have a global binding, prefer that to language binding.
15510 let first_mismatch = templates_with_tags
15511 .iter()
15512 .position(|(tag_source, _)| tag_source != leading_tag_source);
15513 if let Some(index) = first_mismatch {
15514 templates_with_tags.truncate(index);
15515 }
15516 }
15517
15518 templates_with_tags
15519 })
15520 }
15521
15522 pub fn move_to_enclosing_bracket(
15523 &mut self,
15524 _: &MoveToEnclosingBracket,
15525 window: &mut Window,
15526 cx: &mut Context<Self>,
15527 ) {
15528 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15529 self.change_selections(Default::default(), window, cx, |s| {
15530 s.move_offsets_with(|snapshot, selection| {
15531 let Some(enclosing_bracket_ranges) =
15532 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15533 else {
15534 return;
15535 };
15536
15537 let mut best_length = usize::MAX;
15538 let mut best_inside = false;
15539 let mut best_in_bracket_range = false;
15540 let mut best_destination = None;
15541 for (open, close) in enclosing_bracket_ranges {
15542 let close = close.to_inclusive();
15543 let length = close.end() - open.start;
15544 let inside = selection.start >= open.end && selection.end <= *close.start();
15545 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15546 || close.contains(&selection.head());
15547
15548 // If best is next to a bracket and current isn't, skip
15549 if !in_bracket_range && best_in_bracket_range {
15550 continue;
15551 }
15552
15553 // Prefer smaller lengths unless best is inside and current isn't
15554 if length > best_length && (best_inside || !inside) {
15555 continue;
15556 }
15557
15558 best_length = length;
15559 best_inside = inside;
15560 best_in_bracket_range = in_bracket_range;
15561 best_destination = Some(
15562 if close.contains(&selection.start) && close.contains(&selection.end) {
15563 if inside { open.end } else { open.start }
15564 } else if inside {
15565 *close.start()
15566 } else {
15567 *close.end()
15568 },
15569 );
15570 }
15571
15572 if let Some(destination) = best_destination {
15573 selection.collapse_to(destination, SelectionGoal::None);
15574 }
15575 })
15576 });
15577 }
15578
15579 pub fn undo_selection(
15580 &mut self,
15581 _: &UndoSelection,
15582 window: &mut Window,
15583 cx: &mut Context<Self>,
15584 ) {
15585 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15586 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15587 self.selection_history.mode = SelectionHistoryMode::Undoing;
15588 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15589 this.end_selection(window, cx);
15590 this.change_selections(
15591 SelectionEffects::scroll(Autoscroll::newest()),
15592 window,
15593 cx,
15594 |s| s.select_anchors(entry.selections.to_vec()),
15595 );
15596 });
15597 self.selection_history.mode = SelectionHistoryMode::Normal;
15598
15599 self.select_next_state = entry.select_next_state;
15600 self.select_prev_state = entry.select_prev_state;
15601 self.add_selections_state = entry.add_selections_state;
15602 }
15603 }
15604
15605 pub fn redo_selection(
15606 &mut self,
15607 _: &RedoSelection,
15608 window: &mut Window,
15609 cx: &mut Context<Self>,
15610 ) {
15611 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15612 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15613 self.selection_history.mode = SelectionHistoryMode::Redoing;
15614 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15615 this.end_selection(window, cx);
15616 this.change_selections(
15617 SelectionEffects::scroll(Autoscroll::newest()),
15618 window,
15619 cx,
15620 |s| s.select_anchors(entry.selections.to_vec()),
15621 );
15622 });
15623 self.selection_history.mode = SelectionHistoryMode::Normal;
15624
15625 self.select_next_state = entry.select_next_state;
15626 self.select_prev_state = entry.select_prev_state;
15627 self.add_selections_state = entry.add_selections_state;
15628 }
15629 }
15630
15631 pub fn expand_excerpts(
15632 &mut self,
15633 action: &ExpandExcerpts,
15634 _: &mut Window,
15635 cx: &mut Context<Self>,
15636 ) {
15637 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15638 }
15639
15640 pub fn expand_excerpts_down(
15641 &mut self,
15642 action: &ExpandExcerptsDown,
15643 _: &mut Window,
15644 cx: &mut Context<Self>,
15645 ) {
15646 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15647 }
15648
15649 pub fn expand_excerpts_up(
15650 &mut self,
15651 action: &ExpandExcerptsUp,
15652 _: &mut Window,
15653 cx: &mut Context<Self>,
15654 ) {
15655 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15656 }
15657
15658 pub fn expand_excerpts_for_direction(
15659 &mut self,
15660 lines: u32,
15661 direction: ExpandExcerptDirection,
15662
15663 cx: &mut Context<Self>,
15664 ) {
15665 let selections = self.selections.disjoint_anchors_arc();
15666
15667 let lines = if lines == 0 {
15668 EditorSettings::get_global(cx).expand_excerpt_lines
15669 } else {
15670 lines
15671 };
15672
15673 self.buffer.update(cx, |buffer, cx| {
15674 let snapshot = buffer.snapshot(cx);
15675 let mut excerpt_ids = selections
15676 .iter()
15677 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15678 .collect::<Vec<_>>();
15679 excerpt_ids.sort();
15680 excerpt_ids.dedup();
15681 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15682 })
15683 }
15684
15685 pub fn expand_excerpt(
15686 &mut self,
15687 excerpt: ExcerptId,
15688 direction: ExpandExcerptDirection,
15689 window: &mut Window,
15690 cx: &mut Context<Self>,
15691 ) {
15692 let current_scroll_position = self.scroll_position(cx);
15693 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15694 let mut should_scroll_up = false;
15695
15696 if direction == ExpandExcerptDirection::Down {
15697 let multi_buffer = self.buffer.read(cx);
15698 let snapshot = multi_buffer.snapshot(cx);
15699 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15700 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15701 && let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt)
15702 {
15703 let buffer_snapshot = buffer.read(cx).snapshot();
15704 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15705 let last_row = buffer_snapshot.max_point().row;
15706 let lines_below = last_row.saturating_sub(excerpt_end_row);
15707 should_scroll_up = lines_below >= lines_to_expand;
15708 }
15709 }
15710
15711 self.buffer.update(cx, |buffer, cx| {
15712 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15713 });
15714
15715 if should_scroll_up {
15716 let new_scroll_position =
15717 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15718 self.set_scroll_position(new_scroll_position, window, cx);
15719 }
15720 }
15721
15722 pub fn go_to_singleton_buffer_point(
15723 &mut self,
15724 point: Point,
15725 window: &mut Window,
15726 cx: &mut Context<Self>,
15727 ) {
15728 self.go_to_singleton_buffer_range(point..point, window, cx);
15729 }
15730
15731 pub fn go_to_singleton_buffer_range(
15732 &mut self,
15733 range: Range<Point>,
15734 window: &mut Window,
15735 cx: &mut Context<Self>,
15736 ) {
15737 let multibuffer = self.buffer().read(cx);
15738 let Some(buffer) = multibuffer.as_singleton() else {
15739 return;
15740 };
15741 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15742 return;
15743 };
15744 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15745 return;
15746 };
15747 self.change_selections(
15748 SelectionEffects::default().nav_history(true),
15749 window,
15750 cx,
15751 |s| s.select_anchor_ranges([start..end]),
15752 );
15753 }
15754
15755 pub fn go_to_diagnostic(
15756 &mut self,
15757 action: &GoToDiagnostic,
15758 window: &mut Window,
15759 cx: &mut Context<Self>,
15760 ) {
15761 if !self.diagnostics_enabled() {
15762 return;
15763 }
15764 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15765 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15766 }
15767
15768 pub fn go_to_prev_diagnostic(
15769 &mut self,
15770 action: &GoToPreviousDiagnostic,
15771 window: &mut Window,
15772 cx: &mut Context<Self>,
15773 ) {
15774 if !self.diagnostics_enabled() {
15775 return;
15776 }
15777 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15778 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15779 }
15780
15781 pub fn go_to_diagnostic_impl(
15782 &mut self,
15783 direction: Direction,
15784 severity: GoToDiagnosticSeverityFilter,
15785 window: &mut Window,
15786 cx: &mut Context<Self>,
15787 ) {
15788 let buffer = self.buffer.read(cx).snapshot(cx);
15789 let selection = self.selections.newest::<usize>(cx);
15790
15791 let mut active_group_id = None;
15792 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15793 && active_group.active_range.start.to_offset(&buffer) == selection.start
15794 {
15795 active_group_id = Some(active_group.group_id);
15796 }
15797
15798 fn filtered(
15799 snapshot: EditorSnapshot,
15800 severity: GoToDiagnosticSeverityFilter,
15801 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15802 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15803 diagnostics
15804 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15805 .filter(|entry| entry.range.start != entry.range.end)
15806 .filter(|entry| !entry.diagnostic.is_unnecessary)
15807 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15808 }
15809
15810 let snapshot = self.snapshot(window, cx);
15811 let before = filtered(
15812 snapshot.clone(),
15813 severity,
15814 buffer
15815 .diagnostics_in_range(0..selection.start)
15816 .filter(|entry| entry.range.start <= selection.start),
15817 );
15818 let after = filtered(
15819 snapshot,
15820 severity,
15821 buffer
15822 .diagnostics_in_range(selection.start..buffer.len())
15823 .filter(|entry| entry.range.start >= selection.start),
15824 );
15825
15826 let mut found: Option<DiagnosticEntry<usize>> = None;
15827 if direction == Direction::Prev {
15828 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15829 {
15830 for diagnostic in prev_diagnostics.into_iter().rev() {
15831 if diagnostic.range.start != selection.start
15832 || active_group_id
15833 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15834 {
15835 found = Some(diagnostic);
15836 break 'outer;
15837 }
15838 }
15839 }
15840 } else {
15841 for diagnostic in after.chain(before) {
15842 if diagnostic.range.start != selection.start
15843 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15844 {
15845 found = Some(diagnostic);
15846 break;
15847 }
15848 }
15849 }
15850 let Some(next_diagnostic) = found else {
15851 return;
15852 };
15853
15854 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
15855 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
15856 return;
15857 };
15858 self.change_selections(Default::default(), window, cx, |s| {
15859 s.select_ranges(vec![
15860 next_diagnostic.range.start..next_diagnostic.range.start,
15861 ])
15862 });
15863 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15864 self.refresh_edit_prediction(false, true, window, cx);
15865 }
15866
15867 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15868 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15869 let snapshot = self.snapshot(window, cx);
15870 let selection = self.selections.newest::<Point>(cx);
15871 self.go_to_hunk_before_or_after_position(
15872 &snapshot,
15873 selection.head(),
15874 Direction::Next,
15875 window,
15876 cx,
15877 );
15878 }
15879
15880 pub fn go_to_hunk_before_or_after_position(
15881 &mut self,
15882 snapshot: &EditorSnapshot,
15883 position: Point,
15884 direction: Direction,
15885 window: &mut Window,
15886 cx: &mut Context<Editor>,
15887 ) {
15888 let row = if direction == Direction::Next {
15889 self.hunk_after_position(snapshot, position)
15890 .map(|hunk| hunk.row_range.start)
15891 } else {
15892 self.hunk_before_position(snapshot, position)
15893 };
15894
15895 if let Some(row) = row {
15896 let destination = Point::new(row.0, 0);
15897 let autoscroll = Autoscroll::center();
15898
15899 self.unfold_ranges(&[destination..destination], false, false, cx);
15900 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15901 s.select_ranges([destination..destination]);
15902 });
15903 }
15904 }
15905
15906 fn hunk_after_position(
15907 &mut self,
15908 snapshot: &EditorSnapshot,
15909 position: Point,
15910 ) -> Option<MultiBufferDiffHunk> {
15911 snapshot
15912 .buffer_snapshot
15913 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15914 .find(|hunk| hunk.row_range.start.0 > position.row)
15915 .or_else(|| {
15916 snapshot
15917 .buffer_snapshot
15918 .diff_hunks_in_range(Point::zero()..position)
15919 .find(|hunk| hunk.row_range.end.0 < position.row)
15920 })
15921 }
15922
15923 fn go_to_prev_hunk(
15924 &mut self,
15925 _: &GoToPreviousHunk,
15926 window: &mut Window,
15927 cx: &mut Context<Self>,
15928 ) {
15929 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15930 let snapshot = self.snapshot(window, cx);
15931 let selection = self.selections.newest::<Point>(cx);
15932 self.go_to_hunk_before_or_after_position(
15933 &snapshot,
15934 selection.head(),
15935 Direction::Prev,
15936 window,
15937 cx,
15938 );
15939 }
15940
15941 fn hunk_before_position(
15942 &mut self,
15943 snapshot: &EditorSnapshot,
15944 position: Point,
15945 ) -> Option<MultiBufferRow> {
15946 snapshot
15947 .buffer_snapshot
15948 .diff_hunk_before(position)
15949 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15950 }
15951
15952 fn go_to_next_change(
15953 &mut self,
15954 _: &GoToNextChange,
15955 window: &mut Window,
15956 cx: &mut Context<Self>,
15957 ) {
15958 if let Some(selections) = self
15959 .change_list
15960 .next_change(1, Direction::Next)
15961 .map(|s| s.to_vec())
15962 {
15963 self.change_selections(Default::default(), window, cx, |s| {
15964 let map = s.display_map();
15965 s.select_display_ranges(selections.iter().map(|a| {
15966 let point = a.to_display_point(&map);
15967 point..point
15968 }))
15969 })
15970 }
15971 }
15972
15973 fn go_to_previous_change(
15974 &mut self,
15975 _: &GoToPreviousChange,
15976 window: &mut Window,
15977 cx: &mut Context<Self>,
15978 ) {
15979 if let Some(selections) = self
15980 .change_list
15981 .next_change(1, Direction::Prev)
15982 .map(|s| s.to_vec())
15983 {
15984 self.change_selections(Default::default(), window, cx, |s| {
15985 let map = s.display_map();
15986 s.select_display_ranges(selections.iter().map(|a| {
15987 let point = a.to_display_point(&map);
15988 point..point
15989 }))
15990 })
15991 }
15992 }
15993
15994 pub fn go_to_next_document_highlight(
15995 &mut self,
15996 _: &GoToNextDocumentHighlight,
15997 window: &mut Window,
15998 cx: &mut Context<Self>,
15999 ) {
16000 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16001 }
16002
16003 pub fn go_to_prev_document_highlight(
16004 &mut self,
16005 _: &GoToPreviousDocumentHighlight,
16006 window: &mut Window,
16007 cx: &mut Context<Self>,
16008 ) {
16009 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16010 }
16011
16012 pub fn go_to_document_highlight_before_or_after_position(
16013 &mut self,
16014 direction: Direction,
16015 window: &mut Window,
16016 cx: &mut Context<Editor>,
16017 ) {
16018 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16019 let snapshot = self.snapshot(window, cx);
16020 let buffer = &snapshot.buffer_snapshot;
16021 let position = self.selections.newest::<Point>(cx).head();
16022 let anchor_position = buffer.anchor_after(position);
16023
16024 // Get all document highlights (both read and write)
16025 let mut all_highlights = Vec::new();
16026
16027 if let Some((_, read_highlights)) = self
16028 .background_highlights
16029 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16030 {
16031 all_highlights.extend(read_highlights.iter());
16032 }
16033
16034 if let Some((_, write_highlights)) = self
16035 .background_highlights
16036 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16037 {
16038 all_highlights.extend(write_highlights.iter());
16039 }
16040
16041 if all_highlights.is_empty() {
16042 return;
16043 }
16044
16045 // Sort highlights by position
16046 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16047
16048 let target_highlight = match direction {
16049 Direction::Next => {
16050 // Find the first highlight after the current position
16051 all_highlights
16052 .iter()
16053 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16054 }
16055 Direction::Prev => {
16056 // Find the last highlight before the current position
16057 all_highlights
16058 .iter()
16059 .rev()
16060 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16061 }
16062 };
16063
16064 if let Some(highlight) = target_highlight {
16065 let destination = highlight.start.to_point(buffer);
16066 let autoscroll = Autoscroll::center();
16067
16068 self.unfold_ranges(&[destination..destination], false, false, cx);
16069 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16070 s.select_ranges([destination..destination]);
16071 });
16072 }
16073 }
16074
16075 fn go_to_line<T: 'static>(
16076 &mut self,
16077 position: Anchor,
16078 highlight_color: Option<Hsla>,
16079 window: &mut Window,
16080 cx: &mut Context<Self>,
16081 ) {
16082 let snapshot = self.snapshot(window, cx).display_snapshot;
16083 let position = position.to_point(&snapshot.buffer_snapshot);
16084 let start = snapshot
16085 .buffer_snapshot
16086 .clip_point(Point::new(position.row, 0), Bias::Left);
16087 let end = start + Point::new(1, 0);
16088 let start = snapshot.buffer_snapshot.anchor_before(start);
16089 let end = snapshot.buffer_snapshot.anchor_before(end);
16090
16091 self.highlight_rows::<T>(
16092 start..end,
16093 highlight_color
16094 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16095 Default::default(),
16096 cx,
16097 );
16098
16099 if self.buffer.read(cx).is_singleton() {
16100 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16101 }
16102 }
16103
16104 pub fn go_to_definition(
16105 &mut self,
16106 _: &GoToDefinition,
16107 window: &mut Window,
16108 cx: &mut Context<Self>,
16109 ) -> Task<Result<Navigated>> {
16110 let definition =
16111 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16112 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16113 cx.spawn_in(window, async move |editor, cx| {
16114 if definition.await? == Navigated::Yes {
16115 return Ok(Navigated::Yes);
16116 }
16117 match fallback_strategy {
16118 GoToDefinitionFallback::None => Ok(Navigated::No),
16119 GoToDefinitionFallback::FindAllReferences => {
16120 match editor.update_in(cx, |editor, window, cx| {
16121 editor.find_all_references(&FindAllReferences, window, cx)
16122 })? {
16123 Some(references) => references.await,
16124 None => Ok(Navigated::No),
16125 }
16126 }
16127 }
16128 })
16129 }
16130
16131 pub fn go_to_declaration(
16132 &mut self,
16133 _: &GoToDeclaration,
16134 window: &mut Window,
16135 cx: &mut Context<Self>,
16136 ) -> Task<Result<Navigated>> {
16137 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16138 }
16139
16140 pub fn go_to_declaration_split(
16141 &mut self,
16142 _: &GoToDeclaration,
16143 window: &mut Window,
16144 cx: &mut Context<Self>,
16145 ) -> Task<Result<Navigated>> {
16146 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16147 }
16148
16149 pub fn go_to_implementation(
16150 &mut self,
16151 _: &GoToImplementation,
16152 window: &mut Window,
16153 cx: &mut Context<Self>,
16154 ) -> Task<Result<Navigated>> {
16155 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16156 }
16157
16158 pub fn go_to_implementation_split(
16159 &mut self,
16160 _: &GoToImplementationSplit,
16161 window: &mut Window,
16162 cx: &mut Context<Self>,
16163 ) -> Task<Result<Navigated>> {
16164 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16165 }
16166
16167 pub fn go_to_type_definition(
16168 &mut self,
16169 _: &GoToTypeDefinition,
16170 window: &mut Window,
16171 cx: &mut Context<Self>,
16172 ) -> Task<Result<Navigated>> {
16173 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16174 }
16175
16176 pub fn go_to_definition_split(
16177 &mut self,
16178 _: &GoToDefinitionSplit,
16179 window: &mut Window,
16180 cx: &mut Context<Self>,
16181 ) -> Task<Result<Navigated>> {
16182 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16183 }
16184
16185 pub fn go_to_type_definition_split(
16186 &mut self,
16187 _: &GoToTypeDefinitionSplit,
16188 window: &mut Window,
16189 cx: &mut Context<Self>,
16190 ) -> Task<Result<Navigated>> {
16191 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16192 }
16193
16194 fn go_to_definition_of_kind(
16195 &mut self,
16196 kind: GotoDefinitionKind,
16197 split: bool,
16198 window: &mut Window,
16199 cx: &mut Context<Self>,
16200 ) -> Task<Result<Navigated>> {
16201 let Some(provider) = self.semantics_provider.clone() else {
16202 return Task::ready(Ok(Navigated::No));
16203 };
16204 let head = self.selections.newest::<usize>(cx).head();
16205 let buffer = self.buffer.read(cx);
16206 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16207 return Task::ready(Ok(Navigated::No));
16208 };
16209 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16210 return Task::ready(Ok(Navigated::No));
16211 };
16212
16213 cx.spawn_in(window, async move |editor, cx| {
16214 let Some(definitions) = definitions.await? else {
16215 return Ok(Navigated::No);
16216 };
16217 let navigated = editor
16218 .update_in(cx, |editor, window, cx| {
16219 editor.navigate_to_hover_links(
16220 Some(kind),
16221 definitions
16222 .into_iter()
16223 .filter(|location| {
16224 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16225 })
16226 .map(HoverLink::Text)
16227 .collect::<Vec<_>>(),
16228 split,
16229 window,
16230 cx,
16231 )
16232 })?
16233 .await?;
16234 anyhow::Ok(navigated)
16235 })
16236 }
16237
16238 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16239 let selection = self.selections.newest_anchor();
16240 let head = selection.head();
16241 let tail = selection.tail();
16242
16243 let Some((buffer, start_position)) =
16244 self.buffer.read(cx).text_anchor_for_position(head, cx)
16245 else {
16246 return;
16247 };
16248
16249 let end_position = if head != tail {
16250 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16251 return;
16252 };
16253 Some(pos)
16254 } else {
16255 None
16256 };
16257
16258 let url_finder = cx.spawn_in(window, async move |editor, cx| {
16259 let url = if let Some(end_pos) = end_position {
16260 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16261 } else {
16262 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16263 };
16264
16265 if let Some(url) = url {
16266 editor.update(cx, |_, cx| {
16267 cx.open_url(&url);
16268 })
16269 } else {
16270 Ok(())
16271 }
16272 });
16273
16274 url_finder.detach();
16275 }
16276
16277 pub fn open_selected_filename(
16278 &mut self,
16279 _: &OpenSelectedFilename,
16280 window: &mut Window,
16281 cx: &mut Context<Self>,
16282 ) {
16283 let Some(workspace) = self.workspace() else {
16284 return;
16285 };
16286
16287 let position = self.selections.newest_anchor().head();
16288
16289 let Some((buffer, buffer_position)) =
16290 self.buffer.read(cx).text_anchor_for_position(position, cx)
16291 else {
16292 return;
16293 };
16294
16295 let project = self.project.clone();
16296
16297 cx.spawn_in(window, async move |_, cx| {
16298 let result = find_file(&buffer, project, buffer_position, cx).await;
16299
16300 if let Some((_, path)) = result {
16301 workspace
16302 .update_in(cx, |workspace, window, cx| {
16303 workspace.open_resolved_path(path, window, cx)
16304 })?
16305 .await?;
16306 }
16307 anyhow::Ok(())
16308 })
16309 .detach();
16310 }
16311
16312 pub(crate) fn navigate_to_hover_links(
16313 &mut self,
16314 kind: Option<GotoDefinitionKind>,
16315 definitions: Vec<HoverLink>,
16316 split: bool,
16317 window: &mut Window,
16318 cx: &mut Context<Editor>,
16319 ) -> Task<Result<Navigated>> {
16320 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16321 let mut first_url_or_file = None;
16322 let definitions: Vec<_> = definitions
16323 .into_iter()
16324 .filter_map(|def| match def {
16325 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16326 HoverLink::InlayHint(lsp_location, server_id) => {
16327 let computation =
16328 self.compute_target_location(lsp_location, server_id, window, cx);
16329 Some(cx.background_spawn(computation))
16330 }
16331 HoverLink::Url(url) => {
16332 first_url_or_file = Some(Either::Left(url));
16333 None
16334 }
16335 HoverLink::File(path) => {
16336 first_url_or_file = Some(Either::Right(path));
16337 None
16338 }
16339 })
16340 .collect();
16341
16342 let workspace = self.workspace();
16343
16344 cx.spawn_in(window, async move |editor, acx| {
16345 let mut locations: Vec<Location> = future::join_all(definitions)
16346 .await
16347 .into_iter()
16348 .filter_map(|location| location.transpose())
16349 .collect::<Result<_>>()
16350 .context("location tasks")?;
16351
16352 if locations.len() > 1 {
16353 let Some(workspace) = workspace else {
16354 return Ok(Navigated::No);
16355 };
16356
16357 let tab_kind = match kind {
16358 Some(GotoDefinitionKind::Implementation) => "Implementations",
16359 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16360 Some(GotoDefinitionKind::Declaration) => "Declarations",
16361 Some(GotoDefinitionKind::Type) => "Types",
16362 };
16363 let title = editor
16364 .update_in(acx, |_, _, cx| {
16365 let target = locations
16366 .iter()
16367 .map(|location| {
16368 location
16369 .buffer
16370 .read(cx)
16371 .text_for_range(location.range.clone())
16372 .collect::<String>()
16373 })
16374 .filter(|text| !text.contains('\n'))
16375 .unique()
16376 .take(3)
16377 .join(", ");
16378 if target.is_empty() {
16379 tab_kind.to_owned()
16380 } else {
16381 format!("{tab_kind} for {target}")
16382 }
16383 })
16384 .context("buffer title")?;
16385
16386 let opened = workspace
16387 .update_in(acx, |workspace, window, cx| {
16388 Self::open_locations_in_multibuffer(
16389 workspace,
16390 locations,
16391 title,
16392 split,
16393 MultibufferSelectionMode::First,
16394 window,
16395 cx,
16396 )
16397 })
16398 .is_ok();
16399
16400 anyhow::Ok(Navigated::from_bool(opened))
16401 } else if locations.is_empty() {
16402 // If there is one url or file, open it directly
16403 match first_url_or_file {
16404 Some(Either::Left(url)) => {
16405 acx.update(|_, cx| cx.open_url(&url))?;
16406 Ok(Navigated::Yes)
16407 }
16408 Some(Either::Right(path)) => {
16409 let Some(workspace) = workspace else {
16410 return Ok(Navigated::No);
16411 };
16412
16413 workspace
16414 .update_in(acx, |workspace, window, cx| {
16415 workspace.open_resolved_path(path, window, cx)
16416 })?
16417 .await?;
16418 Ok(Navigated::Yes)
16419 }
16420 None => Ok(Navigated::No),
16421 }
16422 } else {
16423 let Some(workspace) = workspace else {
16424 return Ok(Navigated::No);
16425 };
16426
16427 let target = locations.pop().unwrap();
16428 editor.update_in(acx, |editor, window, cx| {
16429 let range = target.range.to_point(target.buffer.read(cx));
16430 let range = editor.range_for_match(&range);
16431 let range = collapse_multiline_range(range);
16432
16433 if !split
16434 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16435 {
16436 editor.go_to_singleton_buffer_range(range, window, cx);
16437 } else {
16438 let pane = workspace.read(cx).active_pane().clone();
16439 window.defer(cx, move |window, cx| {
16440 let target_editor: Entity<Self> =
16441 workspace.update(cx, |workspace, cx| {
16442 let pane = if split {
16443 workspace.adjacent_pane(window, cx)
16444 } else {
16445 workspace.active_pane().clone()
16446 };
16447
16448 workspace.open_project_item(
16449 pane,
16450 target.buffer.clone(),
16451 true,
16452 true,
16453 window,
16454 cx,
16455 )
16456 });
16457 target_editor.update(cx, |target_editor, cx| {
16458 // When selecting a definition in a different buffer, disable the nav history
16459 // to avoid creating a history entry at the previous cursor location.
16460 pane.update(cx, |pane, _| pane.disable_history());
16461 target_editor.go_to_singleton_buffer_range(range, window, cx);
16462 pane.update(cx, |pane, _| pane.enable_history());
16463 });
16464 });
16465 }
16466 Navigated::Yes
16467 })
16468 }
16469 })
16470 }
16471
16472 fn compute_target_location(
16473 &self,
16474 lsp_location: lsp::Location,
16475 server_id: LanguageServerId,
16476 window: &mut Window,
16477 cx: &mut Context<Self>,
16478 ) -> Task<anyhow::Result<Option<Location>>> {
16479 let Some(project) = self.project.clone() else {
16480 return Task::ready(Ok(None));
16481 };
16482
16483 cx.spawn_in(window, async move |editor, cx| {
16484 let location_task = editor.update(cx, |_, cx| {
16485 project.update(cx, |project, cx| {
16486 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16487 })
16488 })?;
16489 let location = Some({
16490 let target_buffer_handle = location_task.await.context("open local buffer")?;
16491 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16492 let target_start = target_buffer
16493 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16494 let target_end = target_buffer
16495 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16496 target_buffer.anchor_after(target_start)
16497 ..target_buffer.anchor_before(target_end)
16498 })?;
16499 Location {
16500 buffer: target_buffer_handle,
16501 range,
16502 }
16503 });
16504 Ok(location)
16505 })
16506 }
16507
16508 pub fn find_all_references(
16509 &mut self,
16510 _: &FindAllReferences,
16511 window: &mut Window,
16512 cx: &mut Context<Self>,
16513 ) -> Option<Task<Result<Navigated>>> {
16514 let selection = self.selections.newest::<usize>(cx);
16515 let multi_buffer = self.buffer.read(cx);
16516 let head = selection.head();
16517
16518 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16519 let head_anchor = multi_buffer_snapshot.anchor_at(
16520 head,
16521 if head < selection.tail() {
16522 Bias::Right
16523 } else {
16524 Bias::Left
16525 },
16526 );
16527
16528 match self
16529 .find_all_references_task_sources
16530 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16531 {
16532 Ok(_) => {
16533 log::info!(
16534 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16535 );
16536 return None;
16537 }
16538 Err(i) => {
16539 self.find_all_references_task_sources.insert(i, head_anchor);
16540 }
16541 }
16542
16543 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16544 let workspace = self.workspace()?;
16545 let project = workspace.read(cx).project().clone();
16546 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16547 Some(cx.spawn_in(window, async move |editor, cx| {
16548 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16549 if let Ok(i) = editor
16550 .find_all_references_task_sources
16551 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16552 {
16553 editor.find_all_references_task_sources.remove(i);
16554 }
16555 });
16556
16557 let Some(locations) = references.await? else {
16558 return anyhow::Ok(Navigated::No);
16559 };
16560 if locations.is_empty() {
16561 return anyhow::Ok(Navigated::No);
16562 }
16563
16564 workspace.update_in(cx, |workspace, window, cx| {
16565 let target = locations
16566 .iter()
16567 .map(|location| {
16568 location
16569 .buffer
16570 .read(cx)
16571 .text_for_range(location.range.clone())
16572 .collect::<String>()
16573 })
16574 .filter(|text| !text.contains('\n'))
16575 .unique()
16576 .take(3)
16577 .join(", ");
16578 let title = if target.is_empty() {
16579 "References".to_owned()
16580 } else {
16581 format!("References to {target}")
16582 };
16583 Self::open_locations_in_multibuffer(
16584 workspace,
16585 locations,
16586 title,
16587 false,
16588 MultibufferSelectionMode::First,
16589 window,
16590 cx,
16591 );
16592 Navigated::Yes
16593 })
16594 }))
16595 }
16596
16597 /// Opens a multibuffer with the given project locations in it
16598 pub fn open_locations_in_multibuffer(
16599 workspace: &mut Workspace,
16600 mut locations: Vec<Location>,
16601 title: String,
16602 split: bool,
16603 multibuffer_selection_mode: MultibufferSelectionMode,
16604 window: &mut Window,
16605 cx: &mut Context<Workspace>,
16606 ) {
16607 if locations.is_empty() {
16608 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16609 return;
16610 }
16611
16612 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16613
16614 let mut locations = locations.into_iter().peekable();
16615 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16616 let capability = workspace.project().read(cx).capability();
16617
16618 // a key to find existing multibuffer editors with the same set of locations
16619 // to prevent us from opening more and more multibuffer tabs for searches and the like
16620 let mut key = (title.clone(), vec![]);
16621 let excerpt_buffer = cx.new(|cx| {
16622 let key = &mut key.1;
16623 let mut multibuffer = MultiBuffer::new(capability);
16624 while let Some(location) = locations.next() {
16625 let buffer = location.buffer.read(cx);
16626 let mut ranges_for_buffer = Vec::new();
16627 let range = location.range.to_point(buffer);
16628 ranges_for_buffer.push(range.clone());
16629
16630 while let Some(next_location) =
16631 locations.next_if(|next_location| next_location.buffer == location.buffer)
16632 {
16633 ranges_for_buffer.push(next_location.range.to_point(buffer));
16634 }
16635
16636 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16637 key.push((
16638 location.buffer.read(cx).remote_id(),
16639 ranges_for_buffer.clone(),
16640 ));
16641 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16642 PathKey::for_buffer(&location.buffer, cx),
16643 location.buffer.clone(),
16644 ranges_for_buffer,
16645 multibuffer_context_lines(cx),
16646 cx,
16647 );
16648 ranges.extend(new_ranges)
16649 }
16650
16651 multibuffer.with_title(title)
16652 });
16653 let existing = workspace.active_pane().update(cx, |pane, cx| {
16654 pane.items()
16655 .filter_map(|item| item.downcast::<Editor>())
16656 .find(|editor| {
16657 editor
16658 .read(cx)
16659 .lookup_key
16660 .as_ref()
16661 .and_then(|it| {
16662 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
16663 })
16664 .is_some_and(|it| *it == key)
16665 })
16666 });
16667 let editor = existing.unwrap_or_else(|| {
16668 cx.new(|cx| {
16669 let mut editor = Editor::for_multibuffer(
16670 excerpt_buffer,
16671 Some(workspace.project().clone()),
16672 window,
16673 cx,
16674 );
16675 editor.lookup_key = Some(Box::new(key));
16676 editor
16677 })
16678 });
16679 editor.update(cx, |editor, cx| {
16680 match multibuffer_selection_mode {
16681 MultibufferSelectionMode::First => {
16682 if let Some(first_range) = ranges.first() {
16683 editor.change_selections(
16684 SelectionEffects::no_scroll(),
16685 window,
16686 cx,
16687 |selections| {
16688 selections.clear_disjoint();
16689 selections
16690 .select_anchor_ranges(std::iter::once(first_range.clone()));
16691 },
16692 );
16693 }
16694 editor.highlight_background::<Self>(
16695 &ranges,
16696 |theme| theme.colors().editor_highlighted_line_background,
16697 cx,
16698 );
16699 }
16700 MultibufferSelectionMode::All => {
16701 editor.change_selections(
16702 SelectionEffects::no_scroll(),
16703 window,
16704 cx,
16705 |selections| {
16706 selections.clear_disjoint();
16707 selections.select_anchor_ranges(ranges);
16708 },
16709 );
16710 }
16711 }
16712 editor.register_buffers_with_language_servers(cx);
16713 });
16714
16715 let item = Box::new(editor);
16716 let item_id = item.item_id();
16717
16718 if split {
16719 workspace.split_item(SplitDirection::Right, item, window, cx);
16720 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16721 let (preview_item_id, preview_item_idx) =
16722 workspace.active_pane().read_with(cx, |pane, _| {
16723 (pane.preview_item_id(), pane.preview_item_idx())
16724 });
16725
16726 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16727
16728 if let Some(preview_item_id) = preview_item_id {
16729 workspace.active_pane().update(cx, |pane, cx| {
16730 pane.remove_item(preview_item_id, false, false, window, cx);
16731 });
16732 }
16733 } else {
16734 workspace.add_item_to_active_pane(item, None, true, window, cx);
16735 }
16736 workspace.active_pane().update(cx, |pane, cx| {
16737 pane.set_preview_item_id(Some(item_id), cx);
16738 });
16739 }
16740
16741 pub fn rename(
16742 &mut self,
16743 _: &Rename,
16744 window: &mut Window,
16745 cx: &mut Context<Self>,
16746 ) -> Option<Task<Result<()>>> {
16747 use language::ToOffset as _;
16748
16749 let provider = self.semantics_provider.clone()?;
16750 let selection = self.selections.newest_anchor().clone();
16751 let (cursor_buffer, cursor_buffer_position) = self
16752 .buffer
16753 .read(cx)
16754 .text_anchor_for_position(selection.head(), cx)?;
16755 let (tail_buffer, cursor_buffer_position_end) = self
16756 .buffer
16757 .read(cx)
16758 .text_anchor_for_position(selection.tail(), cx)?;
16759 if tail_buffer != cursor_buffer {
16760 return None;
16761 }
16762
16763 let snapshot = cursor_buffer.read(cx).snapshot();
16764 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16765 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16766 let prepare_rename = provider
16767 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16768 .unwrap_or_else(|| Task::ready(Ok(None)));
16769 drop(snapshot);
16770
16771 Some(cx.spawn_in(window, async move |this, cx| {
16772 let rename_range = if let Some(range) = prepare_rename.await? {
16773 Some(range)
16774 } else {
16775 this.update(cx, |this, cx| {
16776 let buffer = this.buffer.read(cx).snapshot(cx);
16777 let mut buffer_highlights = this
16778 .document_highlights_for_position(selection.head(), &buffer)
16779 .filter(|highlight| {
16780 highlight.start.excerpt_id == selection.head().excerpt_id
16781 && highlight.end.excerpt_id == selection.head().excerpt_id
16782 });
16783 buffer_highlights
16784 .next()
16785 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16786 })?
16787 };
16788 if let Some(rename_range) = rename_range {
16789 this.update_in(cx, |this, window, cx| {
16790 let snapshot = cursor_buffer.read(cx).snapshot();
16791 let rename_buffer_range = rename_range.to_offset(&snapshot);
16792 let cursor_offset_in_rename_range =
16793 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16794 let cursor_offset_in_rename_range_end =
16795 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16796
16797 this.take_rename(false, window, cx);
16798 let buffer = this.buffer.read(cx).read(cx);
16799 let cursor_offset = selection.head().to_offset(&buffer);
16800 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16801 let rename_end = rename_start + rename_buffer_range.len();
16802 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16803 let mut old_highlight_id = None;
16804 let old_name: Arc<str> = buffer
16805 .chunks(rename_start..rename_end, true)
16806 .map(|chunk| {
16807 if old_highlight_id.is_none() {
16808 old_highlight_id = chunk.syntax_highlight_id;
16809 }
16810 chunk.text
16811 })
16812 .collect::<String>()
16813 .into();
16814
16815 drop(buffer);
16816
16817 // Position the selection in the rename editor so that it matches the current selection.
16818 this.show_local_selections = false;
16819 let rename_editor = cx.new(|cx| {
16820 let mut editor = Editor::single_line(window, cx);
16821 editor.buffer.update(cx, |buffer, cx| {
16822 buffer.edit([(0..0, old_name.clone())], None, cx)
16823 });
16824 let rename_selection_range = match cursor_offset_in_rename_range
16825 .cmp(&cursor_offset_in_rename_range_end)
16826 {
16827 Ordering::Equal => {
16828 editor.select_all(&SelectAll, window, cx);
16829 return editor;
16830 }
16831 Ordering::Less => {
16832 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16833 }
16834 Ordering::Greater => {
16835 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16836 }
16837 };
16838 if rename_selection_range.end > old_name.len() {
16839 editor.select_all(&SelectAll, window, cx);
16840 } else {
16841 editor.change_selections(Default::default(), window, cx, |s| {
16842 s.select_ranges([rename_selection_range]);
16843 });
16844 }
16845 editor
16846 });
16847 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16848 if e == &EditorEvent::Focused {
16849 cx.emit(EditorEvent::FocusedIn)
16850 }
16851 })
16852 .detach();
16853
16854 let write_highlights =
16855 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16856 let read_highlights =
16857 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16858 let ranges = write_highlights
16859 .iter()
16860 .flat_map(|(_, ranges)| ranges.iter())
16861 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16862 .cloned()
16863 .collect();
16864
16865 this.highlight_text::<Rename>(
16866 ranges,
16867 HighlightStyle {
16868 fade_out: Some(0.6),
16869 ..Default::default()
16870 },
16871 cx,
16872 );
16873 let rename_focus_handle = rename_editor.focus_handle(cx);
16874 window.focus(&rename_focus_handle);
16875 let block_id = this.insert_blocks(
16876 [BlockProperties {
16877 style: BlockStyle::Flex,
16878 placement: BlockPlacement::Below(range.start),
16879 height: Some(1),
16880 render: Arc::new({
16881 let rename_editor = rename_editor.clone();
16882 move |cx: &mut BlockContext| {
16883 let mut text_style = cx.editor_style.text.clone();
16884 if let Some(highlight_style) = old_highlight_id
16885 .and_then(|h| h.style(&cx.editor_style.syntax))
16886 {
16887 text_style = text_style.highlight(highlight_style);
16888 }
16889 div()
16890 .block_mouse_except_scroll()
16891 .pl(cx.anchor_x)
16892 .child(EditorElement::new(
16893 &rename_editor,
16894 EditorStyle {
16895 background: cx.theme().system().transparent,
16896 local_player: cx.editor_style.local_player,
16897 text: text_style,
16898 scrollbar_width: cx.editor_style.scrollbar_width,
16899 syntax: cx.editor_style.syntax.clone(),
16900 status: cx.editor_style.status.clone(),
16901 inlay_hints_style: HighlightStyle {
16902 font_weight: Some(FontWeight::BOLD),
16903 ..make_inlay_hints_style(cx.app)
16904 },
16905 edit_prediction_styles: make_suggestion_styles(
16906 cx.app,
16907 ),
16908 ..EditorStyle::default()
16909 },
16910 ))
16911 .into_any_element()
16912 }
16913 }),
16914 priority: 0,
16915 }],
16916 Some(Autoscroll::fit()),
16917 cx,
16918 )[0];
16919 this.pending_rename = Some(RenameState {
16920 range,
16921 old_name,
16922 editor: rename_editor,
16923 block_id,
16924 });
16925 })?;
16926 }
16927
16928 Ok(())
16929 }))
16930 }
16931
16932 pub fn confirm_rename(
16933 &mut self,
16934 _: &ConfirmRename,
16935 window: &mut Window,
16936 cx: &mut Context<Self>,
16937 ) -> Option<Task<Result<()>>> {
16938 let rename = self.take_rename(false, window, cx)?;
16939 let workspace = self.workspace()?.downgrade();
16940 let (buffer, start) = self
16941 .buffer
16942 .read(cx)
16943 .text_anchor_for_position(rename.range.start, cx)?;
16944 let (end_buffer, _) = self
16945 .buffer
16946 .read(cx)
16947 .text_anchor_for_position(rename.range.end, cx)?;
16948 if buffer != end_buffer {
16949 return None;
16950 }
16951
16952 let old_name = rename.old_name;
16953 let new_name = rename.editor.read(cx).text(cx);
16954
16955 let rename = self.semantics_provider.as_ref()?.perform_rename(
16956 &buffer,
16957 start,
16958 new_name.clone(),
16959 cx,
16960 )?;
16961
16962 Some(cx.spawn_in(window, async move |editor, cx| {
16963 let project_transaction = rename.await?;
16964 Self::open_project_transaction(
16965 &editor,
16966 workspace,
16967 project_transaction,
16968 format!("Rename: {} → {}", old_name, new_name),
16969 cx,
16970 )
16971 .await?;
16972
16973 editor.update(cx, |editor, cx| {
16974 editor.refresh_document_highlights(cx);
16975 })?;
16976 Ok(())
16977 }))
16978 }
16979
16980 fn take_rename(
16981 &mut self,
16982 moving_cursor: bool,
16983 window: &mut Window,
16984 cx: &mut Context<Self>,
16985 ) -> Option<RenameState> {
16986 let rename = self.pending_rename.take()?;
16987 if rename.editor.focus_handle(cx).is_focused(window) {
16988 window.focus(&self.focus_handle);
16989 }
16990
16991 self.remove_blocks(
16992 [rename.block_id].into_iter().collect(),
16993 Some(Autoscroll::fit()),
16994 cx,
16995 );
16996 self.clear_highlights::<Rename>(cx);
16997 self.show_local_selections = true;
16998
16999 if moving_cursor {
17000 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17001 editor.selections.newest::<usize>(cx).head()
17002 });
17003
17004 // Update the selection to match the position of the selection inside
17005 // the rename editor.
17006 let snapshot = self.buffer.read(cx).read(cx);
17007 let rename_range = rename.range.to_offset(&snapshot);
17008 let cursor_in_editor = snapshot
17009 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17010 .min(rename_range.end);
17011 drop(snapshot);
17012
17013 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17014 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17015 });
17016 } else {
17017 self.refresh_document_highlights(cx);
17018 }
17019
17020 Some(rename)
17021 }
17022
17023 pub fn pending_rename(&self) -> Option<&RenameState> {
17024 self.pending_rename.as_ref()
17025 }
17026
17027 fn format(
17028 &mut self,
17029 _: &Format,
17030 window: &mut Window,
17031 cx: &mut Context<Self>,
17032 ) -> Option<Task<Result<()>>> {
17033 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17034
17035 let project = match &self.project {
17036 Some(project) => project.clone(),
17037 None => return None,
17038 };
17039
17040 Some(self.perform_format(
17041 project,
17042 FormatTrigger::Manual,
17043 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17044 window,
17045 cx,
17046 ))
17047 }
17048
17049 fn format_selections(
17050 &mut self,
17051 _: &FormatSelections,
17052 window: &mut Window,
17053 cx: &mut Context<Self>,
17054 ) -> Option<Task<Result<()>>> {
17055 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17056
17057 let project = match &self.project {
17058 Some(project) => project.clone(),
17059 None => return None,
17060 };
17061
17062 let ranges = self
17063 .selections
17064 .all_adjusted(cx)
17065 .into_iter()
17066 .map(|selection| selection.range())
17067 .collect_vec();
17068
17069 Some(self.perform_format(
17070 project,
17071 FormatTrigger::Manual,
17072 FormatTarget::Ranges(ranges),
17073 window,
17074 cx,
17075 ))
17076 }
17077
17078 fn perform_format(
17079 &mut self,
17080 project: Entity<Project>,
17081 trigger: FormatTrigger,
17082 target: FormatTarget,
17083 window: &mut Window,
17084 cx: &mut Context<Self>,
17085 ) -> Task<Result<()>> {
17086 let buffer = self.buffer.clone();
17087 let (buffers, target) = match target {
17088 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17089 FormatTarget::Ranges(selection_ranges) => {
17090 let multi_buffer = buffer.read(cx);
17091 let snapshot = multi_buffer.read(cx);
17092 let mut buffers = HashSet::default();
17093 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17094 BTreeMap::new();
17095 for selection_range in selection_ranges {
17096 for (buffer, buffer_range, _) in
17097 snapshot.range_to_buffer_ranges(selection_range)
17098 {
17099 let buffer_id = buffer.remote_id();
17100 let start = buffer.anchor_before(buffer_range.start);
17101 let end = buffer.anchor_after(buffer_range.end);
17102 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17103 buffer_id_to_ranges
17104 .entry(buffer_id)
17105 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17106 .or_insert_with(|| vec![start..end]);
17107 }
17108 }
17109 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17110 }
17111 };
17112
17113 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17114 let selections_prev = transaction_id_prev
17115 .and_then(|transaction_id_prev| {
17116 // default to selections as they were after the last edit, if we have them,
17117 // instead of how they are now.
17118 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17119 // will take you back to where you made the last edit, instead of staying where you scrolled
17120 self.selection_history
17121 .transaction(transaction_id_prev)
17122 .map(|t| t.0.clone())
17123 })
17124 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17125
17126 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17127 let format = project.update(cx, |project, cx| {
17128 project.format(buffers, target, true, trigger, cx)
17129 });
17130
17131 cx.spawn_in(window, async move |editor, cx| {
17132 let transaction = futures::select_biased! {
17133 transaction = format.log_err().fuse() => transaction,
17134 () = timeout => {
17135 log::warn!("timed out waiting for formatting");
17136 None
17137 }
17138 };
17139
17140 buffer
17141 .update(cx, |buffer, cx| {
17142 if let Some(transaction) = transaction
17143 && !buffer.is_singleton()
17144 {
17145 buffer.push_transaction(&transaction.0, cx);
17146 }
17147 cx.notify();
17148 })
17149 .ok();
17150
17151 if let Some(transaction_id_now) =
17152 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17153 {
17154 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17155 if has_new_transaction {
17156 _ = editor.update(cx, |editor, _| {
17157 editor
17158 .selection_history
17159 .insert_transaction(transaction_id_now, selections_prev);
17160 });
17161 }
17162 }
17163
17164 Ok(())
17165 })
17166 }
17167
17168 fn organize_imports(
17169 &mut self,
17170 _: &OrganizeImports,
17171 window: &mut Window,
17172 cx: &mut Context<Self>,
17173 ) -> Option<Task<Result<()>>> {
17174 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17175 let project = match &self.project {
17176 Some(project) => project.clone(),
17177 None => return None,
17178 };
17179 Some(self.perform_code_action_kind(
17180 project,
17181 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17182 window,
17183 cx,
17184 ))
17185 }
17186
17187 fn perform_code_action_kind(
17188 &mut self,
17189 project: Entity<Project>,
17190 kind: CodeActionKind,
17191 window: &mut Window,
17192 cx: &mut Context<Self>,
17193 ) -> Task<Result<()>> {
17194 let buffer = self.buffer.clone();
17195 let buffers = buffer.read(cx).all_buffers();
17196 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17197 let apply_action = project.update(cx, |project, cx| {
17198 project.apply_code_action_kind(buffers, kind, true, cx)
17199 });
17200 cx.spawn_in(window, async move |_, cx| {
17201 let transaction = futures::select_biased! {
17202 () = timeout => {
17203 log::warn!("timed out waiting for executing code action");
17204 None
17205 }
17206 transaction = apply_action.log_err().fuse() => transaction,
17207 };
17208 buffer
17209 .update(cx, |buffer, cx| {
17210 // check if we need this
17211 if let Some(transaction) = transaction
17212 && !buffer.is_singleton()
17213 {
17214 buffer.push_transaction(&transaction.0, cx);
17215 }
17216 cx.notify();
17217 })
17218 .ok();
17219 Ok(())
17220 })
17221 }
17222
17223 pub fn restart_language_server(
17224 &mut self,
17225 _: &RestartLanguageServer,
17226 _: &mut Window,
17227 cx: &mut Context<Self>,
17228 ) {
17229 if let Some(project) = self.project.clone() {
17230 self.buffer.update(cx, |multi_buffer, cx| {
17231 project.update(cx, |project, cx| {
17232 project.restart_language_servers_for_buffers(
17233 multi_buffer.all_buffers().into_iter().collect(),
17234 HashSet::default(),
17235 cx,
17236 );
17237 });
17238 })
17239 }
17240 }
17241
17242 pub fn stop_language_server(
17243 &mut self,
17244 _: &StopLanguageServer,
17245 _: &mut Window,
17246 cx: &mut Context<Self>,
17247 ) {
17248 if let Some(project) = self.project.clone() {
17249 self.buffer.update(cx, |multi_buffer, cx| {
17250 project.update(cx, |project, cx| {
17251 project.stop_language_servers_for_buffers(
17252 multi_buffer.all_buffers().into_iter().collect(),
17253 HashSet::default(),
17254 cx,
17255 );
17256 cx.emit(project::Event::RefreshInlayHints);
17257 });
17258 });
17259 }
17260 }
17261
17262 fn cancel_language_server_work(
17263 workspace: &mut Workspace,
17264 _: &actions::CancelLanguageServerWork,
17265 _: &mut Window,
17266 cx: &mut Context<Workspace>,
17267 ) {
17268 let project = workspace.project();
17269 let buffers = workspace
17270 .active_item(cx)
17271 .and_then(|item| item.act_as::<Editor>(cx))
17272 .map_or(HashSet::default(), |editor| {
17273 editor.read(cx).buffer.read(cx).all_buffers()
17274 });
17275 project.update(cx, |project, cx| {
17276 project.cancel_language_server_work_for_buffers(buffers, cx);
17277 });
17278 }
17279
17280 fn show_character_palette(
17281 &mut self,
17282 _: &ShowCharacterPalette,
17283 window: &mut Window,
17284 _: &mut Context<Self>,
17285 ) {
17286 window.show_character_palette();
17287 }
17288
17289 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17290 if !self.diagnostics_enabled() {
17291 return;
17292 }
17293
17294 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17295 let buffer = self.buffer.read(cx).snapshot(cx);
17296 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17297 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17298 let is_valid = buffer
17299 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17300 .any(|entry| {
17301 entry.diagnostic.is_primary
17302 && !entry.range.is_empty()
17303 && entry.range.start == primary_range_start
17304 && entry.diagnostic.message == active_diagnostics.active_message
17305 });
17306
17307 if !is_valid {
17308 self.dismiss_diagnostics(cx);
17309 }
17310 }
17311 }
17312
17313 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17314 match &self.active_diagnostics {
17315 ActiveDiagnostic::Group(group) => Some(group),
17316 _ => None,
17317 }
17318 }
17319
17320 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17321 if !self.diagnostics_enabled() {
17322 return;
17323 }
17324 self.dismiss_diagnostics(cx);
17325 self.active_diagnostics = ActiveDiagnostic::All;
17326 }
17327
17328 fn activate_diagnostics(
17329 &mut self,
17330 buffer_id: BufferId,
17331 diagnostic: DiagnosticEntry<usize>,
17332 window: &mut Window,
17333 cx: &mut Context<Self>,
17334 ) {
17335 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17336 return;
17337 }
17338 self.dismiss_diagnostics(cx);
17339 let snapshot = self.snapshot(window, cx);
17340 let buffer = self.buffer.read(cx).snapshot(cx);
17341 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17342 return;
17343 };
17344
17345 let diagnostic_group = buffer
17346 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17347 .collect::<Vec<_>>();
17348
17349 let blocks =
17350 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17351
17352 let blocks = self.display_map.update(cx, |display_map, cx| {
17353 display_map.insert_blocks(blocks, cx).into_iter().collect()
17354 });
17355 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17356 active_range: buffer.anchor_before(diagnostic.range.start)
17357 ..buffer.anchor_after(diagnostic.range.end),
17358 active_message: diagnostic.diagnostic.message.clone(),
17359 group_id: diagnostic.diagnostic.group_id,
17360 blocks,
17361 });
17362 cx.notify();
17363 }
17364
17365 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17366 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17367 return;
17368 };
17369
17370 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17371 if let ActiveDiagnostic::Group(group) = prev {
17372 self.display_map.update(cx, |display_map, cx| {
17373 display_map.remove_blocks(group.blocks, cx);
17374 });
17375 cx.notify();
17376 }
17377 }
17378
17379 /// Disable inline diagnostics rendering for this editor.
17380 pub fn disable_inline_diagnostics(&mut self) {
17381 self.inline_diagnostics_enabled = false;
17382 self.inline_diagnostics_update = Task::ready(());
17383 self.inline_diagnostics.clear();
17384 }
17385
17386 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17387 self.diagnostics_enabled = false;
17388 self.dismiss_diagnostics(cx);
17389 self.inline_diagnostics_update = Task::ready(());
17390 self.inline_diagnostics.clear();
17391 }
17392
17393 pub fn disable_word_completions(&mut self) {
17394 self.word_completions_enabled = false;
17395 }
17396
17397 pub fn diagnostics_enabled(&self) -> bool {
17398 self.diagnostics_enabled && self.mode.is_full()
17399 }
17400
17401 pub fn inline_diagnostics_enabled(&self) -> bool {
17402 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17403 }
17404
17405 pub fn show_inline_diagnostics(&self) -> bool {
17406 self.show_inline_diagnostics
17407 }
17408
17409 pub fn toggle_inline_diagnostics(
17410 &mut self,
17411 _: &ToggleInlineDiagnostics,
17412 window: &mut Window,
17413 cx: &mut Context<Editor>,
17414 ) {
17415 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17416 self.refresh_inline_diagnostics(false, window, cx);
17417 }
17418
17419 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17420 self.diagnostics_max_severity = severity;
17421 self.display_map.update(cx, |display_map, _| {
17422 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17423 });
17424 }
17425
17426 pub fn toggle_diagnostics(
17427 &mut self,
17428 _: &ToggleDiagnostics,
17429 window: &mut Window,
17430 cx: &mut Context<Editor>,
17431 ) {
17432 if !self.diagnostics_enabled() {
17433 return;
17434 }
17435
17436 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17437 EditorSettings::get_global(cx)
17438 .diagnostics_max_severity
17439 .filter(|severity| severity != &DiagnosticSeverity::Off)
17440 .unwrap_or(DiagnosticSeverity::Hint)
17441 } else {
17442 DiagnosticSeverity::Off
17443 };
17444 self.set_max_diagnostics_severity(new_severity, cx);
17445 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17446 self.active_diagnostics = ActiveDiagnostic::None;
17447 self.inline_diagnostics_update = Task::ready(());
17448 self.inline_diagnostics.clear();
17449 } else {
17450 self.refresh_inline_diagnostics(false, window, cx);
17451 }
17452
17453 cx.notify();
17454 }
17455
17456 pub fn toggle_minimap(
17457 &mut self,
17458 _: &ToggleMinimap,
17459 window: &mut Window,
17460 cx: &mut Context<Editor>,
17461 ) {
17462 if self.supports_minimap(cx) {
17463 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17464 }
17465 }
17466
17467 fn refresh_inline_diagnostics(
17468 &mut self,
17469 debounce: bool,
17470 window: &mut Window,
17471 cx: &mut Context<Self>,
17472 ) {
17473 let max_severity = ProjectSettings::get_global(cx)
17474 .diagnostics
17475 .inline
17476 .max_severity
17477 .unwrap_or(self.diagnostics_max_severity);
17478
17479 if !self.inline_diagnostics_enabled()
17480 || !self.show_inline_diagnostics
17481 || max_severity == DiagnosticSeverity::Off
17482 {
17483 self.inline_diagnostics_update = Task::ready(());
17484 self.inline_diagnostics.clear();
17485 return;
17486 }
17487
17488 let debounce_ms = ProjectSettings::get_global(cx)
17489 .diagnostics
17490 .inline
17491 .update_debounce_ms;
17492 let debounce = if debounce && debounce_ms > 0 {
17493 Some(Duration::from_millis(debounce_ms))
17494 } else {
17495 None
17496 };
17497 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17498 if let Some(debounce) = debounce {
17499 cx.background_executor().timer(debounce).await;
17500 }
17501 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17502 editor
17503 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17504 .ok()
17505 }) else {
17506 return;
17507 };
17508
17509 let new_inline_diagnostics = cx
17510 .background_spawn(async move {
17511 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17512 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17513 let message = diagnostic_entry
17514 .diagnostic
17515 .message
17516 .split_once('\n')
17517 .map(|(line, _)| line)
17518 .map(SharedString::new)
17519 .unwrap_or_else(|| {
17520 SharedString::from(diagnostic_entry.diagnostic.message)
17521 });
17522 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17523 let (Ok(i) | Err(i)) = inline_diagnostics
17524 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17525 inline_diagnostics.insert(
17526 i,
17527 (
17528 start_anchor,
17529 InlineDiagnostic {
17530 message,
17531 group_id: diagnostic_entry.diagnostic.group_id,
17532 start: diagnostic_entry.range.start.to_point(&snapshot),
17533 is_primary: diagnostic_entry.diagnostic.is_primary,
17534 severity: diagnostic_entry.diagnostic.severity,
17535 },
17536 ),
17537 );
17538 }
17539 inline_diagnostics
17540 })
17541 .await;
17542
17543 editor
17544 .update(cx, |editor, cx| {
17545 editor.inline_diagnostics = new_inline_diagnostics;
17546 cx.notify();
17547 })
17548 .ok();
17549 });
17550 }
17551
17552 fn pull_diagnostics(
17553 &mut self,
17554 buffer_id: Option<BufferId>,
17555 window: &Window,
17556 cx: &mut Context<Self>,
17557 ) -> Option<()> {
17558 if !self.mode().is_full() {
17559 return None;
17560 }
17561 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17562 .diagnostics
17563 .lsp_pull_diagnostics;
17564 if !pull_diagnostics_settings.enabled {
17565 return None;
17566 }
17567 let project = self.project()?.downgrade();
17568 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17569 let mut buffers = self.buffer.read(cx).all_buffers();
17570 if let Some(buffer_id) = buffer_id {
17571 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17572 }
17573
17574 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17575 cx.background_executor().timer(debounce).await;
17576
17577 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17578 buffers
17579 .into_iter()
17580 .filter_map(|buffer| {
17581 project
17582 .update(cx, |project, cx| {
17583 project.lsp_store().update(cx, |lsp_store, cx| {
17584 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17585 })
17586 })
17587 .ok()
17588 })
17589 .collect::<FuturesUnordered<_>>()
17590 }) else {
17591 return;
17592 };
17593
17594 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17595 match pull_task {
17596 Ok(()) => {
17597 if editor
17598 .update_in(cx, |editor, window, cx| {
17599 editor.update_diagnostics_state(window, cx);
17600 })
17601 .is_err()
17602 {
17603 return;
17604 }
17605 }
17606 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17607 }
17608 }
17609 });
17610
17611 Some(())
17612 }
17613
17614 pub fn set_selections_from_remote(
17615 &mut self,
17616 selections: Vec<Selection<Anchor>>,
17617 pending_selection: Option<Selection<Anchor>>,
17618 window: &mut Window,
17619 cx: &mut Context<Self>,
17620 ) {
17621 let old_cursor_position = self.selections.newest_anchor().head();
17622 self.selections.change_with(cx, |s| {
17623 s.select_anchors(selections);
17624 if let Some(pending_selection) = pending_selection {
17625 s.set_pending(pending_selection, SelectMode::Character);
17626 } else {
17627 s.clear_pending();
17628 }
17629 });
17630 self.selections_did_change(
17631 false,
17632 &old_cursor_position,
17633 SelectionEffects::default(),
17634 window,
17635 cx,
17636 );
17637 }
17638
17639 pub fn transact(
17640 &mut self,
17641 window: &mut Window,
17642 cx: &mut Context<Self>,
17643 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17644 ) -> Option<TransactionId> {
17645 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17646 this.start_transaction_at(Instant::now(), window, cx);
17647 update(this, window, cx);
17648 this.end_transaction_at(Instant::now(), cx)
17649 })
17650 }
17651
17652 pub fn start_transaction_at(
17653 &mut self,
17654 now: Instant,
17655 window: &mut Window,
17656 cx: &mut Context<Self>,
17657 ) -> Option<TransactionId> {
17658 self.end_selection(window, cx);
17659 if let Some(tx_id) = self
17660 .buffer
17661 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17662 {
17663 self.selection_history
17664 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
17665 cx.emit(EditorEvent::TransactionBegun {
17666 transaction_id: tx_id,
17667 });
17668 Some(tx_id)
17669 } else {
17670 None
17671 }
17672 }
17673
17674 pub fn end_transaction_at(
17675 &mut self,
17676 now: Instant,
17677 cx: &mut Context<Self>,
17678 ) -> Option<TransactionId> {
17679 if let Some(transaction_id) = self
17680 .buffer
17681 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17682 {
17683 if let Some((_, end_selections)) =
17684 self.selection_history.transaction_mut(transaction_id)
17685 {
17686 *end_selections = Some(self.selections.disjoint_anchors_arc());
17687 } else {
17688 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17689 }
17690
17691 cx.emit(EditorEvent::Edited { transaction_id });
17692 Some(transaction_id)
17693 } else {
17694 None
17695 }
17696 }
17697
17698 pub fn modify_transaction_selection_history(
17699 &mut self,
17700 transaction_id: TransactionId,
17701 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17702 ) -> bool {
17703 self.selection_history
17704 .transaction_mut(transaction_id)
17705 .map(modify)
17706 .is_some()
17707 }
17708
17709 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17710 if self.selection_mark_mode {
17711 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17712 s.move_with(|_, sel| {
17713 sel.collapse_to(sel.head(), SelectionGoal::None);
17714 });
17715 })
17716 }
17717 self.selection_mark_mode = true;
17718 cx.notify();
17719 }
17720
17721 pub fn swap_selection_ends(
17722 &mut self,
17723 _: &actions::SwapSelectionEnds,
17724 window: &mut Window,
17725 cx: &mut Context<Self>,
17726 ) {
17727 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17728 s.move_with(|_, sel| {
17729 if sel.start != sel.end {
17730 sel.reversed = !sel.reversed
17731 }
17732 });
17733 });
17734 self.request_autoscroll(Autoscroll::newest(), cx);
17735 cx.notify();
17736 }
17737
17738 pub fn toggle_focus(
17739 workspace: &mut Workspace,
17740 _: &actions::ToggleFocus,
17741 window: &mut Window,
17742 cx: &mut Context<Workspace>,
17743 ) {
17744 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17745 return;
17746 };
17747 workspace.activate_item(&item, true, true, window, cx);
17748 }
17749
17750 pub fn toggle_fold(
17751 &mut self,
17752 _: &actions::ToggleFold,
17753 window: &mut Window,
17754 cx: &mut Context<Self>,
17755 ) {
17756 if self.is_singleton(cx) {
17757 let selection = self.selections.newest::<Point>(cx);
17758
17759 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17760 let range = if selection.is_empty() {
17761 let point = selection.head().to_display_point(&display_map);
17762 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17763 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17764 .to_point(&display_map);
17765 start..end
17766 } else {
17767 selection.range()
17768 };
17769 if display_map.folds_in_range(range).next().is_some() {
17770 self.unfold_lines(&Default::default(), window, cx)
17771 } else {
17772 self.fold(&Default::default(), window, cx)
17773 }
17774 } else {
17775 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17776 let buffer_ids: HashSet<_> = self
17777 .selections
17778 .disjoint_anchor_ranges()
17779 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17780 .collect();
17781
17782 let should_unfold = buffer_ids
17783 .iter()
17784 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17785
17786 for buffer_id in buffer_ids {
17787 if should_unfold {
17788 self.unfold_buffer(buffer_id, cx);
17789 } else {
17790 self.fold_buffer(buffer_id, cx);
17791 }
17792 }
17793 }
17794 }
17795
17796 pub fn toggle_fold_recursive(
17797 &mut self,
17798 _: &actions::ToggleFoldRecursive,
17799 window: &mut Window,
17800 cx: &mut Context<Self>,
17801 ) {
17802 let selection = self.selections.newest::<Point>(cx);
17803
17804 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17805 let range = if selection.is_empty() {
17806 let point = selection.head().to_display_point(&display_map);
17807 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17808 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17809 .to_point(&display_map);
17810 start..end
17811 } else {
17812 selection.range()
17813 };
17814 if display_map.folds_in_range(range).next().is_some() {
17815 self.unfold_recursive(&Default::default(), window, cx)
17816 } else {
17817 self.fold_recursive(&Default::default(), window, cx)
17818 }
17819 }
17820
17821 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17822 if self.is_singleton(cx) {
17823 let mut to_fold = Vec::new();
17824 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17825 let selections = self.selections.all_adjusted(cx);
17826
17827 for selection in selections {
17828 let range = selection.range().sorted();
17829 let buffer_start_row = range.start.row;
17830
17831 if range.start.row != range.end.row {
17832 let mut found = false;
17833 let mut row = range.start.row;
17834 while row <= range.end.row {
17835 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17836 {
17837 found = true;
17838 row = crease.range().end.row + 1;
17839 to_fold.push(crease);
17840 } else {
17841 row += 1
17842 }
17843 }
17844 if found {
17845 continue;
17846 }
17847 }
17848
17849 for row in (0..=range.start.row).rev() {
17850 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17851 && crease.range().end.row >= buffer_start_row
17852 {
17853 to_fold.push(crease);
17854 if row <= range.start.row {
17855 break;
17856 }
17857 }
17858 }
17859 }
17860
17861 self.fold_creases(to_fold, true, window, cx);
17862 } else {
17863 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17864 let buffer_ids = self
17865 .selections
17866 .disjoint_anchor_ranges()
17867 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17868 .collect::<HashSet<_>>();
17869 for buffer_id in buffer_ids {
17870 self.fold_buffer(buffer_id, cx);
17871 }
17872 }
17873 }
17874
17875 pub fn toggle_fold_all(
17876 &mut self,
17877 _: &actions::ToggleFoldAll,
17878 window: &mut Window,
17879 cx: &mut Context<Self>,
17880 ) {
17881 if self.buffer.read(cx).is_singleton() {
17882 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17883 let has_folds = display_map
17884 .folds_in_range(0..display_map.buffer_snapshot.len())
17885 .next()
17886 .is_some();
17887
17888 if has_folds {
17889 self.unfold_all(&actions::UnfoldAll, window, cx);
17890 } else {
17891 self.fold_all(&actions::FoldAll, window, cx);
17892 }
17893 } else {
17894 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17895 let should_unfold = buffer_ids
17896 .iter()
17897 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17898
17899 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17900 editor
17901 .update_in(cx, |editor, _, cx| {
17902 for buffer_id in buffer_ids {
17903 if should_unfold {
17904 editor.unfold_buffer(buffer_id, cx);
17905 } else {
17906 editor.fold_buffer(buffer_id, cx);
17907 }
17908 }
17909 })
17910 .ok();
17911 });
17912 }
17913 }
17914
17915 fn fold_at_level(
17916 &mut self,
17917 fold_at: &FoldAtLevel,
17918 window: &mut Window,
17919 cx: &mut Context<Self>,
17920 ) {
17921 if !self.buffer.read(cx).is_singleton() {
17922 return;
17923 }
17924
17925 let fold_at_level = fold_at.0;
17926 let snapshot = self.buffer.read(cx).snapshot(cx);
17927 let mut to_fold = Vec::new();
17928 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17929
17930 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17931 while start_row < end_row {
17932 match self
17933 .snapshot(window, cx)
17934 .crease_for_buffer_row(MultiBufferRow(start_row))
17935 {
17936 Some(crease) => {
17937 let nested_start_row = crease.range().start.row + 1;
17938 let nested_end_row = crease.range().end.row;
17939
17940 if current_level < fold_at_level {
17941 stack.push((nested_start_row, nested_end_row, current_level + 1));
17942 } else if current_level == fold_at_level {
17943 to_fold.push(crease);
17944 }
17945
17946 start_row = nested_end_row + 1;
17947 }
17948 None => start_row += 1,
17949 }
17950 }
17951 }
17952
17953 self.fold_creases(to_fold, true, window, cx);
17954 }
17955
17956 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17957 if self.buffer.read(cx).is_singleton() {
17958 let mut fold_ranges = Vec::new();
17959 let snapshot = self.buffer.read(cx).snapshot(cx);
17960
17961 for row in 0..snapshot.max_row().0 {
17962 if let Some(foldable_range) = self
17963 .snapshot(window, cx)
17964 .crease_for_buffer_row(MultiBufferRow(row))
17965 {
17966 fold_ranges.push(foldable_range);
17967 }
17968 }
17969
17970 self.fold_creases(fold_ranges, true, window, cx);
17971 } else {
17972 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17973 editor
17974 .update_in(cx, |editor, _, cx| {
17975 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17976 editor.fold_buffer(buffer_id, cx);
17977 }
17978 })
17979 .ok();
17980 });
17981 }
17982 }
17983
17984 pub fn fold_function_bodies(
17985 &mut self,
17986 _: &actions::FoldFunctionBodies,
17987 window: &mut Window,
17988 cx: &mut Context<Self>,
17989 ) {
17990 let snapshot = self.buffer.read(cx).snapshot(cx);
17991
17992 let ranges = snapshot
17993 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17994 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17995 .collect::<Vec<_>>();
17996
17997 let creases = ranges
17998 .into_iter()
17999 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18000 .collect();
18001
18002 self.fold_creases(creases, true, window, cx);
18003 }
18004
18005 pub fn fold_recursive(
18006 &mut self,
18007 _: &actions::FoldRecursive,
18008 window: &mut Window,
18009 cx: &mut Context<Self>,
18010 ) {
18011 let mut to_fold = Vec::new();
18012 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18013 let selections = self.selections.all_adjusted(cx);
18014
18015 for selection in selections {
18016 let range = selection.range().sorted();
18017 let buffer_start_row = range.start.row;
18018
18019 if range.start.row != range.end.row {
18020 let mut found = false;
18021 for row in range.start.row..=range.end.row {
18022 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18023 found = true;
18024 to_fold.push(crease);
18025 }
18026 }
18027 if found {
18028 continue;
18029 }
18030 }
18031
18032 for row in (0..=range.start.row).rev() {
18033 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18034 if crease.range().end.row >= buffer_start_row {
18035 to_fold.push(crease);
18036 } else {
18037 break;
18038 }
18039 }
18040 }
18041 }
18042
18043 self.fold_creases(to_fold, true, window, cx);
18044 }
18045
18046 pub fn fold_at(
18047 &mut self,
18048 buffer_row: MultiBufferRow,
18049 window: &mut Window,
18050 cx: &mut Context<Self>,
18051 ) {
18052 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18053
18054 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18055 let autoscroll = self
18056 .selections
18057 .all::<Point>(cx)
18058 .iter()
18059 .any(|selection| crease.range().overlaps(&selection.range()));
18060
18061 self.fold_creases(vec![crease], autoscroll, window, cx);
18062 }
18063 }
18064
18065 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18066 if self.is_singleton(cx) {
18067 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18068 let buffer = &display_map.buffer_snapshot;
18069 let selections = self.selections.all::<Point>(cx);
18070 let ranges = selections
18071 .iter()
18072 .map(|s| {
18073 let range = s.display_range(&display_map).sorted();
18074 let mut start = range.start.to_point(&display_map);
18075 let mut end = range.end.to_point(&display_map);
18076 start.column = 0;
18077 end.column = buffer.line_len(MultiBufferRow(end.row));
18078 start..end
18079 })
18080 .collect::<Vec<_>>();
18081
18082 self.unfold_ranges(&ranges, true, true, cx);
18083 } else {
18084 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18085 let buffer_ids = self
18086 .selections
18087 .disjoint_anchor_ranges()
18088 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18089 .collect::<HashSet<_>>();
18090 for buffer_id in buffer_ids {
18091 self.unfold_buffer(buffer_id, cx);
18092 }
18093 }
18094 }
18095
18096 pub fn unfold_recursive(
18097 &mut self,
18098 _: &UnfoldRecursive,
18099 _window: &mut Window,
18100 cx: &mut Context<Self>,
18101 ) {
18102 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18103 let selections = self.selections.all::<Point>(cx);
18104 let ranges = selections
18105 .iter()
18106 .map(|s| {
18107 let mut range = s.display_range(&display_map).sorted();
18108 *range.start.column_mut() = 0;
18109 *range.end.column_mut() = display_map.line_len(range.end.row());
18110 let start = range.start.to_point(&display_map);
18111 let end = range.end.to_point(&display_map);
18112 start..end
18113 })
18114 .collect::<Vec<_>>();
18115
18116 self.unfold_ranges(&ranges, true, true, cx);
18117 }
18118
18119 pub fn unfold_at(
18120 &mut self,
18121 buffer_row: MultiBufferRow,
18122 _window: &mut Window,
18123 cx: &mut Context<Self>,
18124 ) {
18125 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18126
18127 let intersection_range = Point::new(buffer_row.0, 0)
18128 ..Point::new(
18129 buffer_row.0,
18130 display_map.buffer_snapshot.line_len(buffer_row),
18131 );
18132
18133 let autoscroll = self
18134 .selections
18135 .all::<Point>(cx)
18136 .iter()
18137 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18138
18139 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18140 }
18141
18142 pub fn unfold_all(
18143 &mut self,
18144 _: &actions::UnfoldAll,
18145 _window: &mut Window,
18146 cx: &mut Context<Self>,
18147 ) {
18148 if self.buffer.read(cx).is_singleton() {
18149 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18150 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
18151 } else {
18152 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18153 editor
18154 .update(cx, |editor, cx| {
18155 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18156 editor.unfold_buffer(buffer_id, cx);
18157 }
18158 })
18159 .ok();
18160 });
18161 }
18162 }
18163
18164 pub fn fold_selected_ranges(
18165 &mut self,
18166 _: &FoldSelectedRanges,
18167 window: &mut Window,
18168 cx: &mut Context<Self>,
18169 ) {
18170 let selections = self.selections.all_adjusted(cx);
18171 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18172 let ranges = selections
18173 .into_iter()
18174 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18175 .collect::<Vec<_>>();
18176 self.fold_creases(ranges, true, window, cx);
18177 }
18178
18179 pub fn fold_ranges<T: ToOffset + Clone>(
18180 &mut self,
18181 ranges: Vec<Range<T>>,
18182 auto_scroll: bool,
18183 window: &mut Window,
18184 cx: &mut Context<Self>,
18185 ) {
18186 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18187 let ranges = ranges
18188 .into_iter()
18189 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18190 .collect::<Vec<_>>();
18191 self.fold_creases(ranges, auto_scroll, window, cx);
18192 }
18193
18194 pub fn fold_creases<T: ToOffset + Clone>(
18195 &mut self,
18196 creases: Vec<Crease<T>>,
18197 auto_scroll: bool,
18198 _window: &mut Window,
18199 cx: &mut Context<Self>,
18200 ) {
18201 if creases.is_empty() {
18202 return;
18203 }
18204
18205 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18206
18207 if auto_scroll {
18208 self.request_autoscroll(Autoscroll::fit(), cx);
18209 }
18210
18211 cx.notify();
18212
18213 self.scrollbar_marker_state.dirty = true;
18214 self.folds_did_change(cx);
18215 }
18216
18217 /// Removes any folds whose ranges intersect any of the given ranges.
18218 pub fn unfold_ranges<T: ToOffset + Clone>(
18219 &mut self,
18220 ranges: &[Range<T>],
18221 inclusive: bool,
18222 auto_scroll: bool,
18223 cx: &mut Context<Self>,
18224 ) {
18225 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18226 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18227 });
18228 self.folds_did_change(cx);
18229 }
18230
18231 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18232 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18233 return;
18234 }
18235 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18236 self.display_map.update(cx, |display_map, cx| {
18237 display_map.fold_buffers([buffer_id], cx)
18238 });
18239 cx.emit(EditorEvent::BufferFoldToggled {
18240 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18241 folded: true,
18242 });
18243 cx.notify();
18244 }
18245
18246 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18247 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18248 return;
18249 }
18250 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18251 self.display_map.update(cx, |display_map, cx| {
18252 display_map.unfold_buffers([buffer_id], cx);
18253 });
18254 cx.emit(EditorEvent::BufferFoldToggled {
18255 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18256 folded: false,
18257 });
18258 cx.notify();
18259 }
18260
18261 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18262 self.display_map.read(cx).is_buffer_folded(buffer)
18263 }
18264
18265 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18266 self.display_map.read(cx).folded_buffers()
18267 }
18268
18269 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18270 self.display_map.update(cx, |display_map, cx| {
18271 display_map.disable_header_for_buffer(buffer_id, cx);
18272 });
18273 cx.notify();
18274 }
18275
18276 /// Removes any folds with the given ranges.
18277 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18278 &mut self,
18279 ranges: &[Range<T>],
18280 type_id: TypeId,
18281 auto_scroll: bool,
18282 cx: &mut Context<Self>,
18283 ) {
18284 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18285 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18286 });
18287 self.folds_did_change(cx);
18288 }
18289
18290 fn remove_folds_with<T: ToOffset + Clone>(
18291 &mut self,
18292 ranges: &[Range<T>],
18293 auto_scroll: bool,
18294 cx: &mut Context<Self>,
18295 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18296 ) {
18297 if ranges.is_empty() {
18298 return;
18299 }
18300
18301 let mut buffers_affected = HashSet::default();
18302 let multi_buffer = self.buffer().read(cx);
18303 for range in ranges {
18304 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18305 buffers_affected.insert(buffer.read(cx).remote_id());
18306 };
18307 }
18308
18309 self.display_map.update(cx, update);
18310
18311 if auto_scroll {
18312 self.request_autoscroll(Autoscroll::fit(), cx);
18313 }
18314
18315 cx.notify();
18316 self.scrollbar_marker_state.dirty = true;
18317 self.active_indent_guides_state.dirty = true;
18318 }
18319
18320 pub fn update_renderer_widths(
18321 &mut self,
18322 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18323 cx: &mut Context<Self>,
18324 ) -> bool {
18325 self.display_map
18326 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18327 }
18328
18329 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18330 self.display_map.read(cx).fold_placeholder.clone()
18331 }
18332
18333 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18334 self.buffer.update(cx, |buffer, cx| {
18335 buffer.set_all_diff_hunks_expanded(cx);
18336 });
18337 }
18338
18339 pub fn expand_all_diff_hunks(
18340 &mut self,
18341 _: &ExpandAllDiffHunks,
18342 _window: &mut Window,
18343 cx: &mut Context<Self>,
18344 ) {
18345 self.buffer.update(cx, |buffer, cx| {
18346 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18347 });
18348 }
18349
18350 pub fn toggle_selected_diff_hunks(
18351 &mut self,
18352 _: &ToggleSelectedDiffHunks,
18353 _window: &mut Window,
18354 cx: &mut Context<Self>,
18355 ) {
18356 let ranges: Vec<_> = self
18357 .selections
18358 .disjoint_anchors()
18359 .iter()
18360 .map(|s| s.range())
18361 .collect();
18362 self.toggle_diff_hunks_in_ranges(ranges, cx);
18363 }
18364
18365 pub fn diff_hunks_in_ranges<'a>(
18366 &'a self,
18367 ranges: &'a [Range<Anchor>],
18368 buffer: &'a MultiBufferSnapshot,
18369 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18370 ranges.iter().flat_map(move |range| {
18371 let end_excerpt_id = range.end.excerpt_id;
18372 let range = range.to_point(buffer);
18373 let mut peek_end = range.end;
18374 if range.end.row < buffer.max_row().0 {
18375 peek_end = Point::new(range.end.row + 1, 0);
18376 }
18377 buffer
18378 .diff_hunks_in_range(range.start..peek_end)
18379 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18380 })
18381 }
18382
18383 pub fn has_stageable_diff_hunks_in_ranges(
18384 &self,
18385 ranges: &[Range<Anchor>],
18386 snapshot: &MultiBufferSnapshot,
18387 ) -> bool {
18388 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18389 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18390 }
18391
18392 pub fn toggle_staged_selected_diff_hunks(
18393 &mut self,
18394 _: &::git::ToggleStaged,
18395 _: &mut Window,
18396 cx: &mut Context<Self>,
18397 ) {
18398 let snapshot = self.buffer.read(cx).snapshot(cx);
18399 let ranges: Vec<_> = self
18400 .selections
18401 .disjoint_anchors()
18402 .iter()
18403 .map(|s| s.range())
18404 .collect();
18405 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18406 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18407 }
18408
18409 pub fn set_render_diff_hunk_controls(
18410 &mut self,
18411 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18412 cx: &mut Context<Self>,
18413 ) {
18414 self.render_diff_hunk_controls = render_diff_hunk_controls;
18415 cx.notify();
18416 }
18417
18418 pub fn stage_and_next(
18419 &mut self,
18420 _: &::git::StageAndNext,
18421 window: &mut Window,
18422 cx: &mut Context<Self>,
18423 ) {
18424 self.do_stage_or_unstage_and_next(true, window, cx);
18425 }
18426
18427 pub fn unstage_and_next(
18428 &mut self,
18429 _: &::git::UnstageAndNext,
18430 window: &mut Window,
18431 cx: &mut Context<Self>,
18432 ) {
18433 self.do_stage_or_unstage_and_next(false, window, cx);
18434 }
18435
18436 pub fn stage_or_unstage_diff_hunks(
18437 &mut self,
18438 stage: bool,
18439 ranges: Vec<Range<Anchor>>,
18440 cx: &mut Context<Self>,
18441 ) {
18442 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18443 cx.spawn(async move |this, cx| {
18444 task.await?;
18445 this.update(cx, |this, cx| {
18446 let snapshot = this.buffer.read(cx).snapshot(cx);
18447 let chunk_by = this
18448 .diff_hunks_in_ranges(&ranges, &snapshot)
18449 .chunk_by(|hunk| hunk.buffer_id);
18450 for (buffer_id, hunks) in &chunk_by {
18451 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18452 }
18453 })
18454 })
18455 .detach_and_log_err(cx);
18456 }
18457
18458 fn save_buffers_for_ranges_if_needed(
18459 &mut self,
18460 ranges: &[Range<Anchor>],
18461 cx: &mut Context<Editor>,
18462 ) -> Task<Result<()>> {
18463 let multibuffer = self.buffer.read(cx);
18464 let snapshot = multibuffer.read(cx);
18465 let buffer_ids: HashSet<_> = ranges
18466 .iter()
18467 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18468 .collect();
18469 drop(snapshot);
18470
18471 let mut buffers = HashSet::default();
18472 for buffer_id in buffer_ids {
18473 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18474 let buffer = buffer_entity.read(cx);
18475 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18476 {
18477 buffers.insert(buffer_entity);
18478 }
18479 }
18480 }
18481
18482 if let Some(project) = &self.project {
18483 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18484 } else {
18485 Task::ready(Ok(()))
18486 }
18487 }
18488
18489 fn do_stage_or_unstage_and_next(
18490 &mut self,
18491 stage: bool,
18492 window: &mut Window,
18493 cx: &mut Context<Self>,
18494 ) {
18495 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18496
18497 if ranges.iter().any(|range| range.start != range.end) {
18498 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18499 return;
18500 }
18501
18502 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18503 let snapshot = self.snapshot(window, cx);
18504 let position = self.selections.newest::<Point>(cx).head();
18505 let mut row = snapshot
18506 .buffer_snapshot
18507 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
18508 .find(|hunk| hunk.row_range.start.0 > position.row)
18509 .map(|hunk| hunk.row_range.start);
18510
18511 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18512 // Outside of the project diff editor, wrap around to the beginning.
18513 if !all_diff_hunks_expanded {
18514 row = row.or_else(|| {
18515 snapshot
18516 .buffer_snapshot
18517 .diff_hunks_in_range(Point::zero()..position)
18518 .find(|hunk| hunk.row_range.end.0 < position.row)
18519 .map(|hunk| hunk.row_range.start)
18520 });
18521 }
18522
18523 if let Some(row) = row {
18524 let destination = Point::new(row.0, 0);
18525 let autoscroll = Autoscroll::center();
18526
18527 self.unfold_ranges(&[destination..destination], false, false, cx);
18528 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18529 s.select_ranges([destination..destination]);
18530 });
18531 }
18532 }
18533
18534 fn do_stage_or_unstage(
18535 &self,
18536 stage: bool,
18537 buffer_id: BufferId,
18538 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18539 cx: &mut App,
18540 ) -> Option<()> {
18541 let project = self.project()?;
18542 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18543 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18544 let buffer_snapshot = buffer.read(cx).snapshot();
18545 let file_exists = buffer_snapshot
18546 .file()
18547 .is_some_and(|file| file.disk_state().exists());
18548 diff.update(cx, |diff, cx| {
18549 diff.stage_or_unstage_hunks(
18550 stage,
18551 &hunks
18552 .map(|hunk| buffer_diff::DiffHunk {
18553 buffer_range: hunk.buffer_range,
18554 diff_base_byte_range: hunk.diff_base_byte_range,
18555 secondary_status: hunk.secondary_status,
18556 range: Point::zero()..Point::zero(), // unused
18557 })
18558 .collect::<Vec<_>>(),
18559 &buffer_snapshot,
18560 file_exists,
18561 cx,
18562 )
18563 });
18564 None
18565 }
18566
18567 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18568 let ranges: Vec<_> = self
18569 .selections
18570 .disjoint_anchors()
18571 .iter()
18572 .map(|s| s.range())
18573 .collect();
18574 self.buffer
18575 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18576 }
18577
18578 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18579 self.buffer.update(cx, |buffer, cx| {
18580 let ranges = vec![Anchor::min()..Anchor::max()];
18581 if !buffer.all_diff_hunks_expanded()
18582 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18583 {
18584 buffer.collapse_diff_hunks(ranges, cx);
18585 true
18586 } else {
18587 false
18588 }
18589 })
18590 }
18591
18592 fn toggle_diff_hunks_in_ranges(
18593 &mut self,
18594 ranges: Vec<Range<Anchor>>,
18595 cx: &mut Context<Editor>,
18596 ) {
18597 self.buffer.update(cx, |buffer, cx| {
18598 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18599 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18600 })
18601 }
18602
18603 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18604 self.buffer.update(cx, |buffer, cx| {
18605 let snapshot = buffer.snapshot(cx);
18606 let excerpt_id = range.end.excerpt_id;
18607 let point_range = range.to_point(&snapshot);
18608 let expand = !buffer.single_hunk_is_expanded(range, cx);
18609 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18610 })
18611 }
18612
18613 pub(crate) fn apply_all_diff_hunks(
18614 &mut self,
18615 _: &ApplyAllDiffHunks,
18616 window: &mut Window,
18617 cx: &mut Context<Self>,
18618 ) {
18619 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18620
18621 let buffers = self.buffer.read(cx).all_buffers();
18622 for branch_buffer in buffers {
18623 branch_buffer.update(cx, |branch_buffer, cx| {
18624 branch_buffer.merge_into_base(Vec::new(), cx);
18625 });
18626 }
18627
18628 if let Some(project) = self.project.clone() {
18629 self.save(
18630 SaveOptions {
18631 format: true,
18632 autosave: false,
18633 },
18634 project,
18635 window,
18636 cx,
18637 )
18638 .detach_and_log_err(cx);
18639 }
18640 }
18641
18642 pub(crate) fn apply_selected_diff_hunks(
18643 &mut self,
18644 _: &ApplyDiffHunk,
18645 window: &mut Window,
18646 cx: &mut Context<Self>,
18647 ) {
18648 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18649 let snapshot = self.snapshot(window, cx);
18650 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18651 let mut ranges_by_buffer = HashMap::default();
18652 self.transact(window, cx, |editor, _window, cx| {
18653 for hunk in hunks {
18654 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18655 ranges_by_buffer
18656 .entry(buffer.clone())
18657 .or_insert_with(Vec::new)
18658 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18659 }
18660 }
18661
18662 for (buffer, ranges) in ranges_by_buffer {
18663 buffer.update(cx, |buffer, cx| {
18664 buffer.merge_into_base(ranges, cx);
18665 });
18666 }
18667 });
18668
18669 if let Some(project) = self.project.clone() {
18670 self.save(
18671 SaveOptions {
18672 format: true,
18673 autosave: false,
18674 },
18675 project,
18676 window,
18677 cx,
18678 )
18679 .detach_and_log_err(cx);
18680 }
18681 }
18682
18683 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18684 if hovered != self.gutter_hovered {
18685 self.gutter_hovered = hovered;
18686 cx.notify();
18687 }
18688 }
18689
18690 pub fn insert_blocks(
18691 &mut self,
18692 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18693 autoscroll: Option<Autoscroll>,
18694 cx: &mut Context<Self>,
18695 ) -> Vec<CustomBlockId> {
18696 let blocks = self
18697 .display_map
18698 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18699 if let Some(autoscroll) = autoscroll {
18700 self.request_autoscroll(autoscroll, cx);
18701 }
18702 cx.notify();
18703 blocks
18704 }
18705
18706 pub fn resize_blocks(
18707 &mut self,
18708 heights: HashMap<CustomBlockId, u32>,
18709 autoscroll: Option<Autoscroll>,
18710 cx: &mut Context<Self>,
18711 ) {
18712 self.display_map
18713 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18714 if let Some(autoscroll) = autoscroll {
18715 self.request_autoscroll(autoscroll, cx);
18716 }
18717 cx.notify();
18718 }
18719
18720 pub fn replace_blocks(
18721 &mut self,
18722 renderers: HashMap<CustomBlockId, RenderBlock>,
18723 autoscroll: Option<Autoscroll>,
18724 cx: &mut Context<Self>,
18725 ) {
18726 self.display_map
18727 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18728 if let Some(autoscroll) = autoscroll {
18729 self.request_autoscroll(autoscroll, cx);
18730 }
18731 cx.notify();
18732 }
18733
18734 pub fn remove_blocks(
18735 &mut self,
18736 block_ids: HashSet<CustomBlockId>,
18737 autoscroll: Option<Autoscroll>,
18738 cx: &mut Context<Self>,
18739 ) {
18740 self.display_map.update(cx, |display_map, cx| {
18741 display_map.remove_blocks(block_ids, cx)
18742 });
18743 if let Some(autoscroll) = autoscroll {
18744 self.request_autoscroll(autoscroll, cx);
18745 }
18746 cx.notify();
18747 }
18748
18749 pub fn row_for_block(
18750 &self,
18751 block_id: CustomBlockId,
18752 cx: &mut Context<Self>,
18753 ) -> Option<DisplayRow> {
18754 self.display_map
18755 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18756 }
18757
18758 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18759 self.focused_block = Some(focused_block);
18760 }
18761
18762 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18763 self.focused_block.take()
18764 }
18765
18766 pub fn insert_creases(
18767 &mut self,
18768 creases: impl IntoIterator<Item = Crease<Anchor>>,
18769 cx: &mut Context<Self>,
18770 ) -> Vec<CreaseId> {
18771 self.display_map
18772 .update(cx, |map, cx| map.insert_creases(creases, cx))
18773 }
18774
18775 pub fn remove_creases(
18776 &mut self,
18777 ids: impl IntoIterator<Item = CreaseId>,
18778 cx: &mut Context<Self>,
18779 ) -> Vec<(CreaseId, Range<Anchor>)> {
18780 self.display_map
18781 .update(cx, |map, cx| map.remove_creases(ids, cx))
18782 }
18783
18784 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18785 self.display_map
18786 .update(cx, |map, cx| map.snapshot(cx))
18787 .longest_row()
18788 }
18789
18790 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18791 self.display_map
18792 .update(cx, |map, cx| map.snapshot(cx))
18793 .max_point()
18794 }
18795
18796 pub fn text(&self, cx: &App) -> String {
18797 self.buffer.read(cx).read(cx).text()
18798 }
18799
18800 pub fn is_empty(&self, cx: &App) -> bool {
18801 self.buffer.read(cx).read(cx).is_empty()
18802 }
18803
18804 pub fn text_option(&self, cx: &App) -> Option<String> {
18805 let text = self.text(cx);
18806 let text = text.trim();
18807
18808 if text.is_empty() {
18809 return None;
18810 }
18811
18812 Some(text.to_string())
18813 }
18814
18815 pub fn set_text(
18816 &mut self,
18817 text: impl Into<Arc<str>>,
18818 window: &mut Window,
18819 cx: &mut Context<Self>,
18820 ) {
18821 self.transact(window, cx, |this, _, cx| {
18822 this.buffer
18823 .read(cx)
18824 .as_singleton()
18825 .expect("you can only call set_text on editors for singleton buffers")
18826 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18827 });
18828 }
18829
18830 pub fn display_text(&self, cx: &mut App) -> String {
18831 self.display_map
18832 .update(cx, |map, cx| map.snapshot(cx))
18833 .text()
18834 }
18835
18836 fn create_minimap(
18837 &self,
18838 minimap_settings: MinimapSettings,
18839 window: &mut Window,
18840 cx: &mut Context<Self>,
18841 ) -> Option<Entity<Self>> {
18842 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18843 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18844 }
18845
18846 fn initialize_new_minimap(
18847 &self,
18848 minimap_settings: MinimapSettings,
18849 window: &mut Window,
18850 cx: &mut Context<Self>,
18851 ) -> Entity<Self> {
18852 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18853
18854 let mut minimap = Editor::new_internal(
18855 EditorMode::Minimap {
18856 parent: cx.weak_entity(),
18857 },
18858 self.buffer.clone(),
18859 None,
18860 Some(self.display_map.clone()),
18861 window,
18862 cx,
18863 );
18864 minimap.scroll_manager.clone_state(&self.scroll_manager);
18865 minimap.set_text_style_refinement(TextStyleRefinement {
18866 font_size: Some(MINIMAP_FONT_SIZE),
18867 font_weight: Some(MINIMAP_FONT_WEIGHT),
18868 ..Default::default()
18869 });
18870 minimap.update_minimap_configuration(minimap_settings, cx);
18871 cx.new(|_| minimap)
18872 }
18873
18874 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18875 let current_line_highlight = minimap_settings
18876 .current_line_highlight
18877 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18878 self.set_current_line_highlight(Some(current_line_highlight));
18879 }
18880
18881 pub fn minimap(&self) -> Option<&Entity<Self>> {
18882 self.minimap
18883 .as_ref()
18884 .filter(|_| self.minimap_visibility.visible())
18885 }
18886
18887 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18888 let mut wrap_guides = smallvec![];
18889
18890 if self.show_wrap_guides == Some(false) {
18891 return wrap_guides;
18892 }
18893
18894 let settings = self.buffer.read(cx).language_settings(cx);
18895 if settings.show_wrap_guides {
18896 match self.soft_wrap_mode(cx) {
18897 SoftWrap::Column(soft_wrap) => {
18898 wrap_guides.push((soft_wrap as usize, true));
18899 }
18900 SoftWrap::Bounded(soft_wrap) => {
18901 wrap_guides.push((soft_wrap as usize, true));
18902 }
18903 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18904 }
18905 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18906 }
18907
18908 wrap_guides
18909 }
18910
18911 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18912 let settings = self.buffer.read(cx).language_settings(cx);
18913 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18914 match mode {
18915 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18916 SoftWrap::None
18917 }
18918 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18919 language_settings::SoftWrap::PreferredLineLength => {
18920 SoftWrap::Column(settings.preferred_line_length)
18921 }
18922 language_settings::SoftWrap::Bounded => {
18923 SoftWrap::Bounded(settings.preferred_line_length)
18924 }
18925 }
18926 }
18927
18928 pub fn set_soft_wrap_mode(
18929 &mut self,
18930 mode: language_settings::SoftWrap,
18931
18932 cx: &mut Context<Self>,
18933 ) {
18934 self.soft_wrap_mode_override = Some(mode);
18935 cx.notify();
18936 }
18937
18938 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18939 self.hard_wrap = hard_wrap;
18940 cx.notify();
18941 }
18942
18943 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18944 self.text_style_refinement = Some(style);
18945 }
18946
18947 /// called by the Element so we know what style we were most recently rendered with.
18948 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
18949 // We intentionally do not inform the display map about the minimap style
18950 // so that wrapping is not recalculated and stays consistent for the editor
18951 // and its linked minimap.
18952 if !self.mode.is_minimap() {
18953 let font = style.text.font();
18954 let font_size = style.text.font_size.to_pixels(window.rem_size());
18955 let display_map = self
18956 .placeholder_display_map
18957 .as_ref()
18958 .filter(|_| self.is_empty(cx))
18959 .unwrap_or(&self.display_map);
18960
18961 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
18962 }
18963 self.style = Some(style);
18964 }
18965
18966 pub fn style(&self) -> Option<&EditorStyle> {
18967 self.style.as_ref()
18968 }
18969
18970 // Called by the element. This method is not designed to be called outside of the editor
18971 // element's layout code because it does not notify when rewrapping is computed synchronously.
18972 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18973 if self.is_empty(cx) {
18974 self.placeholder_display_map
18975 .as_ref()
18976 .map_or(false, |display_map| {
18977 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
18978 })
18979 } else {
18980 self.display_map
18981 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18982 }
18983 }
18984
18985 pub fn set_soft_wrap(&mut self) {
18986 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18987 }
18988
18989 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18990 if self.soft_wrap_mode_override.is_some() {
18991 self.soft_wrap_mode_override.take();
18992 } else {
18993 let soft_wrap = match self.soft_wrap_mode(cx) {
18994 SoftWrap::GitDiff => return,
18995 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18996 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18997 language_settings::SoftWrap::None
18998 }
18999 };
19000 self.soft_wrap_mode_override = Some(soft_wrap);
19001 }
19002 cx.notify();
19003 }
19004
19005 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19006 let Some(workspace) = self.workspace() else {
19007 return;
19008 };
19009 let fs = workspace.read(cx).app_state().fs.clone();
19010 let current_show = TabBarSettings::get_global(cx).show;
19011 update_settings_file(fs, cx, move |setting, _| {
19012 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19013 });
19014 }
19015
19016 pub fn toggle_indent_guides(
19017 &mut self,
19018 _: &ToggleIndentGuides,
19019 _: &mut Window,
19020 cx: &mut Context<Self>,
19021 ) {
19022 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19023 self.buffer
19024 .read(cx)
19025 .language_settings(cx)
19026 .indent_guides
19027 .enabled
19028 });
19029 self.show_indent_guides = Some(!currently_enabled);
19030 cx.notify();
19031 }
19032
19033 fn should_show_indent_guides(&self) -> Option<bool> {
19034 self.show_indent_guides
19035 }
19036
19037 pub fn toggle_line_numbers(
19038 &mut self,
19039 _: &ToggleLineNumbers,
19040 _: &mut Window,
19041 cx: &mut Context<Self>,
19042 ) {
19043 let mut editor_settings = EditorSettings::get_global(cx).clone();
19044 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19045 EditorSettings::override_global(editor_settings, cx);
19046 }
19047
19048 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19049 if let Some(show_line_numbers) = self.show_line_numbers {
19050 return show_line_numbers;
19051 }
19052 EditorSettings::get_global(cx).gutter.line_numbers
19053 }
19054
19055 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19056 self.use_relative_line_numbers
19057 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19058 }
19059
19060 pub fn toggle_relative_line_numbers(
19061 &mut self,
19062 _: &ToggleRelativeLineNumbers,
19063 _: &mut Window,
19064 cx: &mut Context<Self>,
19065 ) {
19066 let is_relative = self.should_use_relative_line_numbers(cx);
19067 self.set_relative_line_number(Some(!is_relative), cx)
19068 }
19069
19070 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19071 self.use_relative_line_numbers = is_relative;
19072 cx.notify();
19073 }
19074
19075 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19076 self.show_gutter = show_gutter;
19077 cx.notify();
19078 }
19079
19080 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19081 self.show_scrollbars = ScrollbarAxes {
19082 horizontal: show,
19083 vertical: show,
19084 };
19085 cx.notify();
19086 }
19087
19088 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19089 self.show_scrollbars.vertical = show;
19090 cx.notify();
19091 }
19092
19093 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19094 self.show_scrollbars.horizontal = show;
19095 cx.notify();
19096 }
19097
19098 pub fn set_minimap_visibility(
19099 &mut self,
19100 minimap_visibility: MinimapVisibility,
19101 window: &mut Window,
19102 cx: &mut Context<Self>,
19103 ) {
19104 if self.minimap_visibility != minimap_visibility {
19105 if minimap_visibility.visible() && self.minimap.is_none() {
19106 let minimap_settings = EditorSettings::get_global(cx).minimap;
19107 self.minimap =
19108 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19109 }
19110 self.minimap_visibility = minimap_visibility;
19111 cx.notify();
19112 }
19113 }
19114
19115 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19116 self.set_show_scrollbars(false, cx);
19117 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19118 }
19119
19120 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19121 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19122 }
19123
19124 /// Normally the text in full mode and auto height editors is padded on the
19125 /// left side by roughly half a character width for improved hit testing.
19126 ///
19127 /// Use this method to disable this for cases where this is not wanted (e.g.
19128 /// if you want to align the editor text with some other text above or below)
19129 /// or if you want to add this padding to single-line editors.
19130 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19131 self.offset_content = offset_content;
19132 cx.notify();
19133 }
19134
19135 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19136 self.show_line_numbers = Some(show_line_numbers);
19137 cx.notify();
19138 }
19139
19140 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19141 self.disable_expand_excerpt_buttons = true;
19142 cx.notify();
19143 }
19144
19145 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19146 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19147 cx.notify();
19148 }
19149
19150 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19151 self.show_code_actions = Some(show_code_actions);
19152 cx.notify();
19153 }
19154
19155 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19156 self.show_runnables = Some(show_runnables);
19157 cx.notify();
19158 }
19159
19160 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19161 self.show_breakpoints = Some(show_breakpoints);
19162 cx.notify();
19163 }
19164
19165 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19166 if self.display_map.read(cx).masked != masked {
19167 self.display_map.update(cx, |map, _| map.masked = masked);
19168 }
19169 cx.notify()
19170 }
19171
19172 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19173 self.show_wrap_guides = Some(show_wrap_guides);
19174 cx.notify();
19175 }
19176
19177 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19178 self.show_indent_guides = Some(show_indent_guides);
19179 cx.notify();
19180 }
19181
19182 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19183 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19184 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19185 && let Some(dir) = file.abs_path(cx).parent()
19186 {
19187 return Some(dir.to_owned());
19188 }
19189
19190 if let Some(project_path) = buffer.read(cx).project_path(cx) {
19191 return Some(project_path.path.to_path_buf());
19192 }
19193 }
19194
19195 None
19196 }
19197
19198 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19199 self.active_excerpt(cx)?
19200 .1
19201 .read(cx)
19202 .file()
19203 .and_then(|f| f.as_local())
19204 }
19205
19206 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19207 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19208 let buffer = buffer.read(cx);
19209 if let Some(project_path) = buffer.project_path(cx) {
19210 let project = self.project()?.read(cx);
19211 project.absolute_path(&project_path, cx)
19212 } else {
19213 buffer
19214 .file()
19215 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19216 }
19217 })
19218 }
19219
19220 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19221 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19222 let project_path = buffer.read(cx).project_path(cx)?;
19223 let project = self.project()?.read(cx);
19224 let entry = project.entry_for_path(&project_path, cx)?;
19225 let path = entry.path.to_path_buf();
19226 Some(path)
19227 })
19228 }
19229
19230 pub fn reveal_in_finder(
19231 &mut self,
19232 _: &RevealInFileManager,
19233 _window: &mut Window,
19234 cx: &mut Context<Self>,
19235 ) {
19236 if let Some(target) = self.target_file(cx) {
19237 cx.reveal_path(&target.abs_path(cx));
19238 }
19239 }
19240
19241 pub fn copy_path(
19242 &mut self,
19243 _: &zed_actions::workspace::CopyPath,
19244 _window: &mut Window,
19245 cx: &mut Context<Self>,
19246 ) {
19247 if let Some(path) = self.target_file_abs_path(cx)
19248 && let Some(path) = path.to_str()
19249 {
19250 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19251 }
19252 }
19253
19254 pub fn copy_relative_path(
19255 &mut self,
19256 _: &zed_actions::workspace::CopyRelativePath,
19257 _window: &mut Window,
19258 cx: &mut Context<Self>,
19259 ) {
19260 if let Some(path) = self.target_file_path(cx)
19261 && let Some(path) = path.to_str()
19262 {
19263 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19264 }
19265 }
19266
19267 /// Returns the project path for the editor's buffer, if any buffer is
19268 /// opened in the editor.
19269 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19270 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19271 buffer.read(cx).project_path(cx)
19272 } else {
19273 None
19274 }
19275 }
19276
19277 // Returns true if the editor handled a go-to-line request
19278 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19279 maybe!({
19280 let breakpoint_store = self.breakpoint_store.as_ref()?;
19281
19282 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19283 else {
19284 self.clear_row_highlights::<ActiveDebugLine>();
19285 return None;
19286 };
19287
19288 let position = active_stack_frame.position;
19289 let buffer_id = position.buffer_id?;
19290 let snapshot = self
19291 .project
19292 .as_ref()?
19293 .read(cx)
19294 .buffer_for_id(buffer_id, cx)?
19295 .read(cx)
19296 .snapshot();
19297
19298 let mut handled = false;
19299 for (id, ExcerptRange { context, .. }) in
19300 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19301 {
19302 if context.start.cmp(&position, &snapshot).is_ge()
19303 || context.end.cmp(&position, &snapshot).is_lt()
19304 {
19305 continue;
19306 }
19307 let snapshot = self.buffer.read(cx).snapshot(cx);
19308 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19309
19310 handled = true;
19311 self.clear_row_highlights::<ActiveDebugLine>();
19312
19313 self.go_to_line::<ActiveDebugLine>(
19314 multibuffer_anchor,
19315 Some(cx.theme().colors().editor_debugger_active_line_background),
19316 window,
19317 cx,
19318 );
19319
19320 cx.notify();
19321 }
19322
19323 handled.then_some(())
19324 })
19325 .is_some()
19326 }
19327
19328 pub fn copy_file_name_without_extension(
19329 &mut self,
19330 _: &CopyFileNameWithoutExtension,
19331 _: &mut Window,
19332 cx: &mut Context<Self>,
19333 ) {
19334 if let Some(file) = self.target_file(cx)
19335 && let Some(file_stem) = file.path().file_stem()
19336 && let Some(name) = file_stem.to_str()
19337 {
19338 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19339 }
19340 }
19341
19342 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19343 if let Some(file) = self.target_file(cx)
19344 && let Some(file_name) = file.path().file_name()
19345 && let Some(name) = file_name.to_str()
19346 {
19347 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19348 }
19349 }
19350
19351 pub fn toggle_git_blame(
19352 &mut self,
19353 _: &::git::Blame,
19354 window: &mut Window,
19355 cx: &mut Context<Self>,
19356 ) {
19357 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19358
19359 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19360 self.start_git_blame(true, window, cx);
19361 }
19362
19363 cx.notify();
19364 }
19365
19366 pub fn toggle_git_blame_inline(
19367 &mut self,
19368 _: &ToggleGitBlameInline,
19369 window: &mut Window,
19370 cx: &mut Context<Self>,
19371 ) {
19372 self.toggle_git_blame_inline_internal(true, window, cx);
19373 cx.notify();
19374 }
19375
19376 pub fn open_git_blame_commit(
19377 &mut self,
19378 _: &OpenGitBlameCommit,
19379 window: &mut Window,
19380 cx: &mut Context<Self>,
19381 ) {
19382 self.open_git_blame_commit_internal(window, cx);
19383 }
19384
19385 fn open_git_blame_commit_internal(
19386 &mut self,
19387 window: &mut Window,
19388 cx: &mut Context<Self>,
19389 ) -> Option<()> {
19390 let blame = self.blame.as_ref()?;
19391 let snapshot = self.snapshot(window, cx);
19392 let cursor = self.selections.newest::<Point>(cx).head();
19393 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
19394 let (_, blame_entry) = blame
19395 .update(cx, |blame, cx| {
19396 blame
19397 .blame_for_rows(
19398 &[RowInfo {
19399 buffer_id: Some(buffer.remote_id()),
19400 buffer_row: Some(point.row),
19401 ..Default::default()
19402 }],
19403 cx,
19404 )
19405 .next()
19406 })
19407 .flatten()?;
19408 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19409 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19410 let workspace = self.workspace()?.downgrade();
19411 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19412 None
19413 }
19414
19415 pub fn git_blame_inline_enabled(&self) -> bool {
19416 self.git_blame_inline_enabled
19417 }
19418
19419 pub fn toggle_selection_menu(
19420 &mut self,
19421 _: &ToggleSelectionMenu,
19422 _: &mut Window,
19423 cx: &mut Context<Self>,
19424 ) {
19425 self.show_selection_menu = self
19426 .show_selection_menu
19427 .map(|show_selections_menu| !show_selections_menu)
19428 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19429
19430 cx.notify();
19431 }
19432
19433 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19434 self.show_selection_menu
19435 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19436 }
19437
19438 fn start_git_blame(
19439 &mut self,
19440 user_triggered: bool,
19441 window: &mut Window,
19442 cx: &mut Context<Self>,
19443 ) {
19444 if let Some(project) = self.project() {
19445 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19446 && buffer.read(cx).file().is_none()
19447 {
19448 return;
19449 }
19450
19451 let focused = self.focus_handle(cx).contains_focused(window, cx);
19452
19453 let project = project.clone();
19454 let blame = cx
19455 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19456 self.blame_subscription =
19457 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19458 self.blame = Some(blame);
19459 }
19460 }
19461
19462 fn toggle_git_blame_inline_internal(
19463 &mut self,
19464 user_triggered: bool,
19465 window: &mut Window,
19466 cx: &mut Context<Self>,
19467 ) {
19468 if self.git_blame_inline_enabled {
19469 self.git_blame_inline_enabled = false;
19470 self.show_git_blame_inline = false;
19471 self.show_git_blame_inline_delay_task.take();
19472 } else {
19473 self.git_blame_inline_enabled = true;
19474 self.start_git_blame_inline(user_triggered, window, cx);
19475 }
19476
19477 cx.notify();
19478 }
19479
19480 fn start_git_blame_inline(
19481 &mut self,
19482 user_triggered: bool,
19483 window: &mut Window,
19484 cx: &mut Context<Self>,
19485 ) {
19486 self.start_git_blame(user_triggered, window, cx);
19487
19488 if ProjectSettings::get_global(cx)
19489 .git
19490 .inline_blame_delay()
19491 .is_some()
19492 {
19493 self.start_inline_blame_timer(window, cx);
19494 } else {
19495 self.show_git_blame_inline = true
19496 }
19497 }
19498
19499 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19500 self.blame.as_ref()
19501 }
19502
19503 pub fn show_git_blame_gutter(&self) -> bool {
19504 self.show_git_blame_gutter
19505 }
19506
19507 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19508 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19509 }
19510
19511 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19512 self.show_git_blame_inline
19513 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19514 && !self.newest_selection_head_on_empty_line(cx)
19515 && self.has_blame_entries(cx)
19516 }
19517
19518 fn has_blame_entries(&self, cx: &App) -> bool {
19519 self.blame()
19520 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19521 }
19522
19523 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19524 let cursor_anchor = self.selections.newest_anchor().head();
19525
19526 let snapshot = self.buffer.read(cx).snapshot(cx);
19527 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19528
19529 snapshot.line_len(buffer_row) == 0
19530 }
19531
19532 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19533 let buffer_and_selection = maybe!({
19534 let selection = self.selections.newest::<Point>(cx);
19535 let selection_range = selection.range();
19536
19537 let multi_buffer = self.buffer().read(cx);
19538 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19539 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19540
19541 let (buffer, range, _) = if selection.reversed {
19542 buffer_ranges.first()
19543 } else {
19544 buffer_ranges.last()
19545 }?;
19546
19547 let selection = text::ToPoint::to_point(&range.start, buffer).row
19548 ..text::ToPoint::to_point(&range.end, buffer).row;
19549 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19550 });
19551
19552 let Some((buffer, selection)) = buffer_and_selection else {
19553 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19554 };
19555
19556 let Some(project) = self.project() else {
19557 return Task::ready(Err(anyhow!("editor does not have project")));
19558 };
19559
19560 project.update(cx, |project, cx| {
19561 project.get_permalink_to_line(&buffer, selection, cx)
19562 })
19563 }
19564
19565 pub fn copy_permalink_to_line(
19566 &mut self,
19567 _: &CopyPermalinkToLine,
19568 window: &mut Window,
19569 cx: &mut Context<Self>,
19570 ) {
19571 let permalink_task = self.get_permalink_to_line(cx);
19572 let workspace = self.workspace();
19573
19574 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19575 Ok(permalink) => {
19576 cx.update(|_, cx| {
19577 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19578 })
19579 .ok();
19580 }
19581 Err(err) => {
19582 let message = format!("Failed to copy permalink: {err}");
19583
19584 anyhow::Result::<()>::Err(err).log_err();
19585
19586 if let Some(workspace) = workspace {
19587 workspace
19588 .update_in(cx, |workspace, _, cx| {
19589 struct CopyPermalinkToLine;
19590
19591 workspace.show_toast(
19592 Toast::new(
19593 NotificationId::unique::<CopyPermalinkToLine>(),
19594 message,
19595 ),
19596 cx,
19597 )
19598 })
19599 .ok();
19600 }
19601 }
19602 })
19603 .detach();
19604 }
19605
19606 pub fn copy_file_location(
19607 &mut self,
19608 _: &CopyFileLocation,
19609 _: &mut Window,
19610 cx: &mut Context<Self>,
19611 ) {
19612 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19613 if let Some(file) = self.target_file(cx)
19614 && let Some(path) = file.path().to_str()
19615 {
19616 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19617 }
19618 }
19619
19620 pub fn open_permalink_to_line(
19621 &mut self,
19622 _: &OpenPermalinkToLine,
19623 window: &mut Window,
19624 cx: &mut Context<Self>,
19625 ) {
19626 let permalink_task = self.get_permalink_to_line(cx);
19627 let workspace = self.workspace();
19628
19629 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19630 Ok(permalink) => {
19631 cx.update(|_, cx| {
19632 cx.open_url(permalink.as_ref());
19633 })
19634 .ok();
19635 }
19636 Err(err) => {
19637 let message = format!("Failed to open permalink: {err}");
19638
19639 anyhow::Result::<()>::Err(err).log_err();
19640
19641 if let Some(workspace) = workspace {
19642 workspace
19643 .update(cx, |workspace, cx| {
19644 struct OpenPermalinkToLine;
19645
19646 workspace.show_toast(
19647 Toast::new(
19648 NotificationId::unique::<OpenPermalinkToLine>(),
19649 message,
19650 ),
19651 cx,
19652 )
19653 })
19654 .ok();
19655 }
19656 }
19657 })
19658 .detach();
19659 }
19660
19661 pub fn insert_uuid_v4(
19662 &mut self,
19663 _: &InsertUuidV4,
19664 window: &mut Window,
19665 cx: &mut Context<Self>,
19666 ) {
19667 self.insert_uuid(UuidVersion::V4, window, cx);
19668 }
19669
19670 pub fn insert_uuid_v7(
19671 &mut self,
19672 _: &InsertUuidV7,
19673 window: &mut Window,
19674 cx: &mut Context<Self>,
19675 ) {
19676 self.insert_uuid(UuidVersion::V7, window, cx);
19677 }
19678
19679 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19680 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19681 self.transact(window, cx, |this, window, cx| {
19682 let edits = this
19683 .selections
19684 .all::<Point>(cx)
19685 .into_iter()
19686 .map(|selection| {
19687 let uuid = match version {
19688 UuidVersion::V4 => uuid::Uuid::new_v4(),
19689 UuidVersion::V7 => uuid::Uuid::now_v7(),
19690 };
19691
19692 (selection.range(), uuid.to_string())
19693 });
19694 this.edit(edits, cx);
19695 this.refresh_edit_prediction(true, false, window, cx);
19696 });
19697 }
19698
19699 pub fn open_selections_in_multibuffer(
19700 &mut self,
19701 _: &OpenSelectionsInMultibuffer,
19702 window: &mut Window,
19703 cx: &mut Context<Self>,
19704 ) {
19705 let multibuffer = self.buffer.read(cx);
19706
19707 let Some(buffer) = multibuffer.as_singleton() else {
19708 return;
19709 };
19710
19711 let Some(workspace) = self.workspace() else {
19712 return;
19713 };
19714
19715 let title = multibuffer.title(cx).to_string();
19716
19717 let locations = self
19718 .selections
19719 .all_anchors(cx)
19720 .iter()
19721 .map(|selection| Location {
19722 buffer: buffer.clone(),
19723 range: selection.start.text_anchor..selection.end.text_anchor,
19724 })
19725 .collect::<Vec<_>>();
19726
19727 cx.spawn_in(window, async move |_, cx| {
19728 workspace.update_in(cx, |workspace, window, cx| {
19729 Self::open_locations_in_multibuffer(
19730 workspace,
19731 locations,
19732 format!("Selections for '{title}'"),
19733 false,
19734 MultibufferSelectionMode::All,
19735 window,
19736 cx,
19737 );
19738 })
19739 })
19740 .detach();
19741 }
19742
19743 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19744 /// last highlight added will be used.
19745 ///
19746 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19747 pub fn highlight_rows<T: 'static>(
19748 &mut self,
19749 range: Range<Anchor>,
19750 color: Hsla,
19751 options: RowHighlightOptions,
19752 cx: &mut Context<Self>,
19753 ) {
19754 let snapshot = self.buffer().read(cx).snapshot(cx);
19755 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19756 let ix = row_highlights.binary_search_by(|highlight| {
19757 Ordering::Equal
19758 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19759 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19760 });
19761
19762 if let Err(mut ix) = ix {
19763 let index = post_inc(&mut self.highlight_order);
19764
19765 // If this range intersects with the preceding highlight, then merge it with
19766 // the preceding highlight. Otherwise insert a new highlight.
19767 let mut merged = false;
19768 if ix > 0 {
19769 let prev_highlight = &mut row_highlights[ix - 1];
19770 if prev_highlight
19771 .range
19772 .end
19773 .cmp(&range.start, &snapshot)
19774 .is_ge()
19775 {
19776 ix -= 1;
19777 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19778 prev_highlight.range.end = range.end;
19779 }
19780 merged = true;
19781 prev_highlight.index = index;
19782 prev_highlight.color = color;
19783 prev_highlight.options = options;
19784 }
19785 }
19786
19787 if !merged {
19788 row_highlights.insert(
19789 ix,
19790 RowHighlight {
19791 range,
19792 index,
19793 color,
19794 options,
19795 type_id: TypeId::of::<T>(),
19796 },
19797 );
19798 }
19799
19800 // If any of the following highlights intersect with this one, merge them.
19801 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19802 let highlight = &row_highlights[ix];
19803 if next_highlight
19804 .range
19805 .start
19806 .cmp(&highlight.range.end, &snapshot)
19807 .is_le()
19808 {
19809 if next_highlight
19810 .range
19811 .end
19812 .cmp(&highlight.range.end, &snapshot)
19813 .is_gt()
19814 {
19815 row_highlights[ix].range.end = next_highlight.range.end;
19816 }
19817 row_highlights.remove(ix + 1);
19818 } else {
19819 break;
19820 }
19821 }
19822 }
19823 }
19824
19825 /// Remove any highlighted row ranges of the given type that intersect the
19826 /// given ranges.
19827 pub fn remove_highlighted_rows<T: 'static>(
19828 &mut self,
19829 ranges_to_remove: Vec<Range<Anchor>>,
19830 cx: &mut Context<Self>,
19831 ) {
19832 let snapshot = self.buffer().read(cx).snapshot(cx);
19833 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19834 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19835 row_highlights.retain(|highlight| {
19836 while let Some(range_to_remove) = ranges_to_remove.peek() {
19837 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19838 Ordering::Less | Ordering::Equal => {
19839 ranges_to_remove.next();
19840 }
19841 Ordering::Greater => {
19842 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19843 Ordering::Less | Ordering::Equal => {
19844 return false;
19845 }
19846 Ordering::Greater => break,
19847 }
19848 }
19849 }
19850 }
19851
19852 true
19853 })
19854 }
19855
19856 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19857 pub fn clear_row_highlights<T: 'static>(&mut self) {
19858 self.highlighted_rows.remove(&TypeId::of::<T>());
19859 }
19860
19861 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19862 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19863 self.highlighted_rows
19864 .get(&TypeId::of::<T>())
19865 .map_or(&[] as &[_], |vec| vec.as_slice())
19866 .iter()
19867 .map(|highlight| (highlight.range.clone(), highlight.color))
19868 }
19869
19870 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19871 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19872 /// Allows to ignore certain kinds of highlights.
19873 pub fn highlighted_display_rows(
19874 &self,
19875 window: &mut Window,
19876 cx: &mut App,
19877 ) -> BTreeMap<DisplayRow, LineHighlight> {
19878 let snapshot = self.snapshot(window, cx);
19879 let mut used_highlight_orders = HashMap::default();
19880 self.highlighted_rows
19881 .iter()
19882 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19883 .fold(
19884 BTreeMap::<DisplayRow, LineHighlight>::new(),
19885 |mut unique_rows, highlight| {
19886 let start = highlight.range.start.to_display_point(&snapshot);
19887 let end = highlight.range.end.to_display_point(&snapshot);
19888 let start_row = start.row().0;
19889 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19890 && end.column() == 0
19891 {
19892 end.row().0.saturating_sub(1)
19893 } else {
19894 end.row().0
19895 };
19896 for row in start_row..=end_row {
19897 let used_index =
19898 used_highlight_orders.entry(row).or_insert(highlight.index);
19899 if highlight.index >= *used_index {
19900 *used_index = highlight.index;
19901 unique_rows.insert(
19902 DisplayRow(row),
19903 LineHighlight {
19904 include_gutter: highlight.options.include_gutter,
19905 border: None,
19906 background: highlight.color.into(),
19907 type_id: Some(highlight.type_id),
19908 },
19909 );
19910 }
19911 }
19912 unique_rows
19913 },
19914 )
19915 }
19916
19917 pub fn highlighted_display_row_for_autoscroll(
19918 &self,
19919 snapshot: &DisplaySnapshot,
19920 ) -> Option<DisplayRow> {
19921 self.highlighted_rows
19922 .values()
19923 .flat_map(|highlighted_rows| highlighted_rows.iter())
19924 .filter_map(|highlight| {
19925 if highlight.options.autoscroll {
19926 Some(highlight.range.start.to_display_point(snapshot).row())
19927 } else {
19928 None
19929 }
19930 })
19931 .min()
19932 }
19933
19934 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19935 self.highlight_background::<SearchWithinRange>(
19936 ranges,
19937 |colors| colors.colors().editor_document_highlight_read_background,
19938 cx,
19939 )
19940 }
19941
19942 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19943 self.breadcrumb_header = Some(new_header);
19944 }
19945
19946 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19947 self.clear_background_highlights::<SearchWithinRange>(cx);
19948 }
19949
19950 pub fn highlight_background<T: 'static>(
19951 &mut self,
19952 ranges: &[Range<Anchor>],
19953 color_fetcher: fn(&Theme) -> Hsla,
19954 cx: &mut Context<Self>,
19955 ) {
19956 self.background_highlights.insert(
19957 HighlightKey::Type(TypeId::of::<T>()),
19958 (color_fetcher, Arc::from(ranges)),
19959 );
19960 self.scrollbar_marker_state.dirty = true;
19961 cx.notify();
19962 }
19963
19964 pub fn highlight_background_key<T: 'static>(
19965 &mut self,
19966 key: usize,
19967 ranges: &[Range<Anchor>],
19968 color_fetcher: fn(&Theme) -> Hsla,
19969 cx: &mut Context<Self>,
19970 ) {
19971 self.background_highlights.insert(
19972 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19973 (color_fetcher, Arc::from(ranges)),
19974 );
19975 self.scrollbar_marker_state.dirty = true;
19976 cx.notify();
19977 }
19978
19979 pub fn clear_background_highlights<T: 'static>(
19980 &mut self,
19981 cx: &mut Context<Self>,
19982 ) -> Option<BackgroundHighlight> {
19983 let text_highlights = self
19984 .background_highlights
19985 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19986 if !text_highlights.1.is_empty() {
19987 self.scrollbar_marker_state.dirty = true;
19988 cx.notify();
19989 }
19990 Some(text_highlights)
19991 }
19992
19993 pub fn highlight_gutter<T: 'static>(
19994 &mut self,
19995 ranges: impl Into<Vec<Range<Anchor>>>,
19996 color_fetcher: fn(&App) -> Hsla,
19997 cx: &mut Context<Self>,
19998 ) {
19999 self.gutter_highlights
20000 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20001 cx.notify();
20002 }
20003
20004 pub fn clear_gutter_highlights<T: 'static>(
20005 &mut self,
20006 cx: &mut Context<Self>,
20007 ) -> Option<GutterHighlight> {
20008 cx.notify();
20009 self.gutter_highlights.remove(&TypeId::of::<T>())
20010 }
20011
20012 pub fn insert_gutter_highlight<T: 'static>(
20013 &mut self,
20014 range: Range<Anchor>,
20015 color_fetcher: fn(&App) -> Hsla,
20016 cx: &mut Context<Self>,
20017 ) {
20018 let snapshot = self.buffer().read(cx).snapshot(cx);
20019 let mut highlights = self
20020 .gutter_highlights
20021 .remove(&TypeId::of::<T>())
20022 .map(|(_, highlights)| highlights)
20023 .unwrap_or_default();
20024 let ix = highlights.binary_search_by(|highlight| {
20025 Ordering::Equal
20026 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20027 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20028 });
20029 if let Err(ix) = ix {
20030 highlights.insert(ix, range);
20031 }
20032 self.gutter_highlights
20033 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20034 }
20035
20036 pub fn remove_gutter_highlights<T: 'static>(
20037 &mut self,
20038 ranges_to_remove: Vec<Range<Anchor>>,
20039 cx: &mut Context<Self>,
20040 ) {
20041 let snapshot = self.buffer().read(cx).snapshot(cx);
20042 let Some((color_fetcher, mut gutter_highlights)) =
20043 self.gutter_highlights.remove(&TypeId::of::<T>())
20044 else {
20045 return;
20046 };
20047 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20048 gutter_highlights.retain(|highlight| {
20049 while let Some(range_to_remove) = ranges_to_remove.peek() {
20050 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20051 Ordering::Less | Ordering::Equal => {
20052 ranges_to_remove.next();
20053 }
20054 Ordering::Greater => {
20055 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20056 Ordering::Less | Ordering::Equal => {
20057 return false;
20058 }
20059 Ordering::Greater => break,
20060 }
20061 }
20062 }
20063 }
20064
20065 true
20066 });
20067 self.gutter_highlights
20068 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20069 }
20070
20071 #[cfg(feature = "test-support")]
20072 pub fn all_text_highlights(
20073 &self,
20074 window: &mut Window,
20075 cx: &mut Context<Self>,
20076 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20077 let snapshot = self.snapshot(window, cx);
20078 self.display_map.update(cx, |display_map, _| {
20079 display_map
20080 .all_text_highlights()
20081 .map(|highlight| {
20082 let (style, ranges) = highlight.as_ref();
20083 (
20084 *style,
20085 ranges
20086 .iter()
20087 .map(|range| range.clone().to_display_points(&snapshot))
20088 .collect(),
20089 )
20090 })
20091 .collect()
20092 })
20093 }
20094
20095 #[cfg(feature = "test-support")]
20096 pub fn all_text_background_highlights(
20097 &self,
20098 window: &mut Window,
20099 cx: &mut Context<Self>,
20100 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20101 let snapshot = self.snapshot(window, cx);
20102 let buffer = &snapshot.buffer_snapshot;
20103 let start = buffer.anchor_before(0);
20104 let end = buffer.anchor_after(buffer.len());
20105 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20106 }
20107
20108 #[cfg(any(test, feature = "test-support"))]
20109 pub fn sorted_background_highlights_in_range(
20110 &self,
20111 search_range: Range<Anchor>,
20112 display_snapshot: &DisplaySnapshot,
20113 theme: &Theme,
20114 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20115 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20116 res.sort_by(|a, b| {
20117 a.0.start
20118 .cmp(&b.0.start)
20119 .then_with(|| a.0.end.cmp(&b.0.end))
20120 .then_with(|| a.1.cmp(&b.1))
20121 });
20122 res
20123 }
20124
20125 #[cfg(feature = "test-support")]
20126 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20127 let snapshot = self.buffer().read(cx).snapshot(cx);
20128
20129 let highlights = self
20130 .background_highlights
20131 .get(&HighlightKey::Type(TypeId::of::<
20132 items::BufferSearchHighlights,
20133 >()));
20134
20135 if let Some((_color, ranges)) = highlights {
20136 ranges
20137 .iter()
20138 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20139 .collect_vec()
20140 } else {
20141 vec![]
20142 }
20143 }
20144
20145 fn document_highlights_for_position<'a>(
20146 &'a self,
20147 position: Anchor,
20148 buffer: &'a MultiBufferSnapshot,
20149 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20150 let read_highlights = self
20151 .background_highlights
20152 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20153 .map(|h| &h.1);
20154 let write_highlights = self
20155 .background_highlights
20156 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20157 .map(|h| &h.1);
20158 let left_position = position.bias_left(buffer);
20159 let right_position = position.bias_right(buffer);
20160 read_highlights
20161 .into_iter()
20162 .chain(write_highlights)
20163 .flat_map(move |ranges| {
20164 let start_ix = match ranges.binary_search_by(|probe| {
20165 let cmp = probe.end.cmp(&left_position, buffer);
20166 if cmp.is_ge() {
20167 Ordering::Greater
20168 } else {
20169 Ordering::Less
20170 }
20171 }) {
20172 Ok(i) | Err(i) => i,
20173 };
20174
20175 ranges[start_ix..]
20176 .iter()
20177 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20178 })
20179 }
20180
20181 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20182 self.background_highlights
20183 .get(&HighlightKey::Type(TypeId::of::<T>()))
20184 .is_some_and(|(_, highlights)| !highlights.is_empty())
20185 }
20186
20187 /// Returns all background highlights for a given range.
20188 ///
20189 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20190 pub fn background_highlights_in_range(
20191 &self,
20192 search_range: Range<Anchor>,
20193 display_snapshot: &DisplaySnapshot,
20194 theme: &Theme,
20195 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20196 let mut results = Vec::new();
20197 for (color_fetcher, ranges) in self.background_highlights.values() {
20198 let color = color_fetcher(theme);
20199 let start_ix = match ranges.binary_search_by(|probe| {
20200 let cmp = probe
20201 .end
20202 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20203 if cmp.is_gt() {
20204 Ordering::Greater
20205 } else {
20206 Ordering::Less
20207 }
20208 }) {
20209 Ok(i) | Err(i) => i,
20210 };
20211 for range in &ranges[start_ix..] {
20212 if range
20213 .start
20214 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20215 .is_ge()
20216 {
20217 break;
20218 }
20219
20220 let start = range.start.to_display_point(display_snapshot);
20221 let end = range.end.to_display_point(display_snapshot);
20222 results.push((start..end, color))
20223 }
20224 }
20225 results
20226 }
20227
20228 pub fn gutter_highlights_in_range(
20229 &self,
20230 search_range: Range<Anchor>,
20231 display_snapshot: &DisplaySnapshot,
20232 cx: &App,
20233 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20234 let mut results = Vec::new();
20235 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20236 let color = color_fetcher(cx);
20237 let start_ix = match ranges.binary_search_by(|probe| {
20238 let cmp = probe
20239 .end
20240 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20241 if cmp.is_gt() {
20242 Ordering::Greater
20243 } else {
20244 Ordering::Less
20245 }
20246 }) {
20247 Ok(i) | Err(i) => i,
20248 };
20249 for range in &ranges[start_ix..] {
20250 if range
20251 .start
20252 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20253 .is_ge()
20254 {
20255 break;
20256 }
20257
20258 let start = range.start.to_display_point(display_snapshot);
20259 let end = range.end.to_display_point(display_snapshot);
20260 results.push((start..end, color))
20261 }
20262 }
20263 results
20264 }
20265
20266 /// Get the text ranges corresponding to the redaction query
20267 pub fn redacted_ranges(
20268 &self,
20269 search_range: Range<Anchor>,
20270 display_snapshot: &DisplaySnapshot,
20271 cx: &App,
20272 ) -> Vec<Range<DisplayPoint>> {
20273 display_snapshot
20274 .buffer_snapshot
20275 .redacted_ranges(search_range, |file| {
20276 if let Some(file) = file {
20277 file.is_private()
20278 && EditorSettings::get(
20279 Some(SettingsLocation {
20280 worktree_id: file.worktree_id(cx),
20281 path: file.path().as_ref(),
20282 }),
20283 cx,
20284 )
20285 .redact_private_values
20286 } else {
20287 false
20288 }
20289 })
20290 .map(|range| {
20291 range.start.to_display_point(display_snapshot)
20292 ..range.end.to_display_point(display_snapshot)
20293 })
20294 .collect()
20295 }
20296
20297 pub fn highlight_text_key<T: 'static>(
20298 &mut self,
20299 key: usize,
20300 ranges: Vec<Range<Anchor>>,
20301 style: HighlightStyle,
20302 cx: &mut Context<Self>,
20303 ) {
20304 self.display_map.update(cx, |map, _| {
20305 map.highlight_text(
20306 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20307 ranges,
20308 style,
20309 );
20310 });
20311 cx.notify();
20312 }
20313
20314 pub fn highlight_text<T: 'static>(
20315 &mut self,
20316 ranges: Vec<Range<Anchor>>,
20317 style: HighlightStyle,
20318 cx: &mut Context<Self>,
20319 ) {
20320 self.display_map.update(cx, |map, _| {
20321 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20322 });
20323 cx.notify();
20324 }
20325
20326 pub(crate) fn highlight_inlays<T: 'static>(
20327 &mut self,
20328 highlights: Vec<InlayHighlight>,
20329 style: HighlightStyle,
20330 cx: &mut Context<Self>,
20331 ) {
20332 self.display_map.update(cx, |map, _| {
20333 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20334 });
20335 cx.notify();
20336 }
20337
20338 pub fn text_highlights<'a, T: 'static>(
20339 &'a self,
20340 cx: &'a App,
20341 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20342 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20343 }
20344
20345 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20346 let cleared = self
20347 .display_map
20348 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20349 if cleared {
20350 cx.notify();
20351 }
20352 }
20353
20354 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20355 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20356 && self.focus_handle.is_focused(window)
20357 }
20358
20359 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20360 self.show_cursor_when_unfocused = is_enabled;
20361 cx.notify();
20362 }
20363
20364 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20365 cx.notify();
20366 }
20367
20368 fn on_debug_session_event(
20369 &mut self,
20370 _session: Entity<Session>,
20371 event: &SessionEvent,
20372 cx: &mut Context<Self>,
20373 ) {
20374 if let SessionEvent::InvalidateInlineValue = event {
20375 self.refresh_inline_values(cx);
20376 }
20377 }
20378
20379 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20380 let Some(project) = self.project.clone() else {
20381 return;
20382 };
20383
20384 if !self.inline_value_cache.enabled {
20385 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20386 self.splice_inlays(&inlays, Vec::new(), cx);
20387 return;
20388 }
20389
20390 let current_execution_position = self
20391 .highlighted_rows
20392 .get(&TypeId::of::<ActiveDebugLine>())
20393 .and_then(|lines| lines.last().map(|line| line.range.end));
20394
20395 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20396 let inline_values = editor
20397 .update(cx, |editor, cx| {
20398 let Some(current_execution_position) = current_execution_position else {
20399 return Some(Task::ready(Ok(Vec::new())));
20400 };
20401
20402 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20403 let snapshot = buffer.snapshot(cx);
20404
20405 let excerpt = snapshot.excerpt_containing(
20406 current_execution_position..current_execution_position,
20407 )?;
20408
20409 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20410 })?;
20411
20412 let range =
20413 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20414
20415 project.inline_values(buffer, range, cx)
20416 })
20417 .ok()
20418 .flatten()?
20419 .await
20420 .context("refreshing debugger inlays")
20421 .log_err()?;
20422
20423 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20424
20425 for (buffer_id, inline_value) in inline_values
20426 .into_iter()
20427 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20428 {
20429 buffer_inline_values
20430 .entry(buffer_id)
20431 .or_default()
20432 .push(inline_value);
20433 }
20434
20435 editor
20436 .update(cx, |editor, cx| {
20437 let snapshot = editor.buffer.read(cx).snapshot(cx);
20438 let mut new_inlays = Vec::default();
20439
20440 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20441 let buffer_id = buffer_snapshot.remote_id();
20442 buffer_inline_values
20443 .get(&buffer_id)
20444 .into_iter()
20445 .flatten()
20446 .for_each(|hint| {
20447 let inlay = Inlay::debugger(
20448 post_inc(&mut editor.next_inlay_id),
20449 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20450 hint.text(),
20451 );
20452 if !inlay.text.chars().contains(&'\n') {
20453 new_inlays.push(inlay);
20454 }
20455 });
20456 }
20457
20458 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20459 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20460
20461 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20462 })
20463 .ok()?;
20464 Some(())
20465 });
20466 }
20467
20468 fn on_buffer_event(
20469 &mut self,
20470 multibuffer: &Entity<MultiBuffer>,
20471 event: &multi_buffer::Event,
20472 window: &mut Window,
20473 cx: &mut Context<Self>,
20474 ) {
20475 match event {
20476 multi_buffer::Event::Edited {
20477 singleton_buffer_edited,
20478 edited_buffer,
20479 } => {
20480 self.scrollbar_marker_state.dirty = true;
20481 self.active_indent_guides_state.dirty = true;
20482 self.refresh_active_diagnostics(cx);
20483 self.refresh_code_actions(window, cx);
20484 self.refresh_selected_text_highlights(true, window, cx);
20485 self.refresh_single_line_folds(window, cx);
20486 refresh_matching_bracket_highlights(self, window, cx);
20487 if self.has_active_edit_prediction() {
20488 self.update_visible_edit_prediction(window, cx);
20489 }
20490 if let Some(project) = self.project.as_ref()
20491 && let Some(edited_buffer) = edited_buffer
20492 {
20493 project.update(cx, |project, cx| {
20494 self.registered_buffers
20495 .entry(edited_buffer.read(cx).remote_id())
20496 .or_insert_with(|| {
20497 project.register_buffer_with_language_servers(edited_buffer, cx)
20498 });
20499 });
20500 }
20501 cx.emit(EditorEvent::BufferEdited);
20502 cx.emit(SearchEvent::MatchesInvalidated);
20503
20504 if let Some(buffer) = edited_buffer {
20505 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20506 }
20507
20508 if *singleton_buffer_edited {
20509 if let Some(buffer) = edited_buffer
20510 && buffer.read(cx).file().is_none()
20511 {
20512 cx.emit(EditorEvent::TitleChanged);
20513 }
20514 if let Some(project) = &self.project {
20515 #[allow(clippy::mutable_key_type)]
20516 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20517 multibuffer
20518 .all_buffers()
20519 .into_iter()
20520 .filter_map(|buffer| {
20521 buffer.update(cx, |buffer, cx| {
20522 let language = buffer.language()?;
20523 let should_discard = project.update(cx, |project, cx| {
20524 project.is_local()
20525 && !project.has_language_servers_for(buffer, cx)
20526 });
20527 should_discard.not().then_some(language.clone())
20528 })
20529 })
20530 .collect::<HashSet<_>>()
20531 });
20532 if !languages_affected.is_empty() {
20533 self.refresh_inlay_hints(
20534 InlayHintRefreshReason::BufferEdited(languages_affected),
20535 cx,
20536 );
20537 }
20538 }
20539 }
20540
20541 let Some(project) = &self.project else { return };
20542 let (telemetry, is_via_ssh) = {
20543 let project = project.read(cx);
20544 let telemetry = project.client().telemetry().clone();
20545 let is_via_ssh = project.is_via_remote_server();
20546 (telemetry, is_via_ssh)
20547 };
20548 refresh_linked_ranges(self, window, cx);
20549 telemetry.log_edit_event("editor", is_via_ssh);
20550 }
20551 multi_buffer::Event::ExcerptsAdded {
20552 buffer,
20553 predecessor,
20554 excerpts,
20555 } => {
20556 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20557 let buffer_id = buffer.read(cx).remote_id();
20558 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20559 && let Some(project) = &self.project
20560 {
20561 update_uncommitted_diff_for_buffer(
20562 cx.entity(),
20563 project,
20564 [buffer.clone()],
20565 self.buffer.clone(),
20566 cx,
20567 )
20568 .detach();
20569 }
20570 if self.active_diagnostics != ActiveDiagnostic::All {
20571 self.update_lsp_data(false, Some(buffer_id), window, cx);
20572 }
20573 cx.emit(EditorEvent::ExcerptsAdded {
20574 buffer: buffer.clone(),
20575 predecessor: *predecessor,
20576 excerpts: excerpts.clone(),
20577 });
20578 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20579 }
20580 multi_buffer::Event::ExcerptsRemoved {
20581 ids,
20582 removed_buffer_ids,
20583 } => {
20584 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20585 let buffer = self.buffer.read(cx);
20586 self.registered_buffers
20587 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20588 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20589 cx.emit(EditorEvent::ExcerptsRemoved {
20590 ids: ids.clone(),
20591 removed_buffer_ids: removed_buffer_ids.clone(),
20592 });
20593 }
20594 multi_buffer::Event::ExcerptsEdited {
20595 excerpt_ids,
20596 buffer_ids,
20597 } => {
20598 self.display_map.update(cx, |map, cx| {
20599 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20600 });
20601 cx.emit(EditorEvent::ExcerptsEdited {
20602 ids: excerpt_ids.clone(),
20603 });
20604 }
20605 multi_buffer::Event::ExcerptsExpanded { ids } => {
20606 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20607 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20608 }
20609 multi_buffer::Event::Reparsed(buffer_id) => {
20610 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20611 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20612
20613 cx.emit(EditorEvent::Reparsed(*buffer_id));
20614 }
20615 multi_buffer::Event::DiffHunksToggled => {
20616 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20617 }
20618 multi_buffer::Event::LanguageChanged(buffer_id) => {
20619 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20620 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20621 cx.emit(EditorEvent::Reparsed(*buffer_id));
20622 cx.notify();
20623 }
20624 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20625 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20626 multi_buffer::Event::FileHandleChanged
20627 | multi_buffer::Event::Reloaded
20628 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20629 multi_buffer::Event::DiagnosticsUpdated => {
20630 self.update_diagnostics_state(window, cx);
20631 }
20632 _ => {}
20633 };
20634 }
20635
20636 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20637 if !self.diagnostics_enabled() {
20638 return;
20639 }
20640 self.refresh_active_diagnostics(cx);
20641 self.refresh_inline_diagnostics(true, window, cx);
20642 self.scrollbar_marker_state.dirty = true;
20643 cx.notify();
20644 }
20645
20646 pub fn start_temporary_diff_override(&mut self) {
20647 self.load_diff_task.take();
20648 self.temporary_diff_override = true;
20649 }
20650
20651 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20652 self.temporary_diff_override = false;
20653 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20654 self.buffer.update(cx, |buffer, cx| {
20655 buffer.set_all_diff_hunks_collapsed(cx);
20656 });
20657
20658 if let Some(project) = self.project.clone() {
20659 self.load_diff_task = Some(
20660 update_uncommitted_diff_for_buffer(
20661 cx.entity(),
20662 &project,
20663 self.buffer.read(cx).all_buffers(),
20664 self.buffer.clone(),
20665 cx,
20666 )
20667 .shared(),
20668 );
20669 }
20670 }
20671
20672 fn on_display_map_changed(
20673 &mut self,
20674 _: Entity<DisplayMap>,
20675 _: &mut Window,
20676 cx: &mut Context<Self>,
20677 ) {
20678 cx.notify();
20679 }
20680
20681 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20682 if self.diagnostics_enabled() {
20683 let new_severity = EditorSettings::get_global(cx)
20684 .diagnostics_max_severity
20685 .unwrap_or(DiagnosticSeverity::Hint);
20686 self.set_max_diagnostics_severity(new_severity, cx);
20687 }
20688 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20689 self.update_edit_prediction_settings(cx);
20690 self.refresh_edit_prediction(true, false, window, cx);
20691 self.refresh_inline_values(cx);
20692 self.refresh_inlay_hints(
20693 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20694 self.selections.newest_anchor().head(),
20695 &self.buffer.read(cx).snapshot(cx),
20696 cx,
20697 )),
20698 cx,
20699 );
20700
20701 let old_cursor_shape = self.cursor_shape;
20702 let old_show_breadcrumbs = self.show_breadcrumbs;
20703
20704 {
20705 let editor_settings = EditorSettings::get_global(cx);
20706 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20707 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20708 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20709 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20710 }
20711
20712 if old_cursor_shape != self.cursor_shape {
20713 cx.emit(EditorEvent::CursorShapeChanged);
20714 }
20715
20716 if old_show_breadcrumbs != self.show_breadcrumbs {
20717 cx.emit(EditorEvent::BreadcrumbsChanged);
20718 }
20719
20720 let project_settings = ProjectSettings::get_global(cx);
20721 self.serialize_dirty_buffers =
20722 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20723
20724 if self.mode.is_full() {
20725 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20726 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
20727 if self.show_inline_diagnostics != show_inline_diagnostics {
20728 self.show_inline_diagnostics = show_inline_diagnostics;
20729 self.refresh_inline_diagnostics(false, window, cx);
20730 }
20731
20732 if self.git_blame_inline_enabled != inline_blame_enabled {
20733 self.toggle_git_blame_inline_internal(false, window, cx);
20734 }
20735
20736 let minimap_settings = EditorSettings::get_global(cx).minimap;
20737 if self.minimap_visibility != MinimapVisibility::Disabled {
20738 if self.minimap_visibility.settings_visibility()
20739 != minimap_settings.minimap_enabled()
20740 {
20741 self.set_minimap_visibility(
20742 MinimapVisibility::for_mode(self.mode(), cx),
20743 window,
20744 cx,
20745 );
20746 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20747 minimap_entity.update(cx, |minimap_editor, cx| {
20748 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20749 })
20750 }
20751 }
20752 }
20753
20754 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20755 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20756 }) {
20757 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20758 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20759 }
20760 self.refresh_colors(false, None, window, cx);
20761 }
20762
20763 cx.notify();
20764 }
20765
20766 pub fn set_searchable(&mut self, searchable: bool) {
20767 self.searchable = searchable;
20768 }
20769
20770 pub fn searchable(&self) -> bool {
20771 self.searchable
20772 }
20773
20774 fn open_proposed_changes_editor(
20775 &mut self,
20776 _: &OpenProposedChangesEditor,
20777 window: &mut Window,
20778 cx: &mut Context<Self>,
20779 ) {
20780 let Some(workspace) = self.workspace() else {
20781 cx.propagate();
20782 return;
20783 };
20784
20785 let selections = self.selections.all::<usize>(cx);
20786 let multi_buffer = self.buffer.read(cx);
20787 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20788 let mut new_selections_by_buffer = HashMap::default();
20789 for selection in selections {
20790 for (buffer, range, _) in
20791 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20792 {
20793 let mut range = range.to_point(buffer);
20794 range.start.column = 0;
20795 range.end.column = buffer.line_len(range.end.row);
20796 new_selections_by_buffer
20797 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20798 .or_insert(Vec::new())
20799 .push(range)
20800 }
20801 }
20802
20803 let proposed_changes_buffers = new_selections_by_buffer
20804 .into_iter()
20805 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20806 .collect::<Vec<_>>();
20807 let proposed_changes_editor = cx.new(|cx| {
20808 ProposedChangesEditor::new(
20809 "Proposed changes",
20810 proposed_changes_buffers,
20811 self.project.clone(),
20812 window,
20813 cx,
20814 )
20815 });
20816
20817 window.defer(cx, move |window, cx| {
20818 workspace.update(cx, |workspace, cx| {
20819 workspace.active_pane().update(cx, |pane, cx| {
20820 pane.add_item(
20821 Box::new(proposed_changes_editor),
20822 true,
20823 true,
20824 None,
20825 window,
20826 cx,
20827 );
20828 });
20829 });
20830 });
20831 }
20832
20833 pub fn open_excerpts_in_split(
20834 &mut self,
20835 _: &OpenExcerptsSplit,
20836 window: &mut Window,
20837 cx: &mut Context<Self>,
20838 ) {
20839 self.open_excerpts_common(None, true, window, cx)
20840 }
20841
20842 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20843 self.open_excerpts_common(None, false, window, cx)
20844 }
20845
20846 fn open_excerpts_common(
20847 &mut self,
20848 jump_data: Option<JumpData>,
20849 split: bool,
20850 window: &mut Window,
20851 cx: &mut Context<Self>,
20852 ) {
20853 let Some(workspace) = self.workspace() else {
20854 cx.propagate();
20855 return;
20856 };
20857
20858 if self.buffer.read(cx).is_singleton() {
20859 cx.propagate();
20860 return;
20861 }
20862
20863 let mut new_selections_by_buffer = HashMap::default();
20864 match &jump_data {
20865 Some(JumpData::MultiBufferPoint {
20866 excerpt_id,
20867 position,
20868 anchor,
20869 line_offset_from_top,
20870 }) => {
20871 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20872 if let Some(buffer) = multi_buffer_snapshot
20873 .buffer_id_for_excerpt(*excerpt_id)
20874 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20875 {
20876 let buffer_snapshot = buffer.read(cx).snapshot();
20877 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20878 language::ToPoint::to_point(anchor, &buffer_snapshot)
20879 } else {
20880 buffer_snapshot.clip_point(*position, Bias::Left)
20881 };
20882 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20883 new_selections_by_buffer.insert(
20884 buffer,
20885 (
20886 vec![jump_to_offset..jump_to_offset],
20887 Some(*line_offset_from_top),
20888 ),
20889 );
20890 }
20891 }
20892 Some(JumpData::MultiBufferRow {
20893 row,
20894 line_offset_from_top,
20895 }) => {
20896 let point = MultiBufferPoint::new(row.0, 0);
20897 if let Some((buffer, buffer_point, _)) =
20898 self.buffer.read(cx).point_to_buffer_point(point, cx)
20899 {
20900 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20901 new_selections_by_buffer
20902 .entry(buffer)
20903 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20904 .0
20905 .push(buffer_offset..buffer_offset)
20906 }
20907 }
20908 None => {
20909 let selections = self.selections.all::<usize>(cx);
20910 let multi_buffer = self.buffer.read(cx);
20911 for selection in selections {
20912 for (snapshot, range, _, anchor) in multi_buffer
20913 .snapshot(cx)
20914 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20915 {
20916 if let Some(anchor) = anchor {
20917 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
20918 else {
20919 continue;
20920 };
20921 let offset = text::ToOffset::to_offset(
20922 &anchor.text_anchor,
20923 &buffer_handle.read(cx).snapshot(),
20924 );
20925 let range = offset..offset;
20926 new_selections_by_buffer
20927 .entry(buffer_handle)
20928 .or_insert((Vec::new(), None))
20929 .0
20930 .push(range)
20931 } else {
20932 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20933 else {
20934 continue;
20935 };
20936 new_selections_by_buffer
20937 .entry(buffer_handle)
20938 .or_insert((Vec::new(), None))
20939 .0
20940 .push(range)
20941 }
20942 }
20943 }
20944 }
20945 }
20946
20947 new_selections_by_buffer
20948 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20949
20950 if new_selections_by_buffer.is_empty() {
20951 return;
20952 }
20953
20954 // We defer the pane interaction because we ourselves are a workspace item
20955 // and activating a new item causes the pane to call a method on us reentrantly,
20956 // which panics if we're on the stack.
20957 window.defer(cx, move |window, cx| {
20958 workspace.update(cx, |workspace, cx| {
20959 let pane = if split {
20960 workspace.adjacent_pane(window, cx)
20961 } else {
20962 workspace.active_pane().clone()
20963 };
20964
20965 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20966 let editor = buffer
20967 .read(cx)
20968 .file()
20969 .is_none()
20970 .then(|| {
20971 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20972 // so `workspace.open_project_item` will never find them, always opening a new editor.
20973 // Instead, we try to activate the existing editor in the pane first.
20974 let (editor, pane_item_index) =
20975 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20976 let editor = item.downcast::<Editor>()?;
20977 let singleton_buffer =
20978 editor.read(cx).buffer().read(cx).as_singleton()?;
20979 if singleton_buffer == buffer {
20980 Some((editor, i))
20981 } else {
20982 None
20983 }
20984 })?;
20985 pane.update(cx, |pane, cx| {
20986 pane.activate_item(pane_item_index, true, true, window, cx)
20987 });
20988 Some(editor)
20989 })
20990 .flatten()
20991 .unwrap_or_else(|| {
20992 workspace.open_project_item::<Self>(
20993 pane.clone(),
20994 buffer,
20995 true,
20996 true,
20997 window,
20998 cx,
20999 )
21000 });
21001
21002 editor.update(cx, |editor, cx| {
21003 let autoscroll = match scroll_offset {
21004 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21005 None => Autoscroll::newest(),
21006 };
21007 let nav_history = editor.nav_history.take();
21008 editor.change_selections(
21009 SelectionEffects::scroll(autoscroll),
21010 window,
21011 cx,
21012 |s| {
21013 s.select_ranges(ranges);
21014 },
21015 );
21016 editor.nav_history = nav_history;
21017 });
21018 }
21019 })
21020 });
21021 }
21022
21023 // For now, don't allow opening excerpts in buffers that aren't backed by
21024 // regular project files.
21025 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21026 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21027 }
21028
21029 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21030 let snapshot = self.buffer.read(cx).read(cx);
21031 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21032 Some(
21033 ranges
21034 .iter()
21035 .map(move |range| {
21036 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21037 })
21038 .collect(),
21039 )
21040 }
21041
21042 fn selection_replacement_ranges(
21043 &self,
21044 range: Range<OffsetUtf16>,
21045 cx: &mut App,
21046 ) -> Vec<Range<OffsetUtf16>> {
21047 let selections = self.selections.all::<OffsetUtf16>(cx);
21048 let newest_selection = selections
21049 .iter()
21050 .max_by_key(|selection| selection.id)
21051 .unwrap();
21052 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21053 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21054 let snapshot = self.buffer.read(cx).read(cx);
21055 selections
21056 .into_iter()
21057 .map(|mut selection| {
21058 selection.start.0 =
21059 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21060 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21061 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21062 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21063 })
21064 .collect()
21065 }
21066
21067 fn report_editor_event(
21068 &self,
21069 reported_event: ReportEditorEvent,
21070 file_extension: Option<String>,
21071 cx: &App,
21072 ) {
21073 if cfg!(any(test, feature = "test-support")) {
21074 return;
21075 }
21076
21077 let Some(project) = &self.project else { return };
21078
21079 // If None, we are in a file without an extension
21080 let file = self
21081 .buffer
21082 .read(cx)
21083 .as_singleton()
21084 .and_then(|b| b.read(cx).file());
21085 let file_extension = file_extension.or(file
21086 .as_ref()
21087 .and_then(|file| Path::new(file.file_name(cx)).extension())
21088 .and_then(|e| e.to_str())
21089 .map(|a| a.to_string()));
21090
21091 let vim_mode = vim_enabled(cx);
21092
21093 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21094 let copilot_enabled = edit_predictions_provider
21095 == language::language_settings::EditPredictionProvider::Copilot;
21096 let copilot_enabled_for_language = self
21097 .buffer
21098 .read(cx)
21099 .language_settings(cx)
21100 .show_edit_predictions;
21101
21102 let project = project.read(cx);
21103 let event_type = reported_event.event_type();
21104
21105 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21106 telemetry::event!(
21107 event_type,
21108 type = if auto_saved {"autosave"} else {"manual"},
21109 file_extension,
21110 vim_mode,
21111 copilot_enabled,
21112 copilot_enabled_for_language,
21113 edit_predictions_provider,
21114 is_via_ssh = project.is_via_remote_server(),
21115 );
21116 } else {
21117 telemetry::event!(
21118 event_type,
21119 file_extension,
21120 vim_mode,
21121 copilot_enabled,
21122 copilot_enabled_for_language,
21123 edit_predictions_provider,
21124 is_via_ssh = project.is_via_remote_server(),
21125 );
21126 };
21127 }
21128
21129 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21130 /// with each line being an array of {text, highlight} objects.
21131 fn copy_highlight_json(
21132 &mut self,
21133 _: &CopyHighlightJson,
21134 window: &mut Window,
21135 cx: &mut Context<Self>,
21136 ) {
21137 #[derive(Serialize)]
21138 struct Chunk<'a> {
21139 text: String,
21140 highlight: Option<&'a str>,
21141 }
21142
21143 let snapshot = self.buffer.read(cx).snapshot(cx);
21144 let range = self
21145 .selected_text_range(false, window, cx)
21146 .and_then(|selection| {
21147 if selection.range.is_empty() {
21148 None
21149 } else {
21150 Some(selection.range)
21151 }
21152 })
21153 .unwrap_or_else(|| 0..snapshot.len());
21154
21155 let chunks = snapshot.chunks(range, true);
21156 let mut lines = Vec::new();
21157 let mut line: VecDeque<Chunk> = VecDeque::new();
21158
21159 let Some(style) = self.style.as_ref() else {
21160 return;
21161 };
21162
21163 for chunk in chunks {
21164 let highlight = chunk
21165 .syntax_highlight_id
21166 .and_then(|id| id.name(&style.syntax));
21167 let mut chunk_lines = chunk.text.split('\n').peekable();
21168 while let Some(text) = chunk_lines.next() {
21169 let mut merged_with_last_token = false;
21170 if let Some(last_token) = line.back_mut()
21171 && last_token.highlight == highlight
21172 {
21173 last_token.text.push_str(text);
21174 merged_with_last_token = true;
21175 }
21176
21177 if !merged_with_last_token {
21178 line.push_back(Chunk {
21179 text: text.into(),
21180 highlight,
21181 });
21182 }
21183
21184 if chunk_lines.peek().is_some() {
21185 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21186 line.pop_front();
21187 }
21188 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21189 line.pop_back();
21190 }
21191
21192 lines.push(mem::take(&mut line));
21193 }
21194 }
21195 }
21196
21197 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21198 return;
21199 };
21200 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21201 }
21202
21203 pub fn open_context_menu(
21204 &mut self,
21205 _: &OpenContextMenu,
21206 window: &mut Window,
21207 cx: &mut Context<Self>,
21208 ) {
21209 self.request_autoscroll(Autoscroll::newest(), cx);
21210 let position = self.selections.newest_display(cx).start;
21211 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21212 }
21213
21214 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21215 &self.inlay_hint_cache
21216 }
21217
21218 pub fn replay_insert_event(
21219 &mut self,
21220 text: &str,
21221 relative_utf16_range: Option<Range<isize>>,
21222 window: &mut Window,
21223 cx: &mut Context<Self>,
21224 ) {
21225 if !self.input_enabled {
21226 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21227 return;
21228 }
21229 if let Some(relative_utf16_range) = relative_utf16_range {
21230 let selections = self.selections.all::<OffsetUtf16>(cx);
21231 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21232 let new_ranges = selections.into_iter().map(|range| {
21233 let start = OffsetUtf16(
21234 range
21235 .head()
21236 .0
21237 .saturating_add_signed(relative_utf16_range.start),
21238 );
21239 let end = OffsetUtf16(
21240 range
21241 .head()
21242 .0
21243 .saturating_add_signed(relative_utf16_range.end),
21244 );
21245 start..end
21246 });
21247 s.select_ranges(new_ranges);
21248 });
21249 }
21250
21251 self.handle_input(text, window, cx);
21252 }
21253
21254 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21255 let Some(provider) = self.semantics_provider.as_ref() else {
21256 return false;
21257 };
21258
21259 let mut supports = false;
21260 self.buffer().update(cx, |this, cx| {
21261 this.for_each_buffer(|buffer| {
21262 supports |= provider.supports_inlay_hints(buffer, cx);
21263 });
21264 });
21265
21266 supports
21267 }
21268
21269 pub fn is_focused(&self, window: &Window) -> bool {
21270 self.focus_handle.is_focused(window)
21271 }
21272
21273 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21274 cx.emit(EditorEvent::Focused);
21275
21276 if let Some(descendant) = self
21277 .last_focused_descendant
21278 .take()
21279 .and_then(|descendant| descendant.upgrade())
21280 {
21281 window.focus(&descendant);
21282 } else {
21283 if let Some(blame) = self.blame.as_ref() {
21284 blame.update(cx, GitBlame::focus)
21285 }
21286
21287 self.blink_manager.update(cx, BlinkManager::enable);
21288 self.show_cursor_names(window, cx);
21289 self.buffer.update(cx, |buffer, cx| {
21290 buffer.finalize_last_transaction(cx);
21291 if self.leader_id.is_none() {
21292 buffer.set_active_selections(
21293 &self.selections.disjoint_anchors_arc(),
21294 self.selections.line_mode,
21295 self.cursor_shape,
21296 cx,
21297 );
21298 }
21299 });
21300 }
21301 }
21302
21303 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21304 cx.emit(EditorEvent::FocusedIn)
21305 }
21306
21307 fn handle_focus_out(
21308 &mut self,
21309 event: FocusOutEvent,
21310 _window: &mut Window,
21311 cx: &mut Context<Self>,
21312 ) {
21313 if event.blurred != self.focus_handle {
21314 self.last_focused_descendant = Some(event.blurred);
21315 }
21316 self.selection_drag_state = SelectionDragState::None;
21317 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21318 }
21319
21320 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21321 self.blink_manager.update(cx, BlinkManager::disable);
21322 self.buffer
21323 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21324
21325 if let Some(blame) = self.blame.as_ref() {
21326 blame.update(cx, GitBlame::blur)
21327 }
21328 if !self.hover_state.focused(window, cx) {
21329 hide_hover(self, cx);
21330 }
21331 if !self
21332 .context_menu
21333 .borrow()
21334 .as_ref()
21335 .is_some_and(|context_menu| context_menu.focused(window, cx))
21336 {
21337 self.hide_context_menu(window, cx);
21338 }
21339 self.discard_edit_prediction(false, cx);
21340 cx.emit(EditorEvent::Blurred);
21341 cx.notify();
21342 }
21343
21344 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21345 let mut pending: String = window
21346 .pending_input_keystrokes()
21347 .into_iter()
21348 .flatten()
21349 .filter_map(|keystroke| {
21350 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21351 keystroke.key_char.clone()
21352 } else {
21353 None
21354 }
21355 })
21356 .collect();
21357
21358 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21359 pending = "".to_string();
21360 }
21361
21362 let existing_pending = self
21363 .text_highlights::<PendingInput>(cx)
21364 .map(|(_, ranges)| ranges.to_vec());
21365 if existing_pending.is_none() && pending.is_empty() {
21366 return;
21367 }
21368 let transaction =
21369 self.transact(window, cx, |this, window, cx| {
21370 let selections = this.selections.all::<usize>(cx);
21371 let edits = selections
21372 .iter()
21373 .map(|selection| (selection.end..selection.end, pending.clone()));
21374 this.edit(edits, cx);
21375 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21376 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21377 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21378 }));
21379 });
21380 if let Some(existing_ranges) = existing_pending {
21381 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21382 this.edit(edits, cx);
21383 }
21384 });
21385
21386 let snapshot = self.snapshot(window, cx);
21387 let ranges = self
21388 .selections
21389 .all::<usize>(cx)
21390 .into_iter()
21391 .map(|selection| {
21392 snapshot.buffer_snapshot.anchor_after(selection.end)
21393 ..snapshot
21394 .buffer_snapshot
21395 .anchor_before(selection.end + pending.len())
21396 })
21397 .collect();
21398
21399 if pending.is_empty() {
21400 self.clear_highlights::<PendingInput>(cx);
21401 } else {
21402 self.highlight_text::<PendingInput>(
21403 ranges,
21404 HighlightStyle {
21405 underline: Some(UnderlineStyle {
21406 thickness: px(1.),
21407 color: None,
21408 wavy: false,
21409 }),
21410 ..Default::default()
21411 },
21412 cx,
21413 );
21414 }
21415
21416 self.ime_transaction = self.ime_transaction.or(transaction);
21417 if let Some(transaction) = self.ime_transaction {
21418 self.buffer.update(cx, |buffer, cx| {
21419 buffer.group_until_transaction(transaction, cx);
21420 });
21421 }
21422
21423 if self.text_highlights::<PendingInput>(cx).is_none() {
21424 self.ime_transaction.take();
21425 }
21426 }
21427
21428 pub fn register_action_renderer(
21429 &mut self,
21430 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21431 ) -> Subscription {
21432 let id = self.next_editor_action_id.post_inc();
21433 self.editor_actions
21434 .borrow_mut()
21435 .insert(id, Box::new(listener));
21436
21437 let editor_actions = self.editor_actions.clone();
21438 Subscription::new(move || {
21439 editor_actions.borrow_mut().remove(&id);
21440 })
21441 }
21442
21443 pub fn register_action<A: Action>(
21444 &mut self,
21445 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21446 ) -> Subscription {
21447 let id = self.next_editor_action_id.post_inc();
21448 let listener = Arc::new(listener);
21449 self.editor_actions.borrow_mut().insert(
21450 id,
21451 Box::new(move |_, window, _| {
21452 let listener = listener.clone();
21453 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21454 let action = action.downcast_ref().unwrap();
21455 if phase == DispatchPhase::Bubble {
21456 listener(action, window, cx)
21457 }
21458 })
21459 }),
21460 );
21461
21462 let editor_actions = self.editor_actions.clone();
21463 Subscription::new(move || {
21464 editor_actions.borrow_mut().remove(&id);
21465 })
21466 }
21467
21468 pub fn file_header_size(&self) -> u32 {
21469 FILE_HEADER_HEIGHT
21470 }
21471
21472 pub fn restore(
21473 &mut self,
21474 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21475 window: &mut Window,
21476 cx: &mut Context<Self>,
21477 ) {
21478 let workspace = self.workspace();
21479 let project = self.project();
21480 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21481 let mut tasks = Vec::new();
21482 for (buffer_id, changes) in revert_changes {
21483 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21484 buffer.update(cx, |buffer, cx| {
21485 buffer.edit(
21486 changes
21487 .into_iter()
21488 .map(|(range, text)| (range, text.to_string())),
21489 None,
21490 cx,
21491 );
21492 });
21493
21494 if let Some(project) =
21495 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21496 {
21497 project.update(cx, |project, cx| {
21498 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21499 })
21500 }
21501 }
21502 }
21503 tasks
21504 });
21505 cx.spawn_in(window, async move |_, cx| {
21506 for (buffer, task) in save_tasks {
21507 let result = task.await;
21508 if result.is_err() {
21509 let Some(path) = buffer
21510 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21511 .ok()
21512 else {
21513 continue;
21514 };
21515 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21516 let Some(task) = cx
21517 .update_window_entity(workspace, |workspace, window, cx| {
21518 workspace
21519 .open_path_preview(path, None, false, false, false, window, cx)
21520 })
21521 .ok()
21522 else {
21523 continue;
21524 };
21525 task.await.log_err();
21526 }
21527 }
21528 }
21529 })
21530 .detach();
21531 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21532 selections.refresh()
21533 });
21534 }
21535
21536 pub fn to_pixel_point(
21537 &self,
21538 source: multi_buffer::Anchor,
21539 editor_snapshot: &EditorSnapshot,
21540 window: &mut Window,
21541 ) -> Option<gpui::Point<Pixels>> {
21542 let source_point = source.to_display_point(editor_snapshot);
21543 self.display_to_pixel_point(source_point, editor_snapshot, window)
21544 }
21545
21546 pub fn display_to_pixel_point(
21547 &self,
21548 source: DisplayPoint,
21549 editor_snapshot: &EditorSnapshot,
21550 window: &mut Window,
21551 ) -> Option<gpui::Point<Pixels>> {
21552 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21553 let text_layout_details = self.text_layout_details(window);
21554 let scroll_top = text_layout_details
21555 .scroll_anchor
21556 .scroll_position(editor_snapshot)
21557 .y;
21558
21559 if source.row().as_f32() < scroll_top.floor() {
21560 return None;
21561 }
21562 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21563 let source_y = line_height * (source.row().as_f32() - scroll_top);
21564 Some(gpui::Point::new(source_x, source_y))
21565 }
21566
21567 pub fn has_visible_completions_menu(&self) -> bool {
21568 !self.edit_prediction_preview_is_active()
21569 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21570 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21571 })
21572 }
21573
21574 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21575 if self.mode.is_minimap() {
21576 return;
21577 }
21578 self.addons
21579 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21580 }
21581
21582 pub fn unregister_addon<T: Addon>(&mut self) {
21583 self.addons.remove(&std::any::TypeId::of::<T>());
21584 }
21585
21586 pub fn addon<T: Addon>(&self) -> Option<&T> {
21587 let type_id = std::any::TypeId::of::<T>();
21588 self.addons
21589 .get(&type_id)
21590 .and_then(|item| item.to_any().downcast_ref::<T>())
21591 }
21592
21593 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21594 let type_id = std::any::TypeId::of::<T>();
21595 self.addons
21596 .get_mut(&type_id)
21597 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21598 }
21599
21600 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21601 let text_layout_details = self.text_layout_details(window);
21602 let style = &text_layout_details.editor_style;
21603 let font_id = window.text_system().resolve_font(&style.text.font());
21604 let font_size = style.text.font_size.to_pixels(window.rem_size());
21605 let line_height = style.text.line_height_in_pixels(window.rem_size());
21606 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21607 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21608
21609 CharacterDimensions {
21610 em_width,
21611 em_advance,
21612 line_height,
21613 }
21614 }
21615
21616 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21617 self.load_diff_task.clone()
21618 }
21619
21620 fn read_metadata_from_db(
21621 &mut self,
21622 item_id: u64,
21623 workspace_id: WorkspaceId,
21624 window: &mut Window,
21625 cx: &mut Context<Editor>,
21626 ) {
21627 if self.is_singleton(cx)
21628 && !self.mode.is_minimap()
21629 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21630 {
21631 let buffer_snapshot = OnceCell::new();
21632
21633 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21634 && !folds.is_empty()
21635 {
21636 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21637 self.fold_ranges(
21638 folds
21639 .into_iter()
21640 .map(|(start, end)| {
21641 snapshot.clip_offset(start, Bias::Left)
21642 ..snapshot.clip_offset(end, Bias::Right)
21643 })
21644 .collect(),
21645 false,
21646 window,
21647 cx,
21648 );
21649 }
21650
21651 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21652 && !selections.is_empty()
21653 {
21654 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21655 // skip adding the initial selection to selection history
21656 self.selection_history.mode = SelectionHistoryMode::Skipping;
21657 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21658 s.select_ranges(selections.into_iter().map(|(start, end)| {
21659 snapshot.clip_offset(start, Bias::Left)
21660 ..snapshot.clip_offset(end, Bias::Right)
21661 }));
21662 });
21663 self.selection_history.mode = SelectionHistoryMode::Normal;
21664 };
21665 }
21666
21667 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21668 }
21669
21670 fn update_lsp_data(
21671 &mut self,
21672 ignore_cache: bool,
21673 for_buffer: Option<BufferId>,
21674 window: &mut Window,
21675 cx: &mut Context<'_, Self>,
21676 ) {
21677 self.pull_diagnostics(for_buffer, window, cx);
21678 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21679 }
21680}
21681
21682fn vim_enabled(cx: &App) -> bool {
21683 vim_mode_setting::VimModeSetting::try_get(cx)
21684 .map(|vim_mode| vim_mode.0)
21685 .unwrap_or(false)
21686}
21687
21688fn process_completion_for_edit(
21689 completion: &Completion,
21690 intent: CompletionIntent,
21691 buffer: &Entity<Buffer>,
21692 cursor_position: &text::Anchor,
21693 cx: &mut Context<Editor>,
21694) -> CompletionEdit {
21695 let buffer = buffer.read(cx);
21696 let buffer_snapshot = buffer.snapshot();
21697 let (snippet, new_text) = if completion.is_snippet() {
21698 // Workaround for typescript language server issues so that methods don't expand within
21699 // strings and functions with type expressions. The previous point is used because the query
21700 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21701 let mut snippet_source = completion.new_text.clone();
21702 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21703 previous_point.column = previous_point.column.saturating_sub(1);
21704 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21705 && scope.prefers_label_for_snippet_in_completion()
21706 && let Some(label) = completion.label()
21707 && matches!(
21708 completion.kind(),
21709 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21710 )
21711 {
21712 snippet_source = label;
21713 }
21714 match Snippet::parse(&snippet_source).log_err() {
21715 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21716 None => (None, completion.new_text.clone()),
21717 }
21718 } else {
21719 (None, completion.new_text.clone())
21720 };
21721
21722 let mut range_to_replace = {
21723 let replace_range = &completion.replace_range;
21724 if let CompletionSource::Lsp {
21725 insert_range: Some(insert_range),
21726 ..
21727 } = &completion.source
21728 {
21729 debug_assert_eq!(
21730 insert_range.start, replace_range.start,
21731 "insert_range and replace_range should start at the same position"
21732 );
21733 debug_assert!(
21734 insert_range
21735 .start
21736 .cmp(cursor_position, &buffer_snapshot)
21737 .is_le(),
21738 "insert_range should start before or at cursor position"
21739 );
21740 debug_assert!(
21741 replace_range
21742 .start
21743 .cmp(cursor_position, &buffer_snapshot)
21744 .is_le(),
21745 "replace_range should start before or at cursor position"
21746 );
21747
21748 let should_replace = match intent {
21749 CompletionIntent::CompleteWithInsert => false,
21750 CompletionIntent::CompleteWithReplace => true,
21751 CompletionIntent::Complete | CompletionIntent::Compose => {
21752 let insert_mode =
21753 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21754 .completions
21755 .lsp_insert_mode;
21756 match insert_mode {
21757 LspInsertMode::Insert => false,
21758 LspInsertMode::Replace => true,
21759 LspInsertMode::ReplaceSubsequence => {
21760 let mut text_to_replace = buffer.chars_for_range(
21761 buffer.anchor_before(replace_range.start)
21762 ..buffer.anchor_after(replace_range.end),
21763 );
21764 let mut current_needle = text_to_replace.next();
21765 for haystack_ch in completion.label.text.chars() {
21766 if let Some(needle_ch) = current_needle
21767 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21768 {
21769 current_needle = text_to_replace.next();
21770 }
21771 }
21772 current_needle.is_none()
21773 }
21774 LspInsertMode::ReplaceSuffix => {
21775 if replace_range
21776 .end
21777 .cmp(cursor_position, &buffer_snapshot)
21778 .is_gt()
21779 {
21780 let range_after_cursor = *cursor_position..replace_range.end;
21781 let text_after_cursor = buffer
21782 .text_for_range(
21783 buffer.anchor_before(range_after_cursor.start)
21784 ..buffer.anchor_after(range_after_cursor.end),
21785 )
21786 .collect::<String>()
21787 .to_ascii_lowercase();
21788 completion
21789 .label
21790 .text
21791 .to_ascii_lowercase()
21792 .ends_with(&text_after_cursor)
21793 } else {
21794 true
21795 }
21796 }
21797 }
21798 }
21799 };
21800
21801 if should_replace {
21802 replace_range.clone()
21803 } else {
21804 insert_range.clone()
21805 }
21806 } else {
21807 replace_range.clone()
21808 }
21809 };
21810
21811 if range_to_replace
21812 .end
21813 .cmp(cursor_position, &buffer_snapshot)
21814 .is_lt()
21815 {
21816 range_to_replace.end = *cursor_position;
21817 }
21818
21819 CompletionEdit {
21820 new_text,
21821 replace_range: range_to_replace.to_offset(buffer),
21822 snippet,
21823 }
21824}
21825
21826struct CompletionEdit {
21827 new_text: String,
21828 replace_range: Range<usize>,
21829 snippet: Option<Snippet>,
21830}
21831
21832fn insert_extra_newline_brackets(
21833 buffer: &MultiBufferSnapshot,
21834 range: Range<usize>,
21835 language: &language::LanguageScope,
21836) -> bool {
21837 let leading_whitespace_len = buffer
21838 .reversed_chars_at(range.start)
21839 .take_while(|c| c.is_whitespace() && *c != '\n')
21840 .map(|c| c.len_utf8())
21841 .sum::<usize>();
21842 let trailing_whitespace_len = buffer
21843 .chars_at(range.end)
21844 .take_while(|c| c.is_whitespace() && *c != '\n')
21845 .map(|c| c.len_utf8())
21846 .sum::<usize>();
21847 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21848
21849 language.brackets().any(|(pair, enabled)| {
21850 let pair_start = pair.start.trim_end();
21851 let pair_end = pair.end.trim_start();
21852
21853 enabled
21854 && pair.newline
21855 && buffer.contains_str_at(range.end, pair_end)
21856 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21857 })
21858}
21859
21860fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21861 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21862 [(buffer, range, _)] => (*buffer, range.clone()),
21863 _ => return false,
21864 };
21865 let pair = {
21866 let mut result: Option<BracketMatch> = None;
21867
21868 for pair in buffer
21869 .all_bracket_ranges(range.clone())
21870 .filter(move |pair| {
21871 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21872 })
21873 {
21874 let len = pair.close_range.end - pair.open_range.start;
21875
21876 if let Some(existing) = &result {
21877 let existing_len = existing.close_range.end - existing.open_range.start;
21878 if len > existing_len {
21879 continue;
21880 }
21881 }
21882
21883 result = Some(pair);
21884 }
21885
21886 result
21887 };
21888 let Some(pair) = pair else {
21889 return false;
21890 };
21891 pair.newline_only
21892 && buffer
21893 .chars_for_range(pair.open_range.end..range.start)
21894 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21895 .all(|c| c.is_whitespace() && c != '\n')
21896}
21897
21898fn update_uncommitted_diff_for_buffer(
21899 editor: Entity<Editor>,
21900 project: &Entity<Project>,
21901 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21902 buffer: Entity<MultiBuffer>,
21903 cx: &mut App,
21904) -> Task<()> {
21905 let mut tasks = Vec::new();
21906 project.update(cx, |project, cx| {
21907 for buffer in buffers {
21908 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21909 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21910 }
21911 }
21912 });
21913 cx.spawn(async move |cx| {
21914 let diffs = future::join_all(tasks).await;
21915 if editor
21916 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21917 .unwrap_or(false)
21918 {
21919 return;
21920 }
21921
21922 buffer
21923 .update(cx, |buffer, cx| {
21924 for diff in diffs.into_iter().flatten() {
21925 buffer.add_diff(diff, cx);
21926 }
21927 })
21928 .ok();
21929 })
21930}
21931
21932fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21933 let tab_size = tab_size.get() as usize;
21934 let mut width = offset;
21935
21936 for ch in text.chars() {
21937 width += if ch == '\t' {
21938 tab_size - (width % tab_size)
21939 } else {
21940 1
21941 };
21942 }
21943
21944 width - offset
21945}
21946
21947#[cfg(test)]
21948mod tests {
21949 use super::*;
21950
21951 #[test]
21952 fn test_string_size_with_expanded_tabs() {
21953 let nz = |val| NonZeroU32::new(val).unwrap();
21954 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21955 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21956 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21957 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21958 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21959 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21960 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21961 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21962 }
21963}
21964
21965/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21966struct WordBreakingTokenizer<'a> {
21967 input: &'a str,
21968}
21969
21970impl<'a> WordBreakingTokenizer<'a> {
21971 fn new(input: &'a str) -> Self {
21972 Self { input }
21973 }
21974}
21975
21976fn is_char_ideographic(ch: char) -> bool {
21977 use unicode_script::Script::*;
21978 use unicode_script::UnicodeScript;
21979 matches!(ch.script(), Han | Tangut | Yi)
21980}
21981
21982fn is_grapheme_ideographic(text: &str) -> bool {
21983 text.chars().any(is_char_ideographic)
21984}
21985
21986fn is_grapheme_whitespace(text: &str) -> bool {
21987 text.chars().any(|x| x.is_whitespace())
21988}
21989
21990fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21991 text.chars()
21992 .next()
21993 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
21994}
21995
21996#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21997enum WordBreakToken<'a> {
21998 Word { token: &'a str, grapheme_len: usize },
21999 InlineWhitespace { token: &'a str, grapheme_len: usize },
22000 Newline,
22001}
22002
22003impl<'a> Iterator for WordBreakingTokenizer<'a> {
22004 /// Yields a span, the count of graphemes in the token, and whether it was
22005 /// whitespace. Note that it also breaks at word boundaries.
22006 type Item = WordBreakToken<'a>;
22007
22008 fn next(&mut self) -> Option<Self::Item> {
22009 use unicode_segmentation::UnicodeSegmentation;
22010 if self.input.is_empty() {
22011 return None;
22012 }
22013
22014 let mut iter = self.input.graphemes(true).peekable();
22015 let mut offset = 0;
22016 let mut grapheme_len = 0;
22017 if let Some(first_grapheme) = iter.next() {
22018 let is_newline = first_grapheme == "\n";
22019 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22020 offset += first_grapheme.len();
22021 grapheme_len += 1;
22022 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22023 if let Some(grapheme) = iter.peek().copied()
22024 && should_stay_with_preceding_ideograph(grapheme)
22025 {
22026 offset += grapheme.len();
22027 grapheme_len += 1;
22028 }
22029 } else {
22030 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22031 let mut next_word_bound = words.peek().copied();
22032 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22033 next_word_bound = words.next();
22034 }
22035 while let Some(grapheme) = iter.peek().copied() {
22036 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22037 break;
22038 };
22039 if is_grapheme_whitespace(grapheme) != is_whitespace
22040 || (grapheme == "\n") != is_newline
22041 {
22042 break;
22043 };
22044 offset += grapheme.len();
22045 grapheme_len += 1;
22046 iter.next();
22047 }
22048 }
22049 let token = &self.input[..offset];
22050 self.input = &self.input[offset..];
22051 if token == "\n" {
22052 Some(WordBreakToken::Newline)
22053 } else if is_whitespace {
22054 Some(WordBreakToken::InlineWhitespace {
22055 token,
22056 grapheme_len,
22057 })
22058 } else {
22059 Some(WordBreakToken::Word {
22060 token,
22061 grapheme_len,
22062 })
22063 }
22064 } else {
22065 None
22066 }
22067 }
22068}
22069
22070#[test]
22071fn test_word_breaking_tokenizer() {
22072 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22073 ("", &[]),
22074 (" ", &[whitespace(" ", 2)]),
22075 ("Ʒ", &[word("Ʒ", 1)]),
22076 ("Ǽ", &[word("Ǽ", 1)]),
22077 ("⋑", &[word("⋑", 1)]),
22078 ("⋑⋑", &[word("⋑⋑", 2)]),
22079 (
22080 "原理,进而",
22081 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22082 ),
22083 (
22084 "hello world",
22085 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22086 ),
22087 (
22088 "hello, world",
22089 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22090 ),
22091 (
22092 " hello world",
22093 &[
22094 whitespace(" ", 2),
22095 word("hello", 5),
22096 whitespace(" ", 1),
22097 word("world", 5),
22098 ],
22099 ),
22100 (
22101 "这是什么 \n 钢笔",
22102 &[
22103 word("这", 1),
22104 word("是", 1),
22105 word("什", 1),
22106 word("么", 1),
22107 whitespace(" ", 1),
22108 newline(),
22109 whitespace(" ", 1),
22110 word("钢", 1),
22111 word("笔", 1),
22112 ],
22113 ),
22114 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22115 ];
22116
22117 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22118 WordBreakToken::Word {
22119 token,
22120 grapheme_len,
22121 }
22122 }
22123
22124 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22125 WordBreakToken::InlineWhitespace {
22126 token,
22127 grapheme_len,
22128 }
22129 }
22130
22131 fn newline() -> WordBreakToken<'static> {
22132 WordBreakToken::Newline
22133 }
22134
22135 for (input, result) in tests {
22136 assert_eq!(
22137 WordBreakingTokenizer::new(input)
22138 .collect::<Vec<_>>()
22139 .as_slice(),
22140 *result,
22141 );
22142 }
22143}
22144
22145fn wrap_with_prefix(
22146 first_line_prefix: String,
22147 subsequent_lines_prefix: String,
22148 unwrapped_text: String,
22149 wrap_column: usize,
22150 tab_size: NonZeroU32,
22151 preserve_existing_whitespace: bool,
22152) -> String {
22153 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22154 let subsequent_lines_prefix_len =
22155 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22156 let mut wrapped_text = String::new();
22157 let mut current_line = first_line_prefix;
22158 let mut is_first_line = true;
22159
22160 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22161 let mut current_line_len = first_line_prefix_len;
22162 let mut in_whitespace = false;
22163 for token in tokenizer {
22164 let have_preceding_whitespace = in_whitespace;
22165 match token {
22166 WordBreakToken::Word {
22167 token,
22168 grapheme_len,
22169 } => {
22170 in_whitespace = false;
22171 let current_prefix_len = if is_first_line {
22172 first_line_prefix_len
22173 } else {
22174 subsequent_lines_prefix_len
22175 };
22176 if current_line_len + grapheme_len > wrap_column
22177 && current_line_len != current_prefix_len
22178 {
22179 wrapped_text.push_str(current_line.trim_end());
22180 wrapped_text.push('\n');
22181 is_first_line = false;
22182 current_line = subsequent_lines_prefix.clone();
22183 current_line_len = subsequent_lines_prefix_len;
22184 }
22185 current_line.push_str(token);
22186 current_line_len += grapheme_len;
22187 }
22188 WordBreakToken::InlineWhitespace {
22189 mut token,
22190 mut grapheme_len,
22191 } => {
22192 in_whitespace = true;
22193 if have_preceding_whitespace && !preserve_existing_whitespace {
22194 continue;
22195 }
22196 if !preserve_existing_whitespace {
22197 token = " ";
22198 grapheme_len = 1;
22199 }
22200 let current_prefix_len = if is_first_line {
22201 first_line_prefix_len
22202 } else {
22203 subsequent_lines_prefix_len
22204 };
22205 if current_line_len + grapheme_len > wrap_column {
22206 wrapped_text.push_str(current_line.trim_end());
22207 wrapped_text.push('\n');
22208 is_first_line = false;
22209 current_line = subsequent_lines_prefix.clone();
22210 current_line_len = subsequent_lines_prefix_len;
22211 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22212 current_line.push_str(token);
22213 current_line_len += grapheme_len;
22214 }
22215 }
22216 WordBreakToken::Newline => {
22217 in_whitespace = true;
22218 let current_prefix_len = if is_first_line {
22219 first_line_prefix_len
22220 } else {
22221 subsequent_lines_prefix_len
22222 };
22223 if preserve_existing_whitespace {
22224 wrapped_text.push_str(current_line.trim_end());
22225 wrapped_text.push('\n');
22226 is_first_line = false;
22227 current_line = subsequent_lines_prefix.clone();
22228 current_line_len = subsequent_lines_prefix_len;
22229 } else if have_preceding_whitespace {
22230 continue;
22231 } else if current_line_len + 1 > wrap_column
22232 && current_line_len != current_prefix_len
22233 {
22234 wrapped_text.push_str(current_line.trim_end());
22235 wrapped_text.push('\n');
22236 is_first_line = false;
22237 current_line = subsequent_lines_prefix.clone();
22238 current_line_len = subsequent_lines_prefix_len;
22239 } else if current_line_len != current_prefix_len {
22240 current_line.push(' ');
22241 current_line_len += 1;
22242 }
22243 }
22244 }
22245 }
22246
22247 if !current_line.is_empty() {
22248 wrapped_text.push_str(¤t_line);
22249 }
22250 wrapped_text
22251}
22252
22253#[test]
22254fn test_wrap_with_prefix() {
22255 assert_eq!(
22256 wrap_with_prefix(
22257 "# ".to_string(),
22258 "# ".to_string(),
22259 "abcdefg".to_string(),
22260 4,
22261 NonZeroU32::new(4).unwrap(),
22262 false,
22263 ),
22264 "# abcdefg"
22265 );
22266 assert_eq!(
22267 wrap_with_prefix(
22268 "".to_string(),
22269 "".to_string(),
22270 "\thello world".to_string(),
22271 8,
22272 NonZeroU32::new(4).unwrap(),
22273 false,
22274 ),
22275 "hello\nworld"
22276 );
22277 assert_eq!(
22278 wrap_with_prefix(
22279 "// ".to_string(),
22280 "// ".to_string(),
22281 "xx \nyy zz aa bb cc".to_string(),
22282 12,
22283 NonZeroU32::new(4).unwrap(),
22284 false,
22285 ),
22286 "// xx yy zz\n// aa bb cc"
22287 );
22288 assert_eq!(
22289 wrap_with_prefix(
22290 String::new(),
22291 String::new(),
22292 "这是什么 \n 钢笔".to_string(),
22293 3,
22294 NonZeroU32::new(4).unwrap(),
22295 false,
22296 ),
22297 "这是什\n么 钢\n笔"
22298 );
22299}
22300
22301pub trait CollaborationHub {
22302 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22303 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22304 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22305}
22306
22307impl CollaborationHub for Entity<Project> {
22308 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22309 self.read(cx).collaborators()
22310 }
22311
22312 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22313 self.read(cx).user_store().read(cx).participant_indices()
22314 }
22315
22316 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22317 let this = self.read(cx);
22318 let user_ids = this.collaborators().values().map(|c| c.user_id);
22319 this.user_store().read(cx).participant_names(user_ids, cx)
22320 }
22321}
22322
22323pub trait SemanticsProvider {
22324 fn hover(
22325 &self,
22326 buffer: &Entity<Buffer>,
22327 position: text::Anchor,
22328 cx: &mut App,
22329 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22330
22331 fn inline_values(
22332 &self,
22333 buffer_handle: Entity<Buffer>,
22334 range: Range<text::Anchor>,
22335 cx: &mut App,
22336 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22337
22338 fn inlay_hints(
22339 &self,
22340 buffer_handle: Entity<Buffer>,
22341 range: Range<text::Anchor>,
22342 cx: &mut App,
22343 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22344
22345 fn resolve_inlay_hint(
22346 &self,
22347 hint: InlayHint,
22348 buffer_handle: Entity<Buffer>,
22349 server_id: LanguageServerId,
22350 cx: &mut App,
22351 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22352
22353 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22354
22355 fn document_highlights(
22356 &self,
22357 buffer: &Entity<Buffer>,
22358 position: text::Anchor,
22359 cx: &mut App,
22360 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22361
22362 fn definitions(
22363 &self,
22364 buffer: &Entity<Buffer>,
22365 position: text::Anchor,
22366 kind: GotoDefinitionKind,
22367 cx: &mut App,
22368 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22369
22370 fn range_for_rename(
22371 &self,
22372 buffer: &Entity<Buffer>,
22373 position: text::Anchor,
22374 cx: &mut App,
22375 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22376
22377 fn perform_rename(
22378 &self,
22379 buffer: &Entity<Buffer>,
22380 position: text::Anchor,
22381 new_name: String,
22382 cx: &mut App,
22383 ) -> Option<Task<Result<ProjectTransaction>>>;
22384}
22385
22386pub trait CompletionProvider {
22387 fn completions(
22388 &self,
22389 excerpt_id: ExcerptId,
22390 buffer: &Entity<Buffer>,
22391 buffer_position: text::Anchor,
22392 trigger: CompletionContext,
22393 window: &mut Window,
22394 cx: &mut Context<Editor>,
22395 ) -> Task<Result<Vec<CompletionResponse>>>;
22396
22397 fn resolve_completions(
22398 &self,
22399 _buffer: Entity<Buffer>,
22400 _completion_indices: Vec<usize>,
22401 _completions: Rc<RefCell<Box<[Completion]>>>,
22402 _cx: &mut Context<Editor>,
22403 ) -> Task<Result<bool>> {
22404 Task::ready(Ok(false))
22405 }
22406
22407 fn apply_additional_edits_for_completion(
22408 &self,
22409 _buffer: Entity<Buffer>,
22410 _completions: Rc<RefCell<Box<[Completion]>>>,
22411 _completion_index: usize,
22412 _push_to_history: bool,
22413 _cx: &mut Context<Editor>,
22414 ) -> Task<Result<Option<language::Transaction>>> {
22415 Task::ready(Ok(None))
22416 }
22417
22418 fn is_completion_trigger(
22419 &self,
22420 buffer: &Entity<Buffer>,
22421 position: language::Anchor,
22422 text: &str,
22423 trigger_in_words: bool,
22424 menu_is_open: bool,
22425 cx: &mut Context<Editor>,
22426 ) -> bool;
22427
22428 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22429
22430 fn sort_completions(&self) -> bool {
22431 true
22432 }
22433
22434 fn filter_completions(&self) -> bool {
22435 true
22436 }
22437}
22438
22439pub trait CodeActionProvider {
22440 fn id(&self) -> Arc<str>;
22441
22442 fn code_actions(
22443 &self,
22444 buffer: &Entity<Buffer>,
22445 range: Range<text::Anchor>,
22446 window: &mut Window,
22447 cx: &mut App,
22448 ) -> Task<Result<Vec<CodeAction>>>;
22449
22450 fn apply_code_action(
22451 &self,
22452 buffer_handle: Entity<Buffer>,
22453 action: CodeAction,
22454 excerpt_id: ExcerptId,
22455 push_to_history: bool,
22456 window: &mut Window,
22457 cx: &mut App,
22458 ) -> Task<Result<ProjectTransaction>>;
22459}
22460
22461impl CodeActionProvider for Entity<Project> {
22462 fn id(&self) -> Arc<str> {
22463 "project".into()
22464 }
22465
22466 fn code_actions(
22467 &self,
22468 buffer: &Entity<Buffer>,
22469 range: Range<text::Anchor>,
22470 _window: &mut Window,
22471 cx: &mut App,
22472 ) -> Task<Result<Vec<CodeAction>>> {
22473 self.update(cx, |project, cx| {
22474 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22475 let code_actions = project.code_actions(buffer, range, None, cx);
22476 cx.background_spawn(async move {
22477 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22478 Ok(code_lens_actions
22479 .context("code lens fetch")?
22480 .into_iter()
22481 .flatten()
22482 .chain(
22483 code_actions
22484 .context("code action fetch")?
22485 .into_iter()
22486 .flatten(),
22487 )
22488 .collect())
22489 })
22490 })
22491 }
22492
22493 fn apply_code_action(
22494 &self,
22495 buffer_handle: Entity<Buffer>,
22496 action: CodeAction,
22497 _excerpt_id: ExcerptId,
22498 push_to_history: bool,
22499 _window: &mut Window,
22500 cx: &mut App,
22501 ) -> Task<Result<ProjectTransaction>> {
22502 self.update(cx, |project, cx| {
22503 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22504 })
22505 }
22506}
22507
22508fn snippet_completions(
22509 project: &Project,
22510 buffer: &Entity<Buffer>,
22511 buffer_position: text::Anchor,
22512 cx: &mut App,
22513) -> Task<Result<CompletionResponse>> {
22514 let languages = buffer.read(cx).languages_at(buffer_position);
22515 let snippet_store = project.snippets().read(cx);
22516
22517 let scopes: Vec<_> = languages
22518 .iter()
22519 .filter_map(|language| {
22520 let language_name = language.lsp_id();
22521 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22522
22523 if snippets.is_empty() {
22524 None
22525 } else {
22526 Some((language.default_scope(), snippets))
22527 }
22528 })
22529 .collect();
22530
22531 if scopes.is_empty() {
22532 return Task::ready(Ok(CompletionResponse {
22533 completions: vec![],
22534 display_options: CompletionDisplayOptions::default(),
22535 is_incomplete: false,
22536 }));
22537 }
22538
22539 let snapshot = buffer.read(cx).text_snapshot();
22540 let chars: String = snapshot
22541 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22542 .collect();
22543 let executor = cx.background_executor().clone();
22544
22545 cx.background_spawn(async move {
22546 let mut is_incomplete = false;
22547 let mut completions: Vec<Completion> = Vec::new();
22548 for (scope, snippets) in scopes.into_iter() {
22549 let classifier =
22550 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
22551 let mut last_word = chars
22552 .chars()
22553 .take_while(|c| classifier.is_word(*c))
22554 .collect::<String>();
22555 last_word = last_word.chars().rev().collect();
22556
22557 if last_word.is_empty() {
22558 return Ok(CompletionResponse {
22559 completions: vec![],
22560 display_options: CompletionDisplayOptions::default(),
22561 is_incomplete: true,
22562 });
22563 }
22564
22565 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22566 let to_lsp = |point: &text::Anchor| {
22567 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22568 point_to_lsp(end)
22569 };
22570 let lsp_end = to_lsp(&buffer_position);
22571
22572 let candidates = snippets
22573 .iter()
22574 .enumerate()
22575 .flat_map(|(ix, snippet)| {
22576 snippet
22577 .prefix
22578 .iter()
22579 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22580 })
22581 .collect::<Vec<StringMatchCandidate>>();
22582
22583 const MAX_RESULTS: usize = 100;
22584 let mut matches = fuzzy::match_strings(
22585 &candidates,
22586 &last_word,
22587 last_word.chars().any(|c| c.is_uppercase()),
22588 true,
22589 MAX_RESULTS,
22590 &Default::default(),
22591 executor.clone(),
22592 )
22593 .await;
22594
22595 if matches.len() >= MAX_RESULTS {
22596 is_incomplete = true;
22597 }
22598
22599 // Remove all candidates where the query's start does not match the start of any word in the candidate
22600 if let Some(query_start) = last_word.chars().next() {
22601 matches.retain(|string_match| {
22602 split_words(&string_match.string).any(|word| {
22603 // Check that the first codepoint of the word as lowercase matches the first
22604 // codepoint of the query as lowercase
22605 word.chars()
22606 .flat_map(|codepoint| codepoint.to_lowercase())
22607 .zip(query_start.to_lowercase())
22608 .all(|(word_cp, query_cp)| word_cp == query_cp)
22609 })
22610 });
22611 }
22612
22613 let matched_strings = matches
22614 .into_iter()
22615 .map(|m| m.string)
22616 .collect::<HashSet<_>>();
22617
22618 completions.extend(snippets.iter().filter_map(|snippet| {
22619 let matching_prefix = snippet
22620 .prefix
22621 .iter()
22622 .find(|prefix| matched_strings.contains(*prefix))?;
22623 let start = as_offset - last_word.len();
22624 let start = snapshot.anchor_before(start);
22625 let range = start..buffer_position;
22626 let lsp_start = to_lsp(&start);
22627 let lsp_range = lsp::Range {
22628 start: lsp_start,
22629 end: lsp_end,
22630 };
22631 Some(Completion {
22632 replace_range: range,
22633 new_text: snippet.body.clone(),
22634 source: CompletionSource::Lsp {
22635 insert_range: None,
22636 server_id: LanguageServerId(usize::MAX),
22637 resolved: true,
22638 lsp_completion: Box::new(lsp::CompletionItem {
22639 label: snippet.prefix.first().unwrap().clone(),
22640 kind: Some(CompletionItemKind::SNIPPET),
22641 label_details: snippet.description.as_ref().map(|description| {
22642 lsp::CompletionItemLabelDetails {
22643 detail: Some(description.clone()),
22644 description: None,
22645 }
22646 }),
22647 insert_text_format: Some(InsertTextFormat::SNIPPET),
22648 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22649 lsp::InsertReplaceEdit {
22650 new_text: snippet.body.clone(),
22651 insert: lsp_range,
22652 replace: lsp_range,
22653 },
22654 )),
22655 filter_text: Some(snippet.body.clone()),
22656 sort_text: Some(char::MAX.to_string()),
22657 ..lsp::CompletionItem::default()
22658 }),
22659 lsp_defaults: None,
22660 },
22661 label: CodeLabel {
22662 text: matching_prefix.clone(),
22663 runs: Vec::new(),
22664 filter_range: 0..matching_prefix.len(),
22665 },
22666 icon_path: None,
22667 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22668 single_line: snippet.name.clone().into(),
22669 plain_text: snippet
22670 .description
22671 .clone()
22672 .map(|description| description.into()),
22673 }),
22674 insert_text_mode: None,
22675 confirm: None,
22676 })
22677 }))
22678 }
22679
22680 Ok(CompletionResponse {
22681 completions,
22682 display_options: CompletionDisplayOptions::default(),
22683 is_incomplete,
22684 })
22685 })
22686}
22687
22688impl CompletionProvider for Entity<Project> {
22689 fn completions(
22690 &self,
22691 _excerpt_id: ExcerptId,
22692 buffer: &Entity<Buffer>,
22693 buffer_position: text::Anchor,
22694 options: CompletionContext,
22695 _window: &mut Window,
22696 cx: &mut Context<Editor>,
22697 ) -> Task<Result<Vec<CompletionResponse>>> {
22698 self.update(cx, |project, cx| {
22699 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22700 let project_completions = project.completions(buffer, buffer_position, options, cx);
22701 cx.background_spawn(async move {
22702 let mut responses = project_completions.await?;
22703 let snippets = snippets.await?;
22704 if !snippets.completions.is_empty() {
22705 responses.push(snippets);
22706 }
22707 Ok(responses)
22708 })
22709 })
22710 }
22711
22712 fn resolve_completions(
22713 &self,
22714 buffer: Entity<Buffer>,
22715 completion_indices: Vec<usize>,
22716 completions: Rc<RefCell<Box<[Completion]>>>,
22717 cx: &mut Context<Editor>,
22718 ) -> Task<Result<bool>> {
22719 self.update(cx, |project, cx| {
22720 project.lsp_store().update(cx, |lsp_store, cx| {
22721 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22722 })
22723 })
22724 }
22725
22726 fn apply_additional_edits_for_completion(
22727 &self,
22728 buffer: Entity<Buffer>,
22729 completions: Rc<RefCell<Box<[Completion]>>>,
22730 completion_index: usize,
22731 push_to_history: bool,
22732 cx: &mut Context<Editor>,
22733 ) -> Task<Result<Option<language::Transaction>>> {
22734 self.update(cx, |project, cx| {
22735 project.lsp_store().update(cx, |lsp_store, cx| {
22736 lsp_store.apply_additional_edits_for_completion(
22737 buffer,
22738 completions,
22739 completion_index,
22740 push_to_history,
22741 cx,
22742 )
22743 })
22744 })
22745 }
22746
22747 fn is_completion_trigger(
22748 &self,
22749 buffer: &Entity<Buffer>,
22750 position: language::Anchor,
22751 text: &str,
22752 trigger_in_words: bool,
22753 menu_is_open: bool,
22754 cx: &mut Context<Editor>,
22755 ) -> bool {
22756 let mut chars = text.chars();
22757 let char = if let Some(char) = chars.next() {
22758 char
22759 } else {
22760 return false;
22761 };
22762 if chars.next().is_some() {
22763 return false;
22764 }
22765
22766 let buffer = buffer.read(cx);
22767 let snapshot = buffer.snapshot();
22768 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22769 return false;
22770 }
22771 let classifier = snapshot
22772 .char_classifier_at(position)
22773 .scope_context(Some(CharScopeContext::Completion));
22774 if trigger_in_words && classifier.is_word(char) {
22775 return true;
22776 }
22777
22778 buffer.completion_triggers().contains(text)
22779 }
22780}
22781
22782impl SemanticsProvider for Entity<Project> {
22783 fn hover(
22784 &self,
22785 buffer: &Entity<Buffer>,
22786 position: text::Anchor,
22787 cx: &mut App,
22788 ) -> Option<Task<Option<Vec<project::Hover>>>> {
22789 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22790 }
22791
22792 fn document_highlights(
22793 &self,
22794 buffer: &Entity<Buffer>,
22795 position: text::Anchor,
22796 cx: &mut App,
22797 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22798 Some(self.update(cx, |project, cx| {
22799 project.document_highlights(buffer, position, cx)
22800 }))
22801 }
22802
22803 fn definitions(
22804 &self,
22805 buffer: &Entity<Buffer>,
22806 position: text::Anchor,
22807 kind: GotoDefinitionKind,
22808 cx: &mut App,
22809 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
22810 Some(self.update(cx, |project, cx| match kind {
22811 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
22812 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
22813 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
22814 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
22815 }))
22816 }
22817
22818 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22819 self.update(cx, |project, cx| {
22820 if project
22821 .active_debug_session(cx)
22822 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22823 {
22824 return true;
22825 }
22826
22827 buffer.update(cx, |buffer, cx| {
22828 project.any_language_server_supports_inlay_hints(buffer, cx)
22829 })
22830 })
22831 }
22832
22833 fn inline_values(
22834 &self,
22835 buffer_handle: Entity<Buffer>,
22836 range: Range<text::Anchor>,
22837 cx: &mut App,
22838 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22839 self.update(cx, |project, cx| {
22840 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22841
22842 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22843 })
22844 }
22845
22846 fn inlay_hints(
22847 &self,
22848 buffer_handle: Entity<Buffer>,
22849 range: Range<text::Anchor>,
22850 cx: &mut App,
22851 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22852 Some(self.update(cx, |project, cx| {
22853 project.inlay_hints(buffer_handle, range, cx)
22854 }))
22855 }
22856
22857 fn resolve_inlay_hint(
22858 &self,
22859 hint: InlayHint,
22860 buffer_handle: Entity<Buffer>,
22861 server_id: LanguageServerId,
22862 cx: &mut App,
22863 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22864 Some(self.update(cx, |project, cx| {
22865 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22866 }))
22867 }
22868
22869 fn range_for_rename(
22870 &self,
22871 buffer: &Entity<Buffer>,
22872 position: text::Anchor,
22873 cx: &mut App,
22874 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22875 Some(self.update(cx, |project, cx| {
22876 let buffer = buffer.clone();
22877 let task = project.prepare_rename(buffer.clone(), position, cx);
22878 cx.spawn(async move |_, cx| {
22879 Ok(match task.await? {
22880 PrepareRenameResponse::Success(range) => Some(range),
22881 PrepareRenameResponse::InvalidPosition => None,
22882 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22883 // Fallback on using TreeSitter info to determine identifier range
22884 buffer.read_with(cx, |buffer, _| {
22885 let snapshot = buffer.snapshot();
22886 let (range, kind) = snapshot.surrounding_word(position, None);
22887 if kind != Some(CharKind::Word) {
22888 return None;
22889 }
22890 Some(
22891 snapshot.anchor_before(range.start)
22892 ..snapshot.anchor_after(range.end),
22893 )
22894 })?
22895 }
22896 })
22897 })
22898 }))
22899 }
22900
22901 fn perform_rename(
22902 &self,
22903 buffer: &Entity<Buffer>,
22904 position: text::Anchor,
22905 new_name: String,
22906 cx: &mut App,
22907 ) -> Option<Task<Result<ProjectTransaction>>> {
22908 Some(self.update(cx, |project, cx| {
22909 project.perform_rename(buffer.clone(), position, new_name, cx)
22910 }))
22911 }
22912}
22913
22914fn inlay_hint_settings(
22915 location: Anchor,
22916 snapshot: &MultiBufferSnapshot,
22917 cx: &mut Context<Editor>,
22918) -> InlayHintSettings {
22919 let file = snapshot.file_at(location);
22920 let language = snapshot.language_at(location).map(|l| l.name());
22921 language_settings(language, file, cx).inlay_hints
22922}
22923
22924fn consume_contiguous_rows(
22925 contiguous_row_selections: &mut Vec<Selection<Point>>,
22926 selection: &Selection<Point>,
22927 display_map: &DisplaySnapshot,
22928 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22929) -> (MultiBufferRow, MultiBufferRow) {
22930 contiguous_row_selections.push(selection.clone());
22931 let start_row = starting_row(selection, display_map);
22932 let mut end_row = ending_row(selection, display_map);
22933
22934 while let Some(next_selection) = selections.peek() {
22935 if next_selection.start.row <= end_row.0 {
22936 end_row = ending_row(next_selection, display_map);
22937 contiguous_row_selections.push(selections.next().unwrap().clone());
22938 } else {
22939 break;
22940 }
22941 }
22942 (start_row, end_row)
22943}
22944
22945fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22946 if selection.start.column > 0 {
22947 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22948 } else {
22949 MultiBufferRow(selection.start.row)
22950 }
22951}
22952
22953fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22954 if next_selection.end.column > 0 || next_selection.is_empty() {
22955 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22956 } else {
22957 MultiBufferRow(next_selection.end.row)
22958 }
22959}
22960
22961impl EditorSnapshot {
22962 pub fn remote_selections_in_range<'a>(
22963 &'a self,
22964 range: &'a Range<Anchor>,
22965 collaboration_hub: &dyn CollaborationHub,
22966 cx: &'a App,
22967 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22968 let participant_names = collaboration_hub.user_names(cx);
22969 let participant_indices = collaboration_hub.user_participant_indices(cx);
22970 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22971 let collaborators_by_replica_id = collaborators_by_peer_id
22972 .values()
22973 .map(|collaborator| (collaborator.replica_id, collaborator))
22974 .collect::<HashMap<_, _>>();
22975 self.buffer_snapshot
22976 .selections_in_range(range, false)
22977 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22978 if replica_id == AGENT_REPLICA_ID {
22979 Some(RemoteSelection {
22980 replica_id,
22981 selection,
22982 cursor_shape,
22983 line_mode,
22984 collaborator_id: CollaboratorId::Agent,
22985 user_name: Some("Agent".into()),
22986 color: cx.theme().players().agent(),
22987 })
22988 } else {
22989 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22990 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22991 let user_name = participant_names.get(&collaborator.user_id).cloned();
22992 Some(RemoteSelection {
22993 replica_id,
22994 selection,
22995 cursor_shape,
22996 line_mode,
22997 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22998 user_name,
22999 color: if let Some(index) = participant_index {
23000 cx.theme().players().color_for_participant(index.0)
23001 } else {
23002 cx.theme().players().absent()
23003 },
23004 })
23005 }
23006 })
23007 }
23008
23009 pub fn hunks_for_ranges(
23010 &self,
23011 ranges: impl IntoIterator<Item = Range<Point>>,
23012 ) -> Vec<MultiBufferDiffHunk> {
23013 let mut hunks = Vec::new();
23014 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23015 HashMap::default();
23016 for query_range in ranges {
23017 let query_rows =
23018 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23019 for hunk in self.buffer_snapshot.diff_hunks_in_range(
23020 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23021 ) {
23022 // Include deleted hunks that are adjacent to the query range, because
23023 // otherwise they would be missed.
23024 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23025 if hunk.status().is_deleted() {
23026 intersects_range |= hunk.row_range.start == query_rows.end;
23027 intersects_range |= hunk.row_range.end == query_rows.start;
23028 }
23029 if intersects_range {
23030 if !processed_buffer_rows
23031 .entry(hunk.buffer_id)
23032 .or_default()
23033 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23034 {
23035 continue;
23036 }
23037 hunks.push(hunk);
23038 }
23039 }
23040 }
23041
23042 hunks
23043 }
23044
23045 fn display_diff_hunks_for_rows<'a>(
23046 &'a self,
23047 display_rows: Range<DisplayRow>,
23048 folded_buffers: &'a HashSet<BufferId>,
23049 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23050 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23051 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23052
23053 self.buffer_snapshot
23054 .diff_hunks_in_range(buffer_start..buffer_end)
23055 .filter_map(|hunk| {
23056 if folded_buffers.contains(&hunk.buffer_id) {
23057 return None;
23058 }
23059
23060 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23061 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23062
23063 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23064 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23065
23066 let display_hunk = if hunk_display_start.column() != 0 {
23067 DisplayDiffHunk::Folded {
23068 display_row: hunk_display_start.row(),
23069 }
23070 } else {
23071 let mut end_row = hunk_display_end.row();
23072 if hunk_display_end.column() > 0 {
23073 end_row.0 += 1;
23074 }
23075 let is_created_file = hunk.is_created_file();
23076 DisplayDiffHunk::Unfolded {
23077 status: hunk.status(),
23078 diff_base_byte_range: hunk.diff_base_byte_range,
23079 display_row_range: hunk_display_start.row()..end_row,
23080 multi_buffer_range: Anchor::range_in_buffer(
23081 hunk.excerpt_id,
23082 hunk.buffer_id,
23083 hunk.buffer_range,
23084 ),
23085 is_created_file,
23086 }
23087 };
23088
23089 Some(display_hunk)
23090 })
23091 }
23092
23093 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23094 self.display_snapshot.buffer_snapshot.language_at(position)
23095 }
23096
23097 pub fn is_focused(&self) -> bool {
23098 self.is_focused
23099 }
23100
23101 pub fn placeholder_text(&self) -> Option<String> {
23102 self.placeholder_display_snapshot
23103 .as_ref()
23104 .map(|display_map| display_map.text())
23105 }
23106
23107 pub fn scroll_position(&self) -> gpui::Point<f32> {
23108 self.scroll_anchor.scroll_position(&self.display_snapshot)
23109 }
23110
23111 fn gutter_dimensions(
23112 &self,
23113 font_id: FontId,
23114 font_size: Pixels,
23115 max_line_number_width: Pixels,
23116 cx: &App,
23117 ) -> Option<GutterDimensions> {
23118 if !self.show_gutter {
23119 return None;
23120 }
23121
23122 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23123 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23124
23125 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23126 matches!(
23127 ProjectSettings::get_global(cx).git.git_gutter,
23128 GitGutterSetting::TrackedFiles
23129 )
23130 });
23131 let gutter_settings = EditorSettings::get_global(cx).gutter;
23132 let show_line_numbers = self
23133 .show_line_numbers
23134 .unwrap_or(gutter_settings.line_numbers);
23135 let line_gutter_width = if show_line_numbers {
23136 // Avoid flicker-like gutter resizes when the line number gains another digit by
23137 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23138 let min_width_for_number_on_gutter =
23139 ch_advance * gutter_settings.min_line_number_digits as f32;
23140 max_line_number_width.max(min_width_for_number_on_gutter)
23141 } else {
23142 0.0.into()
23143 };
23144
23145 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23146 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23147
23148 let git_blame_entries_width =
23149 self.git_blame_gutter_max_author_length
23150 .map(|max_author_length| {
23151 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23152 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23153
23154 /// The number of characters to dedicate to gaps and margins.
23155 const SPACING_WIDTH: usize = 4;
23156
23157 let max_char_count = max_author_length.min(renderer.max_author_length())
23158 + ::git::SHORT_SHA_LENGTH
23159 + MAX_RELATIVE_TIMESTAMP.len()
23160 + SPACING_WIDTH;
23161
23162 ch_advance * max_char_count
23163 });
23164
23165 let is_singleton = self.buffer_snapshot.is_singleton();
23166
23167 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23168 left_padding += if !is_singleton {
23169 ch_width * 4.0
23170 } else if show_runnables || show_breakpoints {
23171 ch_width * 3.0
23172 } else if show_git_gutter && show_line_numbers {
23173 ch_width * 2.0
23174 } else if show_git_gutter || show_line_numbers {
23175 ch_width
23176 } else {
23177 px(0.)
23178 };
23179
23180 let shows_folds = is_singleton && gutter_settings.folds;
23181
23182 let right_padding = if shows_folds && show_line_numbers {
23183 ch_width * 4.0
23184 } else if shows_folds || (!is_singleton && show_line_numbers) {
23185 ch_width * 3.0
23186 } else if show_line_numbers {
23187 ch_width
23188 } else {
23189 px(0.)
23190 };
23191
23192 Some(GutterDimensions {
23193 left_padding,
23194 right_padding,
23195 width: line_gutter_width + left_padding + right_padding,
23196 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23197 git_blame_entries_width,
23198 })
23199 }
23200
23201 pub fn render_crease_toggle(
23202 &self,
23203 buffer_row: MultiBufferRow,
23204 row_contains_cursor: bool,
23205 editor: Entity<Editor>,
23206 window: &mut Window,
23207 cx: &mut App,
23208 ) -> Option<AnyElement> {
23209 let folded = self.is_line_folded(buffer_row);
23210 let mut is_foldable = false;
23211
23212 if let Some(crease) = self
23213 .crease_snapshot
23214 .query_row(buffer_row, &self.buffer_snapshot)
23215 {
23216 is_foldable = true;
23217 match crease {
23218 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23219 if let Some(render_toggle) = render_toggle {
23220 let toggle_callback =
23221 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23222 if folded {
23223 editor.update(cx, |editor, cx| {
23224 editor.fold_at(buffer_row, window, cx)
23225 });
23226 } else {
23227 editor.update(cx, |editor, cx| {
23228 editor.unfold_at(buffer_row, window, cx)
23229 });
23230 }
23231 });
23232 return Some((render_toggle)(
23233 buffer_row,
23234 folded,
23235 toggle_callback,
23236 window,
23237 cx,
23238 ));
23239 }
23240 }
23241 }
23242 }
23243
23244 is_foldable |= self.starts_indent(buffer_row);
23245
23246 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23247 Some(
23248 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23249 .toggle_state(folded)
23250 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23251 if folded {
23252 this.unfold_at(buffer_row, window, cx);
23253 } else {
23254 this.fold_at(buffer_row, window, cx);
23255 }
23256 }))
23257 .into_any_element(),
23258 )
23259 } else {
23260 None
23261 }
23262 }
23263
23264 pub fn render_crease_trailer(
23265 &self,
23266 buffer_row: MultiBufferRow,
23267 window: &mut Window,
23268 cx: &mut App,
23269 ) -> Option<AnyElement> {
23270 let folded = self.is_line_folded(buffer_row);
23271 if let Crease::Inline { render_trailer, .. } = self
23272 .crease_snapshot
23273 .query_row(buffer_row, &self.buffer_snapshot)?
23274 {
23275 let render_trailer = render_trailer.as_ref()?;
23276 Some(render_trailer(buffer_row, folded, window, cx))
23277 } else {
23278 None
23279 }
23280 }
23281}
23282
23283impl Deref for EditorSnapshot {
23284 type Target = DisplaySnapshot;
23285
23286 fn deref(&self) -> &Self::Target {
23287 &self.display_snapshot
23288 }
23289}
23290
23291#[derive(Clone, Debug, PartialEq, Eq)]
23292pub enum EditorEvent {
23293 InputIgnored {
23294 text: Arc<str>,
23295 },
23296 InputHandled {
23297 utf16_range_to_replace: Option<Range<isize>>,
23298 text: Arc<str>,
23299 },
23300 ExcerptsAdded {
23301 buffer: Entity<Buffer>,
23302 predecessor: ExcerptId,
23303 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23304 },
23305 ExcerptsRemoved {
23306 ids: Vec<ExcerptId>,
23307 removed_buffer_ids: Vec<BufferId>,
23308 },
23309 BufferFoldToggled {
23310 ids: Vec<ExcerptId>,
23311 folded: bool,
23312 },
23313 ExcerptsEdited {
23314 ids: Vec<ExcerptId>,
23315 },
23316 ExcerptsExpanded {
23317 ids: Vec<ExcerptId>,
23318 },
23319 BufferEdited,
23320 Edited {
23321 transaction_id: clock::Lamport,
23322 },
23323 Reparsed(BufferId),
23324 Focused,
23325 FocusedIn,
23326 Blurred,
23327 DirtyChanged,
23328 Saved,
23329 TitleChanged,
23330 SelectionsChanged {
23331 local: bool,
23332 },
23333 ScrollPositionChanged {
23334 local: bool,
23335 autoscroll: bool,
23336 },
23337 TransactionUndone {
23338 transaction_id: clock::Lamport,
23339 },
23340 TransactionBegun {
23341 transaction_id: clock::Lamport,
23342 },
23343 CursorShapeChanged,
23344 BreadcrumbsChanged,
23345 PushedToNavHistory {
23346 anchor: Anchor,
23347 is_deactivate: bool,
23348 },
23349}
23350
23351impl EventEmitter<EditorEvent> for Editor {}
23352
23353impl Focusable for Editor {
23354 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23355 self.focus_handle.clone()
23356 }
23357}
23358
23359impl Render for Editor {
23360 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23361 let settings = ThemeSettings::get_global(cx);
23362
23363 let mut text_style = match self.mode {
23364 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23365 color: cx.theme().colors().editor_foreground,
23366 font_family: settings.ui_font.family.clone(),
23367 font_features: settings.ui_font.features.clone(),
23368 font_fallbacks: settings.ui_font.fallbacks.clone(),
23369 font_size: rems(0.875).into(),
23370 font_weight: settings.ui_font.weight,
23371 line_height: relative(settings.buffer_line_height.value()),
23372 ..Default::default()
23373 },
23374 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23375 color: cx.theme().colors().editor_foreground,
23376 font_family: settings.buffer_font.family.clone(),
23377 font_features: settings.buffer_font.features.clone(),
23378 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23379 font_size: settings.buffer_font_size(cx).into(),
23380 font_weight: settings.buffer_font.weight,
23381 line_height: relative(settings.buffer_line_height.value()),
23382 ..Default::default()
23383 },
23384 };
23385 if let Some(text_style_refinement) = &self.text_style_refinement {
23386 text_style.refine(text_style_refinement)
23387 }
23388
23389 let background = match self.mode {
23390 EditorMode::SingleLine => cx.theme().system().transparent,
23391 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23392 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23393 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23394 };
23395
23396 EditorElement::new(
23397 &cx.entity(),
23398 EditorStyle {
23399 background,
23400 border: cx.theme().colors().border,
23401 local_player: cx.theme().players().local(),
23402 text: text_style,
23403 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23404 syntax: cx.theme().syntax().clone(),
23405 status: cx.theme().status().clone(),
23406 inlay_hints_style: make_inlay_hints_style(cx),
23407 edit_prediction_styles: make_suggestion_styles(cx),
23408 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23409 show_underlines: self.diagnostics_enabled(),
23410 },
23411 )
23412 }
23413}
23414
23415impl EntityInputHandler for Editor {
23416 fn text_for_range(
23417 &mut self,
23418 range_utf16: Range<usize>,
23419 adjusted_range: &mut Option<Range<usize>>,
23420 _: &mut Window,
23421 cx: &mut Context<Self>,
23422 ) -> Option<String> {
23423 let snapshot = self.buffer.read(cx).read(cx);
23424 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23425 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23426 if (start.0..end.0) != range_utf16 {
23427 adjusted_range.replace(start.0..end.0);
23428 }
23429 Some(snapshot.text_for_range(start..end).collect())
23430 }
23431
23432 fn selected_text_range(
23433 &mut self,
23434 ignore_disabled_input: bool,
23435 _: &mut Window,
23436 cx: &mut Context<Self>,
23437 ) -> Option<UTF16Selection> {
23438 // Prevent the IME menu from appearing when holding down an alphabetic key
23439 // while input is disabled.
23440 if !ignore_disabled_input && !self.input_enabled {
23441 return None;
23442 }
23443
23444 let selection = self.selections.newest::<OffsetUtf16>(cx);
23445 let range = selection.range();
23446
23447 Some(UTF16Selection {
23448 range: range.start.0..range.end.0,
23449 reversed: selection.reversed,
23450 })
23451 }
23452
23453 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23454 let snapshot = self.buffer.read(cx).read(cx);
23455 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23456 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23457 }
23458
23459 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23460 self.clear_highlights::<InputComposition>(cx);
23461 self.ime_transaction.take();
23462 }
23463
23464 fn replace_text_in_range(
23465 &mut self,
23466 range_utf16: Option<Range<usize>>,
23467 text: &str,
23468 window: &mut Window,
23469 cx: &mut Context<Self>,
23470 ) {
23471 if !self.input_enabled {
23472 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23473 return;
23474 }
23475
23476 self.transact(window, cx, |this, window, cx| {
23477 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23478 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23479 Some(this.selection_replacement_ranges(range_utf16, cx))
23480 } else {
23481 this.marked_text_ranges(cx)
23482 };
23483
23484 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23485 let newest_selection_id = this.selections.newest_anchor().id;
23486 this.selections
23487 .all::<OffsetUtf16>(cx)
23488 .iter()
23489 .zip(ranges_to_replace.iter())
23490 .find_map(|(selection, range)| {
23491 if selection.id == newest_selection_id {
23492 Some(
23493 (range.start.0 as isize - selection.head().0 as isize)
23494 ..(range.end.0 as isize - selection.head().0 as isize),
23495 )
23496 } else {
23497 None
23498 }
23499 })
23500 });
23501
23502 cx.emit(EditorEvent::InputHandled {
23503 utf16_range_to_replace: range_to_replace,
23504 text: text.into(),
23505 });
23506
23507 if let Some(new_selected_ranges) = new_selected_ranges {
23508 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23509 selections.select_ranges(new_selected_ranges)
23510 });
23511 this.backspace(&Default::default(), window, cx);
23512 }
23513
23514 this.handle_input(text, window, cx);
23515 });
23516
23517 if let Some(transaction) = self.ime_transaction {
23518 self.buffer.update(cx, |buffer, cx| {
23519 buffer.group_until_transaction(transaction, cx);
23520 });
23521 }
23522
23523 self.unmark_text(window, cx);
23524 }
23525
23526 fn replace_and_mark_text_in_range(
23527 &mut self,
23528 range_utf16: Option<Range<usize>>,
23529 text: &str,
23530 new_selected_range_utf16: Option<Range<usize>>,
23531 window: &mut Window,
23532 cx: &mut Context<Self>,
23533 ) {
23534 if !self.input_enabled {
23535 return;
23536 }
23537
23538 let transaction = self.transact(window, cx, |this, window, cx| {
23539 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23540 let snapshot = this.buffer.read(cx).read(cx);
23541 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23542 for marked_range in &mut marked_ranges {
23543 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23544 marked_range.start.0 += relative_range_utf16.start;
23545 marked_range.start =
23546 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23547 marked_range.end =
23548 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23549 }
23550 }
23551 Some(marked_ranges)
23552 } else if let Some(range_utf16) = range_utf16 {
23553 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23554 Some(this.selection_replacement_ranges(range_utf16, cx))
23555 } else {
23556 None
23557 };
23558
23559 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23560 let newest_selection_id = this.selections.newest_anchor().id;
23561 this.selections
23562 .all::<OffsetUtf16>(cx)
23563 .iter()
23564 .zip(ranges_to_replace.iter())
23565 .find_map(|(selection, range)| {
23566 if selection.id == newest_selection_id {
23567 Some(
23568 (range.start.0 as isize - selection.head().0 as isize)
23569 ..(range.end.0 as isize - selection.head().0 as isize),
23570 )
23571 } else {
23572 None
23573 }
23574 })
23575 });
23576
23577 cx.emit(EditorEvent::InputHandled {
23578 utf16_range_to_replace: range_to_replace,
23579 text: text.into(),
23580 });
23581
23582 if let Some(ranges) = ranges_to_replace {
23583 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23584 s.select_ranges(ranges)
23585 });
23586 }
23587
23588 let marked_ranges = {
23589 let snapshot = this.buffer.read(cx).read(cx);
23590 this.selections
23591 .disjoint_anchors_arc()
23592 .iter()
23593 .map(|selection| {
23594 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23595 })
23596 .collect::<Vec<_>>()
23597 };
23598
23599 if text.is_empty() {
23600 this.unmark_text(window, cx);
23601 } else {
23602 this.highlight_text::<InputComposition>(
23603 marked_ranges.clone(),
23604 HighlightStyle {
23605 underline: Some(UnderlineStyle {
23606 thickness: px(1.),
23607 color: None,
23608 wavy: false,
23609 }),
23610 ..Default::default()
23611 },
23612 cx,
23613 );
23614 }
23615
23616 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23617 let use_autoclose = this.use_autoclose;
23618 let use_auto_surround = this.use_auto_surround;
23619 this.set_use_autoclose(false);
23620 this.set_use_auto_surround(false);
23621 this.handle_input(text, window, cx);
23622 this.set_use_autoclose(use_autoclose);
23623 this.set_use_auto_surround(use_auto_surround);
23624
23625 if let Some(new_selected_range) = new_selected_range_utf16 {
23626 let snapshot = this.buffer.read(cx).read(cx);
23627 let new_selected_ranges = marked_ranges
23628 .into_iter()
23629 .map(|marked_range| {
23630 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23631 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23632 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23633 snapshot.clip_offset_utf16(new_start, Bias::Left)
23634 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23635 })
23636 .collect::<Vec<_>>();
23637
23638 drop(snapshot);
23639 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23640 selections.select_ranges(new_selected_ranges)
23641 });
23642 }
23643 });
23644
23645 self.ime_transaction = self.ime_transaction.or(transaction);
23646 if let Some(transaction) = self.ime_transaction {
23647 self.buffer.update(cx, |buffer, cx| {
23648 buffer.group_until_transaction(transaction, cx);
23649 });
23650 }
23651
23652 if self.text_highlights::<InputComposition>(cx).is_none() {
23653 self.ime_transaction.take();
23654 }
23655 }
23656
23657 fn bounds_for_range(
23658 &mut self,
23659 range_utf16: Range<usize>,
23660 element_bounds: gpui::Bounds<Pixels>,
23661 window: &mut Window,
23662 cx: &mut Context<Self>,
23663 ) -> Option<gpui::Bounds<Pixels>> {
23664 let text_layout_details = self.text_layout_details(window);
23665 let CharacterDimensions {
23666 em_width,
23667 em_advance,
23668 line_height,
23669 } = self.character_dimensions(window);
23670
23671 let snapshot = self.snapshot(window, cx);
23672 let scroll_position = snapshot.scroll_position();
23673 let scroll_left = scroll_position.x * em_advance;
23674
23675 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23676 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23677 + self.gutter_dimensions.full_width();
23678 let y = line_height * (start.row().as_f32() - scroll_position.y);
23679
23680 Some(Bounds {
23681 origin: element_bounds.origin + point(x, y),
23682 size: size(em_width, line_height),
23683 })
23684 }
23685
23686 fn character_index_for_point(
23687 &mut self,
23688 point: gpui::Point<Pixels>,
23689 _window: &mut Window,
23690 _cx: &mut Context<Self>,
23691 ) -> Option<usize> {
23692 let position_map = self.last_position_map.as_ref()?;
23693 if !position_map.text_hitbox.contains(&point) {
23694 return None;
23695 }
23696 let display_point = position_map.point_for_position(point).previous_valid;
23697 let anchor = position_map
23698 .snapshot
23699 .display_point_to_anchor(display_point, Bias::Left);
23700 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23701 Some(utf16_offset.0)
23702 }
23703}
23704
23705trait SelectionExt {
23706 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23707 fn spanned_rows(
23708 &self,
23709 include_end_if_at_line_start: bool,
23710 map: &DisplaySnapshot,
23711 ) -> Range<MultiBufferRow>;
23712}
23713
23714impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23715 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23716 let start = self
23717 .start
23718 .to_point(&map.buffer_snapshot)
23719 .to_display_point(map);
23720 let end = self
23721 .end
23722 .to_point(&map.buffer_snapshot)
23723 .to_display_point(map);
23724 if self.reversed {
23725 end..start
23726 } else {
23727 start..end
23728 }
23729 }
23730
23731 fn spanned_rows(
23732 &self,
23733 include_end_if_at_line_start: bool,
23734 map: &DisplaySnapshot,
23735 ) -> Range<MultiBufferRow> {
23736 let start = self.start.to_point(&map.buffer_snapshot);
23737 let mut end = self.end.to_point(&map.buffer_snapshot);
23738 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23739 end.row -= 1;
23740 }
23741
23742 let buffer_start = map.prev_line_boundary(start).0;
23743 let buffer_end = map.next_line_boundary(end).0;
23744 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23745 }
23746}
23747
23748impl<T: InvalidationRegion> InvalidationStack<T> {
23749 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23750 where
23751 S: Clone + ToOffset,
23752 {
23753 while let Some(region) = self.last() {
23754 let all_selections_inside_invalidation_ranges =
23755 if selections.len() == region.ranges().len() {
23756 selections
23757 .iter()
23758 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23759 .all(|(selection, invalidation_range)| {
23760 let head = selection.head().to_offset(buffer);
23761 invalidation_range.start <= head && invalidation_range.end >= head
23762 })
23763 } else {
23764 false
23765 };
23766
23767 if all_selections_inside_invalidation_ranges {
23768 break;
23769 } else {
23770 self.pop();
23771 }
23772 }
23773 }
23774}
23775
23776impl<T> Default for InvalidationStack<T> {
23777 fn default() -> Self {
23778 Self(Default::default())
23779 }
23780}
23781
23782impl<T> Deref for InvalidationStack<T> {
23783 type Target = Vec<T>;
23784
23785 fn deref(&self) -> &Self::Target {
23786 &self.0
23787 }
23788}
23789
23790impl<T> DerefMut for InvalidationStack<T> {
23791 fn deref_mut(&mut self) -> &mut Self::Target {
23792 &mut self.0
23793 }
23794}
23795
23796impl InvalidationRegion for SnippetState {
23797 fn ranges(&self) -> &[Range<Anchor>] {
23798 &self.ranges[self.active_index]
23799 }
23800}
23801
23802fn edit_prediction_edit_text(
23803 current_snapshot: &BufferSnapshot,
23804 edits: &[(Range<Anchor>, String)],
23805 edit_preview: &EditPreview,
23806 include_deletions: bool,
23807 cx: &App,
23808) -> HighlightedText {
23809 let edits = edits
23810 .iter()
23811 .map(|(anchor, text)| {
23812 (
23813 anchor.start.text_anchor..anchor.end.text_anchor,
23814 text.clone(),
23815 )
23816 })
23817 .collect::<Vec<_>>();
23818
23819 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23820}
23821
23822fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23823 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23824 // Just show the raw edit text with basic styling
23825 let mut text = String::new();
23826 let mut highlights = Vec::new();
23827
23828 let insertion_highlight_style = HighlightStyle {
23829 color: Some(cx.theme().colors().text),
23830 ..Default::default()
23831 };
23832
23833 for (_, edit_text) in edits {
23834 let start_offset = text.len();
23835 text.push_str(edit_text);
23836 let end_offset = text.len();
23837
23838 if start_offset < end_offset {
23839 highlights.push((start_offset..end_offset, insertion_highlight_style));
23840 }
23841 }
23842
23843 HighlightedText {
23844 text: text.into(),
23845 highlights,
23846 }
23847}
23848
23849pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23850 match severity {
23851 lsp::DiagnosticSeverity::ERROR => colors.error,
23852 lsp::DiagnosticSeverity::WARNING => colors.warning,
23853 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23854 lsp::DiagnosticSeverity::HINT => colors.info,
23855 _ => colors.ignored,
23856 }
23857}
23858
23859pub fn styled_runs_for_code_label<'a>(
23860 label: &'a CodeLabel,
23861 syntax_theme: &'a theme::SyntaxTheme,
23862) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23863 let fade_out = HighlightStyle {
23864 fade_out: Some(0.35),
23865 ..Default::default()
23866 };
23867
23868 let mut prev_end = label.filter_range.end;
23869 label
23870 .runs
23871 .iter()
23872 .enumerate()
23873 .flat_map(move |(ix, (range, highlight_id))| {
23874 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23875 style
23876 } else {
23877 return Default::default();
23878 };
23879 let muted_style = style.highlight(fade_out);
23880
23881 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23882 if range.start >= label.filter_range.end {
23883 if range.start > prev_end {
23884 runs.push((prev_end..range.start, fade_out));
23885 }
23886 runs.push((range.clone(), muted_style));
23887 } else if range.end <= label.filter_range.end {
23888 runs.push((range.clone(), style));
23889 } else {
23890 runs.push((range.start..label.filter_range.end, style));
23891 runs.push((label.filter_range.end..range.end, muted_style));
23892 }
23893 prev_end = cmp::max(prev_end, range.end);
23894
23895 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23896 runs.push((prev_end..label.text.len(), fade_out));
23897 }
23898
23899 runs
23900 })
23901}
23902
23903pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23904 let mut prev_index = 0;
23905 let mut prev_codepoint: Option<char> = None;
23906 text.char_indices()
23907 .chain([(text.len(), '\0')])
23908 .filter_map(move |(index, codepoint)| {
23909 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23910 let is_boundary = index == text.len()
23911 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23912 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23913 if is_boundary {
23914 let chunk = &text[prev_index..index];
23915 prev_index = index;
23916 Some(chunk)
23917 } else {
23918 None
23919 }
23920 })
23921}
23922
23923pub trait RangeToAnchorExt: Sized {
23924 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23925
23926 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23927 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23928 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23929 }
23930}
23931
23932impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23933 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23934 let start_offset = self.start.to_offset(snapshot);
23935 let end_offset = self.end.to_offset(snapshot);
23936 if start_offset == end_offset {
23937 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23938 } else {
23939 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23940 }
23941 }
23942}
23943
23944pub trait RowExt {
23945 fn as_f32(&self) -> f32;
23946
23947 fn next_row(&self) -> Self;
23948
23949 fn previous_row(&self) -> Self;
23950
23951 fn minus(&self, other: Self) -> u32;
23952}
23953
23954impl RowExt for DisplayRow {
23955 fn as_f32(&self) -> f32 {
23956 self.0 as f32
23957 }
23958
23959 fn next_row(&self) -> Self {
23960 Self(self.0 + 1)
23961 }
23962
23963 fn previous_row(&self) -> Self {
23964 Self(self.0.saturating_sub(1))
23965 }
23966
23967 fn minus(&self, other: Self) -> u32 {
23968 self.0 - other.0
23969 }
23970}
23971
23972impl RowExt for MultiBufferRow {
23973 fn as_f32(&self) -> f32 {
23974 self.0 as f32
23975 }
23976
23977 fn next_row(&self) -> Self {
23978 Self(self.0 + 1)
23979 }
23980
23981 fn previous_row(&self) -> Self {
23982 Self(self.0.saturating_sub(1))
23983 }
23984
23985 fn minus(&self, other: Self) -> u32 {
23986 self.0 - other.0
23987 }
23988}
23989
23990trait RowRangeExt {
23991 type Row;
23992
23993 fn len(&self) -> usize;
23994
23995 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23996}
23997
23998impl RowRangeExt for Range<MultiBufferRow> {
23999 type Row = MultiBufferRow;
24000
24001 fn len(&self) -> usize {
24002 (self.end.0 - self.start.0) as usize
24003 }
24004
24005 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24006 (self.start.0..self.end.0).map(MultiBufferRow)
24007 }
24008}
24009
24010impl RowRangeExt for Range<DisplayRow> {
24011 type Row = DisplayRow;
24012
24013 fn len(&self) -> usize {
24014 (self.end.0 - self.start.0) as usize
24015 }
24016
24017 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24018 (self.start.0..self.end.0).map(DisplayRow)
24019 }
24020}
24021
24022/// If select range has more than one line, we
24023/// just point the cursor to range.start.
24024fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24025 if range.start.row == range.end.row {
24026 range
24027 } else {
24028 range.start..range.start
24029 }
24030}
24031pub struct KillRing(ClipboardItem);
24032impl Global for KillRing {}
24033
24034const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24035
24036enum BreakpointPromptEditAction {
24037 Log,
24038 Condition,
24039 HitCondition,
24040}
24041
24042struct BreakpointPromptEditor {
24043 pub(crate) prompt: Entity<Editor>,
24044 editor: WeakEntity<Editor>,
24045 breakpoint_anchor: Anchor,
24046 breakpoint: Breakpoint,
24047 edit_action: BreakpointPromptEditAction,
24048 block_ids: HashSet<CustomBlockId>,
24049 editor_margins: Arc<Mutex<EditorMargins>>,
24050 _subscriptions: Vec<Subscription>,
24051}
24052
24053impl BreakpointPromptEditor {
24054 const MAX_LINES: u8 = 4;
24055
24056 fn new(
24057 editor: WeakEntity<Editor>,
24058 breakpoint_anchor: Anchor,
24059 breakpoint: Breakpoint,
24060 edit_action: BreakpointPromptEditAction,
24061 window: &mut Window,
24062 cx: &mut Context<Self>,
24063 ) -> Self {
24064 let base_text = match edit_action {
24065 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24066 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24067 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24068 }
24069 .map(|msg| msg.to_string())
24070 .unwrap_or_default();
24071
24072 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24073 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24074
24075 let prompt = cx.new(|cx| {
24076 let mut prompt = Editor::new(
24077 EditorMode::AutoHeight {
24078 min_lines: 1,
24079 max_lines: Some(Self::MAX_LINES as usize),
24080 },
24081 buffer,
24082 None,
24083 window,
24084 cx,
24085 );
24086 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24087 prompt.set_show_cursor_when_unfocused(false, cx);
24088 prompt.set_placeholder_text(
24089 match edit_action {
24090 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24091 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24092 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24093 },
24094 window,
24095 cx,
24096 );
24097
24098 prompt
24099 });
24100
24101 Self {
24102 prompt,
24103 editor,
24104 breakpoint_anchor,
24105 breakpoint,
24106 edit_action,
24107 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24108 block_ids: Default::default(),
24109 _subscriptions: vec![],
24110 }
24111 }
24112
24113 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24114 self.block_ids.extend(block_ids)
24115 }
24116
24117 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24118 if let Some(editor) = self.editor.upgrade() {
24119 let message = self
24120 .prompt
24121 .read(cx)
24122 .buffer
24123 .read(cx)
24124 .as_singleton()
24125 .expect("A multi buffer in breakpoint prompt isn't possible")
24126 .read(cx)
24127 .as_rope()
24128 .to_string();
24129
24130 editor.update(cx, |editor, cx| {
24131 editor.edit_breakpoint_at_anchor(
24132 self.breakpoint_anchor,
24133 self.breakpoint.clone(),
24134 match self.edit_action {
24135 BreakpointPromptEditAction::Log => {
24136 BreakpointEditAction::EditLogMessage(message.into())
24137 }
24138 BreakpointPromptEditAction::Condition => {
24139 BreakpointEditAction::EditCondition(message.into())
24140 }
24141 BreakpointPromptEditAction::HitCondition => {
24142 BreakpointEditAction::EditHitCondition(message.into())
24143 }
24144 },
24145 cx,
24146 );
24147
24148 editor.remove_blocks(self.block_ids.clone(), None, cx);
24149 cx.focus_self(window);
24150 });
24151 }
24152 }
24153
24154 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24155 self.editor
24156 .update(cx, |editor, cx| {
24157 editor.remove_blocks(self.block_ids.clone(), None, cx);
24158 window.focus(&editor.focus_handle);
24159 })
24160 .log_err();
24161 }
24162
24163 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24164 let settings = ThemeSettings::get_global(cx);
24165 let text_style = TextStyle {
24166 color: if self.prompt.read(cx).read_only(cx) {
24167 cx.theme().colors().text_disabled
24168 } else {
24169 cx.theme().colors().text
24170 },
24171 font_family: settings.buffer_font.family.clone(),
24172 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24173 font_size: settings.buffer_font_size(cx).into(),
24174 font_weight: settings.buffer_font.weight,
24175 line_height: relative(settings.buffer_line_height.value()),
24176 ..Default::default()
24177 };
24178 EditorElement::new(
24179 &self.prompt,
24180 EditorStyle {
24181 background: cx.theme().colors().editor_background,
24182 local_player: cx.theme().players().local(),
24183 text: text_style,
24184 ..Default::default()
24185 },
24186 )
24187 }
24188}
24189
24190impl Render for BreakpointPromptEditor {
24191 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24192 let editor_margins = *self.editor_margins.lock();
24193 let gutter_dimensions = editor_margins.gutter;
24194 h_flex()
24195 .key_context("Editor")
24196 .bg(cx.theme().colors().editor_background)
24197 .border_y_1()
24198 .border_color(cx.theme().status().info_border)
24199 .size_full()
24200 .py(window.line_height() / 2.5)
24201 .on_action(cx.listener(Self::confirm))
24202 .on_action(cx.listener(Self::cancel))
24203 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24204 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24205 }
24206}
24207
24208impl Focusable for BreakpointPromptEditor {
24209 fn focus_handle(&self, cx: &App) -> FocusHandle {
24210 self.prompt.focus_handle(cx)
24211 }
24212}
24213
24214fn all_edits_insertions_or_deletions(
24215 edits: &Vec<(Range<Anchor>, String)>,
24216 snapshot: &MultiBufferSnapshot,
24217) -> bool {
24218 let mut all_insertions = true;
24219 let mut all_deletions = true;
24220
24221 for (range, new_text) in edits.iter() {
24222 let range_is_empty = range.to_offset(snapshot).is_empty();
24223 let text_is_empty = new_text.is_empty();
24224
24225 if range_is_empty != text_is_empty {
24226 if range_is_empty {
24227 all_deletions = false;
24228 } else {
24229 all_insertions = false;
24230 }
24231 } else {
24232 return false;
24233 }
24234
24235 if !all_insertions && !all_deletions {
24236 return false;
24237 }
24238 }
24239 all_insertions || all_deletions
24240}
24241
24242struct MissingEditPredictionKeybindingTooltip;
24243
24244impl Render for MissingEditPredictionKeybindingTooltip {
24245 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24246 ui::tooltip_container(window, cx, |container, _, cx| {
24247 container
24248 .flex_shrink_0()
24249 .max_w_80()
24250 .min_h(rems_from_px(124.))
24251 .justify_between()
24252 .child(
24253 v_flex()
24254 .flex_1()
24255 .text_ui_sm(cx)
24256 .child(Label::new("Conflict with Accept Keybinding"))
24257 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24258 )
24259 .child(
24260 h_flex()
24261 .pb_1()
24262 .gap_1()
24263 .items_end()
24264 .w_full()
24265 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24266 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
24267 }))
24268 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24269 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24270 })),
24271 )
24272 })
24273 }
24274}
24275
24276#[derive(Debug, Clone, Copy, PartialEq)]
24277pub struct LineHighlight {
24278 pub background: Background,
24279 pub border: Option<gpui::Hsla>,
24280 pub include_gutter: bool,
24281 pub type_id: Option<TypeId>,
24282}
24283
24284struct LineManipulationResult {
24285 pub new_text: String,
24286 pub line_count_before: usize,
24287 pub line_count_after: usize,
24288}
24289
24290fn render_diff_hunk_controls(
24291 row: u32,
24292 status: &DiffHunkStatus,
24293 hunk_range: Range<Anchor>,
24294 is_created_file: bool,
24295 line_height: Pixels,
24296 editor: &Entity<Editor>,
24297 _window: &mut Window,
24298 cx: &mut App,
24299) -> AnyElement {
24300 h_flex()
24301 .h(line_height)
24302 .mr_1()
24303 .gap_1()
24304 .px_0p5()
24305 .pb_1()
24306 .border_x_1()
24307 .border_b_1()
24308 .border_color(cx.theme().colors().border_variant)
24309 .rounded_b_lg()
24310 .bg(cx.theme().colors().editor_background)
24311 .gap_1()
24312 .block_mouse_except_scroll()
24313 .shadow_md()
24314 .child(if status.has_secondary_hunk() {
24315 Button::new(("stage", row as u64), "Stage")
24316 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24317 .tooltip({
24318 let focus_handle = editor.focus_handle(cx);
24319 move |window, cx| {
24320 Tooltip::for_action_in(
24321 "Stage Hunk",
24322 &::git::ToggleStaged,
24323 &focus_handle,
24324 window,
24325 cx,
24326 )
24327 }
24328 })
24329 .on_click({
24330 let editor = editor.clone();
24331 move |_event, _window, cx| {
24332 editor.update(cx, |editor, cx| {
24333 editor.stage_or_unstage_diff_hunks(
24334 true,
24335 vec![hunk_range.start..hunk_range.start],
24336 cx,
24337 );
24338 });
24339 }
24340 })
24341 } else {
24342 Button::new(("unstage", row as u64), "Unstage")
24343 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24344 .tooltip({
24345 let focus_handle = editor.focus_handle(cx);
24346 move |window, cx| {
24347 Tooltip::for_action_in(
24348 "Unstage Hunk",
24349 &::git::ToggleStaged,
24350 &focus_handle,
24351 window,
24352 cx,
24353 )
24354 }
24355 })
24356 .on_click({
24357 let editor = editor.clone();
24358 move |_event, _window, cx| {
24359 editor.update(cx, |editor, cx| {
24360 editor.stage_or_unstage_diff_hunks(
24361 false,
24362 vec![hunk_range.start..hunk_range.start],
24363 cx,
24364 );
24365 });
24366 }
24367 })
24368 })
24369 .child(
24370 Button::new(("restore", row as u64), "Restore")
24371 .tooltip({
24372 let focus_handle = editor.focus_handle(cx);
24373 move |window, cx| {
24374 Tooltip::for_action_in(
24375 "Restore Hunk",
24376 &::git::Restore,
24377 &focus_handle,
24378 window,
24379 cx,
24380 )
24381 }
24382 })
24383 .on_click({
24384 let editor = editor.clone();
24385 move |_event, window, cx| {
24386 editor.update(cx, |editor, cx| {
24387 let snapshot = editor.snapshot(window, cx);
24388 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
24389 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24390 });
24391 }
24392 })
24393 .disabled(is_created_file),
24394 )
24395 .when(
24396 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24397 |el| {
24398 el.child(
24399 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24400 .shape(IconButtonShape::Square)
24401 .icon_size(IconSize::Small)
24402 // .disabled(!has_multiple_hunks)
24403 .tooltip({
24404 let focus_handle = editor.focus_handle(cx);
24405 move |window, cx| {
24406 Tooltip::for_action_in(
24407 "Next Hunk",
24408 &GoToHunk,
24409 &focus_handle,
24410 window,
24411 cx,
24412 )
24413 }
24414 })
24415 .on_click({
24416 let editor = editor.clone();
24417 move |_event, window, cx| {
24418 editor.update(cx, |editor, cx| {
24419 let snapshot = editor.snapshot(window, cx);
24420 let position =
24421 hunk_range.end.to_point(&snapshot.buffer_snapshot);
24422 editor.go_to_hunk_before_or_after_position(
24423 &snapshot,
24424 position,
24425 Direction::Next,
24426 window,
24427 cx,
24428 );
24429 editor.expand_selected_diff_hunks(cx);
24430 });
24431 }
24432 }),
24433 )
24434 .child(
24435 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24436 .shape(IconButtonShape::Square)
24437 .icon_size(IconSize::Small)
24438 // .disabled(!has_multiple_hunks)
24439 .tooltip({
24440 let focus_handle = editor.focus_handle(cx);
24441 move |window, cx| {
24442 Tooltip::for_action_in(
24443 "Previous Hunk",
24444 &GoToPreviousHunk,
24445 &focus_handle,
24446 window,
24447 cx,
24448 )
24449 }
24450 })
24451 .on_click({
24452 let editor = editor.clone();
24453 move |_event, window, cx| {
24454 editor.update(cx, |editor, cx| {
24455 let snapshot = editor.snapshot(window, cx);
24456 let point =
24457 hunk_range.start.to_point(&snapshot.buffer_snapshot);
24458 editor.go_to_hunk_before_or_after_position(
24459 &snapshot,
24460 point,
24461 Direction::Prev,
24462 window,
24463 cx,
24464 );
24465 editor.expand_selected_diff_hunks(cx);
24466 });
24467 }
24468 }),
24469 )
24470 },
24471 )
24472 .into_any_element()
24473}
24474
24475pub fn multibuffer_context_lines(cx: &App) -> u32 {
24476 EditorSettings::try_get(cx)
24477 .map(|settings| settings.excerpt_context_lines)
24478 .unwrap_or(2)
24479 .min(32)
24480}