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 editor_tests;
47#[cfg(test)]
48mod inline_completion_tests;
49mod signature_help;
50#[cfg(any(test, feature = "test-support"))]
51pub mod test;
52
53pub(crate) use actions::*;
54pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
55use aho_corasick::AhoCorasick;
56use anyhow::{Context as _, Result, anyhow};
57use blink_manager::BlinkManager;
58use buffer_diff::DiffHunkStatus;
59use client::{Collaborator, ParticipantIndex};
60use clock::{AGENT_REPLICA_ID, ReplicaId};
61use collections::{BTreeMap, HashMap, HashSet, VecDeque};
62use convert_case::{Case, Casing};
63use dap::TelemetrySpawnLocation;
64use display_map::*;
65pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
66pub use editor_settings::{
67 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
68 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowScrollbar,
69};
70use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
71pub use editor_settings_controls::*;
72use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
73pub use element::{
74 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
75};
76use futures::{
77 FutureExt, StreamExt as _,
78 future::{self, Shared, join},
79 stream::FuturesUnordered,
80};
81use fuzzy::{StringMatch, StringMatchCandidate};
82use lsp_colors::LspColorData;
83
84use ::git::blame::BlameEntry;
85use ::git::{Restore, blame::ParsedCommitMessage};
86use code_context_menus::{
87 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
88 CompletionsMenu, ContextMenuOrigin,
89};
90use git::blame::{GitBlame, GlobalBlameRenderer};
91use gpui::{
92 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
93 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
94 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
95 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
96 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
97 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
98 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
99 div, point, prelude::*, pulsating_between, px, relative, size,
100};
101use highlight_matching_bracket::refresh_matching_bracket_highlights;
102use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
103pub use hover_popover::hover_markdown_style;
104use hover_popover::{HoverState, hide_hover};
105use indent_guides::ActiveIndentGuidesState;
106use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
107pub use inline_completion::Direction;
108use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
109pub use items::MAX_TAB_TITLE_LEN;
110use itertools::Itertools;
111use language::{
112 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
113 CursorShape, DiagnosticEntry, DiffOptions, DocumentationConfig, EditPredictionsMode,
114 EditPreview, HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point,
115 Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
116 language_settings::{
117 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
118 all_language_settings, language_settings,
119 },
120 point_from_lsp, text_diff_with_options,
121};
122use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
123use linked_editing_ranges::refresh_linked_ranges;
124use markdown::Markdown;
125use mouse_context_menu::MouseContextMenu;
126use persistence::DB;
127use project::{
128 BreakpointWithPosition, CompletionResponse, ProjectPath,
129 debugger::{
130 breakpoint_store::{
131 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
132 BreakpointStoreEvent,
133 },
134 session::{Session, SessionEvent},
135 },
136 git_store::{GitStoreEvent, RepositoryEvent},
137 project_settings::DiagnosticSeverity,
138};
139
140pub use git::blame::BlameRenderer;
141pub use proposed_changes_editor::{
142 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
143};
144use std::{cell::OnceCell, iter::Peekable, ops::Not};
145use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
146
147pub use lsp::CompletionContext;
148use lsp::{
149 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
150 LanguageServerId, LanguageServerName,
151};
152
153use language::BufferSnapshot;
154pub use lsp_ext::lsp_tasks;
155use movement::TextLayoutDetails;
156pub use multi_buffer::{
157 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
158 RowInfo, ToOffset, ToPoint,
159};
160use multi_buffer::{
161 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
162 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
163};
164use parking_lot::Mutex;
165use project::{
166 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
167 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
168 TaskSourceKind,
169 debugger::breakpoint_store::Breakpoint,
170 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
171 project_settings::{GitGutterSetting, ProjectSettings},
172};
173use rand::prelude::*;
174use rpc::{ErrorExt, proto::*};
175use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
176use selections_collection::{
177 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
178};
179use serde::{Deserialize, Serialize};
180use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
181use smallvec::{SmallVec, smallvec};
182use snippet::Snippet;
183use std::sync::Arc;
184use std::{
185 any::TypeId,
186 borrow::Cow,
187 cell::RefCell,
188 cmp::{self, Ordering, Reverse},
189 mem,
190 num::NonZeroU32,
191 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
192 path::{Path, PathBuf},
193 rc::Rc,
194 time::{Duration, Instant},
195};
196pub use sum_tree::Bias;
197use sum_tree::TreeMap;
198use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
199use theme::{
200 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
201 observe_buffer_font_size_adjustment,
202};
203use ui::{
204 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
205 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
206};
207use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
208use workspace::{
209 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
210 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
211 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
212 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
213 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
214 searchable::SearchEvent,
215};
216
217use crate::{
218 code_context_menus::CompletionsMenuSource,
219 hover_links::{find_url, find_url_from_range},
220};
221use crate::{
222 editor_settings::MultiCursorModifier,
223 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
224};
225
226pub const FILE_HEADER_HEIGHT: u32 = 2;
227pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
228pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
229const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
230const MAX_LINE_LEN: usize = 1024;
231const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
232const MAX_SELECTION_HISTORY_LEN: usize = 1024;
233pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
234#[doc(hidden)]
235pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
236const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
237
238pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
239pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
240pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
241
242pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
243pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
244pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
245
246pub type RenderDiffHunkControlsFn = Arc<
247 dyn Fn(
248 u32,
249 &DiffHunkStatus,
250 Range<Anchor>,
251 bool,
252 Pixels,
253 &Entity<Editor>,
254 &mut Window,
255 &mut App,
256 ) -> AnyElement,
257>;
258
259struct InlineValueCache {
260 enabled: bool,
261 inlays: Vec<InlayId>,
262 refresh_task: Task<Option<()>>,
263}
264
265impl InlineValueCache {
266 fn new(enabled: bool) -> Self {
267 Self {
268 enabled,
269 inlays: Vec::new(),
270 refresh_task: Task::ready(None),
271 }
272 }
273}
274
275#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
276pub enum InlayId {
277 InlineCompletion(usize),
278 DebuggerValue(usize),
279 // LSP
280 Hint(usize),
281 Color(usize),
282}
283
284impl InlayId {
285 fn id(&self) -> usize {
286 match self {
287 Self::InlineCompletion(id) => *id,
288 Self::DebuggerValue(id) => *id,
289 Self::Hint(id) => *id,
290 Self::Color(id) => *id,
291 }
292 }
293}
294
295pub enum ActiveDebugLine {}
296pub enum DebugStackFrameLine {}
297enum DocumentHighlightRead {}
298enum DocumentHighlightWrite {}
299enum InputComposition {}
300pub enum PendingInput {}
301enum SelectedTextHighlight {}
302
303pub enum ConflictsOuter {}
304pub enum ConflictsOurs {}
305pub enum ConflictsTheirs {}
306pub enum ConflictsOursMarker {}
307pub enum ConflictsTheirsMarker {}
308
309#[derive(Debug, Copy, Clone, PartialEq, Eq)]
310pub enum Navigated {
311 Yes,
312 No,
313}
314
315impl Navigated {
316 pub fn from_bool(yes: bool) -> Navigated {
317 if yes { Navigated::Yes } else { Navigated::No }
318 }
319}
320
321#[derive(Debug, Clone, PartialEq, Eq)]
322enum DisplayDiffHunk {
323 Folded {
324 display_row: DisplayRow,
325 },
326 Unfolded {
327 is_created_file: bool,
328 diff_base_byte_range: Range<usize>,
329 display_row_range: Range<DisplayRow>,
330 multi_buffer_range: Range<Anchor>,
331 status: DiffHunkStatus,
332 },
333}
334
335pub enum HideMouseCursorOrigin {
336 TypingAction,
337 MovementAction,
338}
339
340pub fn init_settings(cx: &mut App) {
341 EditorSettings::register(cx);
342}
343
344pub fn init(cx: &mut App) {
345 init_settings(cx);
346
347 cx.set_global(GlobalBlameRenderer(Arc::new(())));
348
349 workspace::register_project_item::<Editor>(cx);
350 workspace::FollowableViewRegistry::register::<Editor>(cx);
351 workspace::register_serializable_item::<Editor>(cx);
352
353 cx.observe_new(
354 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
355 workspace.register_action(Editor::new_file);
356 workspace.register_action(Editor::new_file_vertical);
357 workspace.register_action(Editor::new_file_horizontal);
358 workspace.register_action(Editor::cancel_language_server_work);
359 },
360 )
361 .detach();
362
363 cx.on_action(move |_: &workspace::NewFile, cx| {
364 let app_state = workspace::AppState::global(cx);
365 if let Some(app_state) = app_state.upgrade() {
366 workspace::open_new(
367 Default::default(),
368 app_state,
369 cx,
370 |workspace, window, cx| {
371 Editor::new_file(workspace, &Default::default(), window, cx)
372 },
373 )
374 .detach();
375 }
376 });
377 cx.on_action(move |_: &workspace::NewWindow, cx| {
378 let app_state = workspace::AppState::global(cx);
379 if let Some(app_state) = app_state.upgrade() {
380 workspace::open_new(
381 Default::default(),
382 app_state,
383 cx,
384 |workspace, window, cx| {
385 cx.activate(true);
386 Editor::new_file(workspace, &Default::default(), window, cx)
387 },
388 )
389 .detach();
390 }
391 });
392}
393
394pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
395 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
396}
397
398pub trait DiagnosticRenderer {
399 fn render_group(
400 &self,
401 diagnostic_group: Vec<DiagnosticEntry<Point>>,
402 buffer_id: BufferId,
403 snapshot: EditorSnapshot,
404 editor: WeakEntity<Editor>,
405 cx: &mut App,
406 ) -> Vec<BlockProperties<Anchor>>;
407
408 fn render_hover(
409 &self,
410 diagnostic_group: Vec<DiagnosticEntry<Point>>,
411 range: Range<Point>,
412 buffer_id: BufferId,
413 cx: &mut App,
414 ) -> Option<Entity<markdown::Markdown>>;
415
416 fn open_link(
417 &self,
418 editor: &mut Editor,
419 link: SharedString,
420 window: &mut Window,
421 cx: &mut Context<Editor>,
422 );
423}
424
425pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
426
427impl GlobalDiagnosticRenderer {
428 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
429 cx.try_global::<Self>().map(|g| g.0.clone())
430 }
431}
432
433impl gpui::Global for GlobalDiagnosticRenderer {}
434pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
435 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
436}
437
438pub struct SearchWithinRange;
439
440trait InvalidationRegion {
441 fn ranges(&self) -> &[Range<Anchor>];
442}
443
444#[derive(Clone, Debug, PartialEq)]
445pub enum SelectPhase {
446 Begin {
447 position: DisplayPoint,
448 add: bool,
449 click_count: usize,
450 },
451 BeginColumnar {
452 position: DisplayPoint,
453 reset: bool,
454 mode: ColumnarMode,
455 goal_column: u32,
456 },
457 Extend {
458 position: DisplayPoint,
459 click_count: usize,
460 },
461 Update {
462 position: DisplayPoint,
463 goal_column: u32,
464 scroll_delta: gpui::Point<f32>,
465 },
466 End,
467}
468
469#[derive(Clone, Debug, PartialEq)]
470pub enum ColumnarMode {
471 FromMouse,
472 FromSelection,
473}
474
475#[derive(Clone, Debug)]
476pub enum SelectMode {
477 Character,
478 Word(Range<Anchor>),
479 Line(Range<Anchor>),
480 All,
481}
482
483#[derive(Clone, PartialEq, Eq, Debug)]
484pub enum EditorMode {
485 SingleLine,
486 AutoHeight {
487 min_lines: usize,
488 max_lines: Option<usize>,
489 },
490 Full {
491 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
492 scale_ui_elements_with_buffer_font_size: bool,
493 /// When set to `true`, the editor will render a background for the active line.
494 show_active_line_background: bool,
495 /// When set to `true`, the editor's height will be determined by its content.
496 sized_by_content: bool,
497 },
498 Minimap {
499 parent: WeakEntity<Editor>,
500 },
501}
502
503impl EditorMode {
504 pub fn full() -> Self {
505 Self::Full {
506 scale_ui_elements_with_buffer_font_size: true,
507 show_active_line_background: true,
508 sized_by_content: false,
509 }
510 }
511
512 #[inline]
513 pub fn is_full(&self) -> bool {
514 matches!(self, Self::Full { .. })
515 }
516
517 #[inline]
518 pub fn is_single_line(&self) -> bool {
519 matches!(self, Self::SingleLine { .. })
520 }
521
522 #[inline]
523 fn is_minimap(&self) -> bool {
524 matches!(self, Self::Minimap { .. })
525 }
526}
527
528#[derive(Copy, Clone, Debug)]
529pub enum SoftWrap {
530 /// Prefer not to wrap at all.
531 ///
532 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
533 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
534 GitDiff,
535 /// Prefer a single line generally, unless an overly long line is encountered.
536 None,
537 /// Soft wrap lines that exceed the editor width.
538 EditorWidth,
539 /// Soft wrap lines at the preferred line length.
540 Column(u32),
541 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
542 Bounded(u32),
543}
544
545#[derive(Clone)]
546pub struct EditorStyle {
547 pub background: Hsla,
548 pub border: Hsla,
549 pub local_player: PlayerColor,
550 pub text: TextStyle,
551 pub scrollbar_width: Pixels,
552 pub syntax: Arc<SyntaxTheme>,
553 pub status: StatusColors,
554 pub inlay_hints_style: HighlightStyle,
555 pub inline_completion_styles: InlineCompletionStyles,
556 pub unnecessary_code_fade: f32,
557 pub show_underlines: bool,
558}
559
560impl Default for EditorStyle {
561 fn default() -> Self {
562 Self {
563 background: Hsla::default(),
564 border: Hsla::default(),
565 local_player: PlayerColor::default(),
566 text: TextStyle::default(),
567 scrollbar_width: Pixels::default(),
568 syntax: Default::default(),
569 // HACK: Status colors don't have a real default.
570 // We should look into removing the status colors from the editor
571 // style and retrieve them directly from the theme.
572 status: StatusColors::dark(),
573 inlay_hints_style: HighlightStyle::default(),
574 inline_completion_styles: InlineCompletionStyles {
575 insertion: HighlightStyle::default(),
576 whitespace: HighlightStyle::default(),
577 },
578 unnecessary_code_fade: Default::default(),
579 show_underlines: true,
580 }
581 }
582}
583
584pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
585 let show_background = language_settings::language_settings(None, None, cx)
586 .inlay_hints
587 .show_background;
588
589 HighlightStyle {
590 color: Some(cx.theme().status().hint),
591 background_color: show_background.then(|| cx.theme().status().hint_background),
592 ..HighlightStyle::default()
593 }
594}
595
596pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
597 InlineCompletionStyles {
598 insertion: HighlightStyle {
599 color: Some(cx.theme().status().predictive),
600 ..HighlightStyle::default()
601 },
602 whitespace: HighlightStyle {
603 background_color: Some(cx.theme().status().created_background),
604 ..HighlightStyle::default()
605 },
606 }
607}
608
609type CompletionId = usize;
610
611pub(crate) enum EditDisplayMode {
612 TabAccept,
613 DiffPopover,
614 Inline,
615}
616
617enum InlineCompletion {
618 Edit {
619 edits: Vec<(Range<Anchor>, String)>,
620 edit_preview: Option<EditPreview>,
621 display_mode: EditDisplayMode,
622 snapshot: BufferSnapshot,
623 },
624 Move {
625 target: Anchor,
626 snapshot: BufferSnapshot,
627 },
628}
629
630struct InlineCompletionState {
631 inlay_ids: Vec<InlayId>,
632 completion: InlineCompletion,
633 completion_id: Option<SharedString>,
634 invalidation_range: Range<Anchor>,
635}
636
637enum EditPredictionSettings {
638 Disabled,
639 Enabled {
640 show_in_menu: bool,
641 preview_requires_modifier: bool,
642 },
643}
644
645enum InlineCompletionHighlight {}
646
647#[derive(Debug, Clone)]
648struct InlineDiagnostic {
649 message: SharedString,
650 group_id: usize,
651 is_primary: bool,
652 start: Point,
653 severity: lsp::DiagnosticSeverity,
654}
655
656pub enum MenuInlineCompletionsPolicy {
657 Never,
658 ByProvider,
659}
660
661pub enum EditPredictionPreview {
662 /// Modifier is not pressed
663 Inactive { released_too_fast: bool },
664 /// Modifier pressed
665 Active {
666 since: Instant,
667 previous_scroll_position: Option<ScrollAnchor>,
668 },
669}
670
671impl EditPredictionPreview {
672 pub fn released_too_fast(&self) -> bool {
673 match self {
674 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
675 EditPredictionPreview::Active { .. } => false,
676 }
677 }
678
679 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
680 if let EditPredictionPreview::Active {
681 previous_scroll_position,
682 ..
683 } = self
684 {
685 *previous_scroll_position = scroll_position;
686 }
687 }
688}
689
690pub struct ContextMenuOptions {
691 pub min_entries_visible: usize,
692 pub max_entries_visible: usize,
693 pub placement: Option<ContextMenuPlacement>,
694}
695
696#[derive(Debug, Clone, PartialEq, Eq)]
697pub enum ContextMenuPlacement {
698 Above,
699 Below,
700}
701
702#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
703struct EditorActionId(usize);
704
705impl EditorActionId {
706 pub fn post_inc(&mut self) -> Self {
707 let answer = self.0;
708
709 *self = Self(answer + 1);
710
711 Self(answer)
712 }
713}
714
715// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
716// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
717
718type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
719type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
720
721#[derive(Default)]
722struct ScrollbarMarkerState {
723 scrollbar_size: Size<Pixels>,
724 dirty: bool,
725 markers: Arc<[PaintQuad]>,
726 pending_refresh: Option<Task<Result<()>>>,
727}
728
729impl ScrollbarMarkerState {
730 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
731 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
732 }
733}
734
735#[derive(Clone, Copy, PartialEq, Eq)]
736pub enum MinimapVisibility {
737 Disabled,
738 Enabled {
739 /// The configuration currently present in the users settings.
740 setting_configuration: bool,
741 /// Whether to override the currently set visibility from the users setting.
742 toggle_override: bool,
743 },
744}
745
746impl MinimapVisibility {
747 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
748 if mode.is_full() {
749 Self::Enabled {
750 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
751 toggle_override: false,
752 }
753 } else {
754 Self::Disabled
755 }
756 }
757
758 fn hidden(&self) -> Self {
759 match *self {
760 Self::Enabled {
761 setting_configuration,
762 ..
763 } => Self::Enabled {
764 setting_configuration,
765 toggle_override: setting_configuration,
766 },
767 Self::Disabled => Self::Disabled,
768 }
769 }
770
771 fn disabled(&self) -> bool {
772 match *self {
773 Self::Disabled => true,
774 _ => false,
775 }
776 }
777
778 fn settings_visibility(&self) -> bool {
779 match *self {
780 Self::Enabled {
781 setting_configuration,
782 ..
783 } => setting_configuration,
784 _ => false,
785 }
786 }
787
788 fn visible(&self) -> bool {
789 match *self {
790 Self::Enabled {
791 setting_configuration,
792 toggle_override,
793 } => setting_configuration ^ toggle_override,
794 _ => false,
795 }
796 }
797
798 fn toggle_visibility(&self) -> Self {
799 match *self {
800 Self::Enabled {
801 toggle_override,
802 setting_configuration,
803 } => Self::Enabled {
804 setting_configuration,
805 toggle_override: !toggle_override,
806 },
807 Self::Disabled => Self::Disabled,
808 }
809 }
810}
811
812#[derive(Clone, Debug)]
813struct RunnableTasks {
814 templates: Vec<(TaskSourceKind, TaskTemplate)>,
815 offset: multi_buffer::Anchor,
816 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
817 column: u32,
818 // Values of all named captures, including those starting with '_'
819 extra_variables: HashMap<String, String>,
820 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
821 context_range: Range<BufferOffset>,
822}
823
824impl RunnableTasks {
825 fn resolve<'a>(
826 &'a self,
827 cx: &'a task::TaskContext,
828 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
829 self.templates.iter().filter_map(|(kind, template)| {
830 template
831 .resolve_task(&kind.to_id_base(), cx)
832 .map(|task| (kind.clone(), task))
833 })
834 }
835}
836
837#[derive(Clone)]
838pub struct ResolvedTasks {
839 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
840 position: Anchor,
841}
842
843#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
844struct BufferOffset(usize);
845
846// Addons allow storing per-editor state in other crates (e.g. Vim)
847pub trait Addon: 'static {
848 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
849
850 fn render_buffer_header_controls(
851 &self,
852 _: &ExcerptInfo,
853 _: &Window,
854 _: &App,
855 ) -> Option<AnyElement> {
856 None
857 }
858
859 fn to_any(&self) -> &dyn std::any::Any;
860
861 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
862 None
863 }
864}
865
866struct ChangeLocation {
867 current: Option<Vec<Anchor>>,
868 original: Vec<Anchor>,
869}
870impl ChangeLocation {
871 fn locations(&self) -> &[Anchor] {
872 self.current.as_ref().unwrap_or(&self.original)
873 }
874}
875
876/// A set of caret positions, registered when the editor was edited.
877pub struct ChangeList {
878 changes: Vec<ChangeLocation>,
879 /// Currently "selected" change.
880 position: Option<usize>,
881}
882
883impl ChangeList {
884 pub fn new() -> Self {
885 Self {
886 changes: Vec::new(),
887 position: None,
888 }
889 }
890
891 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
892 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
893 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
894 if self.changes.is_empty() {
895 return None;
896 }
897
898 let prev = self.position.unwrap_or(self.changes.len());
899 let next = if direction == Direction::Prev {
900 prev.saturating_sub(count)
901 } else {
902 (prev + count).min(self.changes.len() - 1)
903 };
904 self.position = Some(next);
905 self.changes.get(next).map(|change| change.locations())
906 }
907
908 /// Adds a new change to the list, resetting the change list position.
909 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
910 self.position.take();
911 if let Some(last) = self.changes.last_mut()
912 && group
913 {
914 last.current = Some(new_positions)
915 } else {
916 self.changes.push(ChangeLocation {
917 original: new_positions,
918 current: None,
919 });
920 }
921 }
922
923 pub fn last(&self) -> Option<&[Anchor]> {
924 self.changes.last().map(|change| change.locations())
925 }
926
927 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
928 self.changes.last().map(|change| change.original.as_slice())
929 }
930
931 pub fn invert_last_group(&mut self) {
932 if let Some(last) = self.changes.last_mut() {
933 if let Some(current) = last.current.as_mut() {
934 mem::swap(&mut last.original, current);
935 }
936 }
937 }
938}
939
940#[derive(Clone)]
941struct InlineBlamePopoverState {
942 scroll_handle: ScrollHandle,
943 commit_message: Option<ParsedCommitMessage>,
944 markdown: Entity<Markdown>,
945}
946
947struct InlineBlamePopover {
948 position: gpui::Point<Pixels>,
949 hide_task: Option<Task<()>>,
950 popover_bounds: Option<Bounds<Pixels>>,
951 popover_state: InlineBlamePopoverState,
952}
953
954enum SelectionDragState {
955 /// State when no drag related activity is detected.
956 None,
957 /// State when the mouse is down on a selection that is about to be dragged.
958 ReadyToDrag {
959 selection: Selection<Anchor>,
960 click_position: gpui::Point<Pixels>,
961 mouse_down_time: Instant,
962 },
963 /// State when the mouse is dragging the selection in the editor.
964 Dragging {
965 selection: Selection<Anchor>,
966 drop_cursor: Selection<Anchor>,
967 hide_drop_cursor: bool,
968 },
969}
970
971enum ColumnarSelectionState {
972 FromMouse {
973 selection_tail: Anchor,
974 display_point: Option<DisplayPoint>,
975 },
976 FromSelection {
977 selection_tail: Anchor,
978 },
979}
980
981/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
982/// a breakpoint on them.
983#[derive(Clone, Copy, Debug, PartialEq, Eq)]
984struct PhantomBreakpointIndicator {
985 display_row: DisplayRow,
986 /// There's a small debounce between hovering over the line and showing the indicator.
987 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
988 is_active: bool,
989 collides_with_existing_breakpoint: bool,
990}
991
992/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
993///
994/// See the [module level documentation](self) for more information.
995pub struct Editor {
996 focus_handle: FocusHandle,
997 last_focused_descendant: Option<WeakFocusHandle>,
998 /// The text buffer being edited
999 buffer: Entity<MultiBuffer>,
1000 /// Map of how text in the buffer should be displayed.
1001 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1002 pub display_map: Entity<DisplayMap>,
1003 pub selections: SelectionsCollection,
1004 pub scroll_manager: ScrollManager,
1005 /// When inline assist editors are linked, they all render cursors because
1006 /// typing enters text into each of them, even the ones that aren't focused.
1007 pub(crate) show_cursor_when_unfocused: bool,
1008 columnar_selection_state: Option<ColumnarSelectionState>,
1009 add_selections_state: Option<AddSelectionsState>,
1010 select_next_state: Option<SelectNextState>,
1011 select_prev_state: Option<SelectNextState>,
1012 selection_history: SelectionHistory,
1013 defer_selection_effects: bool,
1014 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1015 autoclose_regions: Vec<AutocloseRegion>,
1016 snippet_stack: InvalidationStack<SnippetState>,
1017 select_syntax_node_history: SelectSyntaxNodeHistory,
1018 ime_transaction: Option<TransactionId>,
1019 pub diagnostics_max_severity: DiagnosticSeverity,
1020 active_diagnostics: ActiveDiagnostic,
1021 show_inline_diagnostics: bool,
1022 inline_diagnostics_update: Task<()>,
1023 inline_diagnostics_enabled: bool,
1024 diagnostics_enabled: bool,
1025 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1026 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1027 hard_wrap: Option<usize>,
1028
1029 // TODO: make this a access method
1030 pub project: Option<Entity<Project>>,
1031 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1032 completion_provider: Option<Rc<dyn CompletionProvider>>,
1033 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1034 blink_manager: Entity<BlinkManager>,
1035 show_cursor_names: bool,
1036 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1037 pub show_local_selections: bool,
1038 mode: EditorMode,
1039 show_breadcrumbs: bool,
1040 show_gutter: bool,
1041 show_scrollbars: ScrollbarAxes,
1042 minimap_visibility: MinimapVisibility,
1043 offset_content: bool,
1044 disable_expand_excerpt_buttons: bool,
1045 show_line_numbers: Option<bool>,
1046 use_relative_line_numbers: Option<bool>,
1047 show_git_diff_gutter: Option<bool>,
1048 show_code_actions: Option<bool>,
1049 show_runnables: Option<bool>,
1050 show_breakpoints: Option<bool>,
1051 show_wrap_guides: Option<bool>,
1052 show_indent_guides: Option<bool>,
1053 placeholder_text: Option<Arc<str>>,
1054 highlight_order: usize,
1055 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1056 background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
1057 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1058 scrollbar_marker_state: ScrollbarMarkerState,
1059 active_indent_guides_state: ActiveIndentGuidesState,
1060 nav_history: Option<ItemNavHistory>,
1061 context_menu: RefCell<Option<CodeContextMenu>>,
1062 context_menu_options: Option<ContextMenuOptions>,
1063 mouse_context_menu: Option<MouseContextMenu>,
1064 completion_tasks: Vec<(CompletionId, Task<()>)>,
1065 inline_blame_popover: Option<InlineBlamePopover>,
1066 inline_blame_popover_show_task: Option<Task<()>>,
1067 signature_help_state: SignatureHelpState,
1068 auto_signature_help: Option<bool>,
1069 find_all_references_task_sources: Vec<Anchor>,
1070 next_completion_id: CompletionId,
1071 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1072 code_actions_task: Option<Task<Result<()>>>,
1073 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1074 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1075 document_highlights_task: Option<Task<()>>,
1076 linked_editing_range_task: Option<Task<Option<()>>>,
1077 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1078 pending_rename: Option<RenameState>,
1079 searchable: bool,
1080 cursor_shape: CursorShape,
1081 current_line_highlight: Option<CurrentLineHighlight>,
1082 collapse_matches: bool,
1083 autoindent_mode: Option<AutoindentMode>,
1084 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1085 input_enabled: bool,
1086 use_modal_editing: bool,
1087 read_only: bool,
1088 leader_id: Option<CollaboratorId>,
1089 remote_id: Option<ViewId>,
1090 pub hover_state: HoverState,
1091 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1092 gutter_hovered: bool,
1093 hovered_link_state: Option<HoveredLinkState>,
1094 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1095 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1096 active_inline_completion: Option<InlineCompletionState>,
1097 /// Used to prevent flickering as the user types while the menu is open
1098 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1099 edit_prediction_settings: EditPredictionSettings,
1100 inline_completions_hidden_for_vim_mode: bool,
1101 show_inline_completions_override: Option<bool>,
1102 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1103 edit_prediction_preview: EditPredictionPreview,
1104 edit_prediction_indent_conflict: bool,
1105 edit_prediction_requires_modifier_in_indent_conflict: bool,
1106 inlay_hint_cache: InlayHintCache,
1107 next_inlay_id: usize,
1108 _subscriptions: Vec<Subscription>,
1109 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1110 gutter_dimensions: GutterDimensions,
1111 style: Option<EditorStyle>,
1112 text_style_refinement: Option<TextStyleRefinement>,
1113 next_editor_action_id: EditorActionId,
1114 editor_actions: Rc<
1115 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1116 >,
1117 use_autoclose: bool,
1118 use_auto_surround: bool,
1119 auto_replace_emoji_shortcode: bool,
1120 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1121 show_git_blame_gutter: bool,
1122 show_git_blame_inline: bool,
1123 show_git_blame_inline_delay_task: Option<Task<()>>,
1124 git_blame_inline_enabled: bool,
1125 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1126 serialize_dirty_buffers: bool,
1127 show_selection_menu: Option<bool>,
1128 blame: Option<Entity<GitBlame>>,
1129 blame_subscription: Option<Subscription>,
1130 custom_context_menu: Option<
1131 Box<
1132 dyn 'static
1133 + Fn(
1134 &mut Self,
1135 DisplayPoint,
1136 &mut Window,
1137 &mut Context<Self>,
1138 ) -> Option<Entity<ui::ContextMenu>>,
1139 >,
1140 >,
1141 last_bounds: Option<Bounds<Pixels>>,
1142 last_position_map: Option<Rc<PositionMap>>,
1143 expect_bounds_change: Option<Bounds<Pixels>>,
1144 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1145 tasks_update_task: Option<Task<()>>,
1146 breakpoint_store: Option<Entity<BreakpointStore>>,
1147 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1148 hovered_diff_hunk_row: Option<DisplayRow>,
1149 pull_diagnostics_task: Task<()>,
1150 in_project_search: bool,
1151 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1152 breadcrumb_header: Option<String>,
1153 focused_block: Option<FocusedBlock>,
1154 next_scroll_position: NextScrollCursorCenterTopBottom,
1155 addons: HashMap<TypeId, Box<dyn Addon>>,
1156 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1157 load_diff_task: Option<Shared<Task<()>>>,
1158 /// Whether we are temporarily displaying a diff other than git's
1159 temporary_diff_override: bool,
1160 selection_mark_mode: bool,
1161 toggle_fold_multiple_buffers: Task<()>,
1162 _scroll_cursor_center_top_bottom_task: Task<()>,
1163 serialize_selections: Task<()>,
1164 serialize_folds: Task<()>,
1165 mouse_cursor_hidden: bool,
1166 minimap: Option<Entity<Self>>,
1167 hide_mouse_mode: HideMouseMode,
1168 pub change_list: ChangeList,
1169 inline_value_cache: InlineValueCache,
1170 selection_drag_state: SelectionDragState,
1171 next_color_inlay_id: usize,
1172 colors: Option<LspColorData>,
1173 folding_newlines: Task<()>,
1174}
1175
1176#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1177enum NextScrollCursorCenterTopBottom {
1178 #[default]
1179 Center,
1180 Top,
1181 Bottom,
1182}
1183
1184impl NextScrollCursorCenterTopBottom {
1185 fn next(&self) -> Self {
1186 match self {
1187 Self::Center => Self::Top,
1188 Self::Top => Self::Bottom,
1189 Self::Bottom => Self::Center,
1190 }
1191 }
1192}
1193
1194#[derive(Clone)]
1195pub struct EditorSnapshot {
1196 pub mode: EditorMode,
1197 show_gutter: bool,
1198 show_line_numbers: Option<bool>,
1199 show_git_diff_gutter: Option<bool>,
1200 show_code_actions: Option<bool>,
1201 show_runnables: Option<bool>,
1202 show_breakpoints: Option<bool>,
1203 git_blame_gutter_max_author_length: Option<usize>,
1204 pub display_snapshot: DisplaySnapshot,
1205 pub placeholder_text: Option<Arc<str>>,
1206 is_focused: bool,
1207 scroll_anchor: ScrollAnchor,
1208 ongoing_scroll: OngoingScroll,
1209 current_line_highlight: CurrentLineHighlight,
1210 gutter_hovered: bool,
1211}
1212
1213#[derive(Default, Debug, Clone, Copy)]
1214pub struct GutterDimensions {
1215 pub left_padding: Pixels,
1216 pub right_padding: Pixels,
1217 pub width: Pixels,
1218 pub margin: Pixels,
1219 pub git_blame_entries_width: Option<Pixels>,
1220}
1221
1222impl GutterDimensions {
1223 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1224 Self {
1225 margin: Self::default_gutter_margin(font_id, font_size, cx),
1226 ..Default::default()
1227 }
1228 }
1229
1230 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1231 -cx.text_system().descent(font_id, font_size)
1232 }
1233 /// The full width of the space taken up by the gutter.
1234 pub fn full_width(&self) -> Pixels {
1235 self.margin + self.width
1236 }
1237
1238 /// The width of the space reserved for the fold indicators,
1239 /// use alongside 'justify_end' and `gutter_width` to
1240 /// right align content with the line numbers
1241 pub fn fold_area_width(&self) -> Pixels {
1242 self.margin + self.right_padding
1243 }
1244}
1245
1246struct CharacterDimensions {
1247 em_width: Pixels,
1248 em_advance: Pixels,
1249 line_height: Pixels,
1250}
1251
1252#[derive(Debug)]
1253pub struct RemoteSelection {
1254 pub replica_id: ReplicaId,
1255 pub selection: Selection<Anchor>,
1256 pub cursor_shape: CursorShape,
1257 pub collaborator_id: CollaboratorId,
1258 pub line_mode: bool,
1259 pub user_name: Option<SharedString>,
1260 pub color: PlayerColor,
1261}
1262
1263#[derive(Clone, Debug)]
1264struct SelectionHistoryEntry {
1265 selections: Arc<[Selection<Anchor>]>,
1266 select_next_state: Option<SelectNextState>,
1267 select_prev_state: Option<SelectNextState>,
1268 add_selections_state: Option<AddSelectionsState>,
1269}
1270
1271#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1272enum SelectionHistoryMode {
1273 Normal,
1274 Undoing,
1275 Redoing,
1276 Skipping,
1277}
1278
1279#[derive(Clone, PartialEq, Eq, Hash)]
1280struct HoveredCursor {
1281 replica_id: u16,
1282 selection_id: usize,
1283}
1284
1285impl Default for SelectionHistoryMode {
1286 fn default() -> Self {
1287 Self::Normal
1288 }
1289}
1290
1291#[derive(Debug)]
1292/// SelectionEffects controls the side-effects of updating the selection.
1293///
1294/// The default behaviour does "what you mostly want":
1295/// - it pushes to the nav history if the cursor moved by >10 lines
1296/// - it re-triggers completion requests
1297/// - it scrolls to fit
1298///
1299/// You might want to modify these behaviours. For example when doing a "jump"
1300/// like go to definition, we always want to add to nav history; but when scrolling
1301/// in vim mode we never do.
1302///
1303/// Similarly, you might want to disable scrolling if you don't want the viewport to
1304/// move.
1305pub struct SelectionEffects {
1306 nav_history: Option<bool>,
1307 completions: bool,
1308 scroll: Option<Autoscroll>,
1309}
1310
1311impl Default for SelectionEffects {
1312 fn default() -> Self {
1313 Self {
1314 nav_history: None,
1315 completions: true,
1316 scroll: Some(Autoscroll::fit()),
1317 }
1318 }
1319}
1320impl SelectionEffects {
1321 pub fn scroll(scroll: Autoscroll) -> Self {
1322 Self {
1323 scroll: Some(scroll),
1324 ..Default::default()
1325 }
1326 }
1327
1328 pub fn no_scroll() -> Self {
1329 Self {
1330 scroll: None,
1331 ..Default::default()
1332 }
1333 }
1334
1335 pub fn completions(self, completions: bool) -> Self {
1336 Self {
1337 completions,
1338 ..self
1339 }
1340 }
1341
1342 pub fn nav_history(self, nav_history: bool) -> Self {
1343 Self {
1344 nav_history: Some(nav_history),
1345 ..self
1346 }
1347 }
1348}
1349
1350struct DeferredSelectionEffectsState {
1351 changed: bool,
1352 effects: SelectionEffects,
1353 old_cursor_position: Anchor,
1354 history_entry: SelectionHistoryEntry,
1355}
1356
1357#[derive(Default)]
1358struct SelectionHistory {
1359 #[allow(clippy::type_complexity)]
1360 selections_by_transaction:
1361 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1362 mode: SelectionHistoryMode,
1363 undo_stack: VecDeque<SelectionHistoryEntry>,
1364 redo_stack: VecDeque<SelectionHistoryEntry>,
1365}
1366
1367impl SelectionHistory {
1368 #[track_caller]
1369 fn insert_transaction(
1370 &mut self,
1371 transaction_id: TransactionId,
1372 selections: Arc<[Selection<Anchor>]>,
1373 ) {
1374 if selections.is_empty() {
1375 log::error!(
1376 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1377 std::panic::Location::caller()
1378 );
1379 return;
1380 }
1381 self.selections_by_transaction
1382 .insert(transaction_id, (selections, None));
1383 }
1384
1385 #[allow(clippy::type_complexity)]
1386 fn transaction(
1387 &self,
1388 transaction_id: TransactionId,
1389 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1390 self.selections_by_transaction.get(&transaction_id)
1391 }
1392
1393 #[allow(clippy::type_complexity)]
1394 fn transaction_mut(
1395 &mut self,
1396 transaction_id: TransactionId,
1397 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1398 self.selections_by_transaction.get_mut(&transaction_id)
1399 }
1400
1401 fn push(&mut self, entry: SelectionHistoryEntry) {
1402 if !entry.selections.is_empty() {
1403 match self.mode {
1404 SelectionHistoryMode::Normal => {
1405 self.push_undo(entry);
1406 self.redo_stack.clear();
1407 }
1408 SelectionHistoryMode::Undoing => self.push_redo(entry),
1409 SelectionHistoryMode::Redoing => self.push_undo(entry),
1410 SelectionHistoryMode::Skipping => {}
1411 }
1412 }
1413 }
1414
1415 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1416 if self
1417 .undo_stack
1418 .back()
1419 .map_or(true, |e| e.selections != entry.selections)
1420 {
1421 self.undo_stack.push_back(entry);
1422 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1423 self.undo_stack.pop_front();
1424 }
1425 }
1426 }
1427
1428 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1429 if self
1430 .redo_stack
1431 .back()
1432 .map_or(true, |e| e.selections != entry.selections)
1433 {
1434 self.redo_stack.push_back(entry);
1435 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1436 self.redo_stack.pop_front();
1437 }
1438 }
1439 }
1440}
1441
1442#[derive(Clone, Copy)]
1443pub struct RowHighlightOptions {
1444 pub autoscroll: bool,
1445 pub include_gutter: bool,
1446}
1447
1448impl Default for RowHighlightOptions {
1449 fn default() -> Self {
1450 Self {
1451 autoscroll: Default::default(),
1452 include_gutter: true,
1453 }
1454 }
1455}
1456
1457struct RowHighlight {
1458 index: usize,
1459 range: Range<Anchor>,
1460 color: Hsla,
1461 options: RowHighlightOptions,
1462 type_id: TypeId,
1463}
1464
1465#[derive(Clone, Debug)]
1466struct AddSelectionsState {
1467 groups: Vec<AddSelectionsGroup>,
1468}
1469
1470#[derive(Clone, Debug)]
1471struct AddSelectionsGroup {
1472 above: bool,
1473 stack: Vec<usize>,
1474}
1475
1476#[derive(Clone)]
1477struct SelectNextState {
1478 query: AhoCorasick,
1479 wordwise: bool,
1480 done: bool,
1481}
1482
1483impl std::fmt::Debug for SelectNextState {
1484 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1485 f.debug_struct(std::any::type_name::<Self>())
1486 .field("wordwise", &self.wordwise)
1487 .field("done", &self.done)
1488 .finish()
1489 }
1490}
1491
1492#[derive(Debug)]
1493struct AutocloseRegion {
1494 selection_id: usize,
1495 range: Range<Anchor>,
1496 pair: BracketPair,
1497}
1498
1499#[derive(Debug)]
1500struct SnippetState {
1501 ranges: Vec<Vec<Range<Anchor>>>,
1502 active_index: usize,
1503 choices: Vec<Option<Vec<String>>>,
1504}
1505
1506#[doc(hidden)]
1507pub struct RenameState {
1508 pub range: Range<Anchor>,
1509 pub old_name: Arc<str>,
1510 pub editor: Entity<Editor>,
1511 block_id: CustomBlockId,
1512}
1513
1514struct InvalidationStack<T>(Vec<T>);
1515
1516struct RegisteredInlineCompletionProvider {
1517 provider: Arc<dyn InlineCompletionProviderHandle>,
1518 _subscription: Subscription,
1519}
1520
1521#[derive(Debug, PartialEq, Eq)]
1522pub struct ActiveDiagnosticGroup {
1523 pub active_range: Range<Anchor>,
1524 pub active_message: String,
1525 pub group_id: usize,
1526 pub blocks: HashSet<CustomBlockId>,
1527}
1528
1529#[derive(Debug, PartialEq, Eq)]
1530
1531pub(crate) enum ActiveDiagnostic {
1532 None,
1533 All,
1534 Group(ActiveDiagnosticGroup),
1535}
1536
1537#[derive(Serialize, Deserialize, Clone, Debug)]
1538pub struct ClipboardSelection {
1539 /// The number of bytes in this selection.
1540 pub len: usize,
1541 /// Whether this was a full-line selection.
1542 pub is_entire_line: bool,
1543 /// The indentation of the first line when this content was originally copied.
1544 pub first_line_indent: u32,
1545}
1546
1547// selections, scroll behavior, was newest selection reversed
1548type SelectSyntaxNodeHistoryState = (
1549 Box<[Selection<usize>]>,
1550 SelectSyntaxNodeScrollBehavior,
1551 bool,
1552);
1553
1554#[derive(Default)]
1555struct SelectSyntaxNodeHistory {
1556 stack: Vec<SelectSyntaxNodeHistoryState>,
1557 // disable temporarily to allow changing selections without losing the stack
1558 pub disable_clearing: bool,
1559}
1560
1561impl SelectSyntaxNodeHistory {
1562 pub fn try_clear(&mut self) {
1563 if !self.disable_clearing {
1564 self.stack.clear();
1565 }
1566 }
1567
1568 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1569 self.stack.push(selection);
1570 }
1571
1572 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1573 self.stack.pop()
1574 }
1575}
1576
1577enum SelectSyntaxNodeScrollBehavior {
1578 CursorTop,
1579 FitSelection,
1580 CursorBottom,
1581}
1582
1583#[derive(Debug)]
1584pub(crate) struct NavigationData {
1585 cursor_anchor: Anchor,
1586 cursor_position: Point,
1587 scroll_anchor: ScrollAnchor,
1588 scroll_top_row: u32,
1589}
1590
1591#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1592pub enum GotoDefinitionKind {
1593 Symbol,
1594 Declaration,
1595 Type,
1596 Implementation,
1597}
1598
1599#[derive(Debug, Clone)]
1600enum InlayHintRefreshReason {
1601 ModifiersChanged(bool),
1602 Toggle(bool),
1603 SettingsChange(InlayHintSettings),
1604 NewLinesShown,
1605 BufferEdited(HashSet<Arc<Language>>),
1606 RefreshRequested,
1607 ExcerptsRemoved(Vec<ExcerptId>),
1608}
1609
1610impl InlayHintRefreshReason {
1611 fn description(&self) -> &'static str {
1612 match self {
1613 Self::ModifiersChanged(_) => "modifiers changed",
1614 Self::Toggle(_) => "toggle",
1615 Self::SettingsChange(_) => "settings change",
1616 Self::NewLinesShown => "new lines shown",
1617 Self::BufferEdited(_) => "buffer edited",
1618 Self::RefreshRequested => "refresh requested",
1619 Self::ExcerptsRemoved(_) => "excerpts removed",
1620 }
1621 }
1622}
1623
1624pub enum FormatTarget {
1625 Buffers(HashSet<Entity<Buffer>>),
1626 Ranges(Vec<Range<MultiBufferPoint>>),
1627}
1628
1629pub(crate) struct FocusedBlock {
1630 id: BlockId,
1631 focus_handle: WeakFocusHandle,
1632}
1633
1634#[derive(Clone)]
1635enum JumpData {
1636 MultiBufferRow {
1637 row: MultiBufferRow,
1638 line_offset_from_top: u32,
1639 },
1640 MultiBufferPoint {
1641 excerpt_id: ExcerptId,
1642 position: Point,
1643 anchor: text::Anchor,
1644 line_offset_from_top: u32,
1645 },
1646}
1647
1648pub enum MultibufferSelectionMode {
1649 First,
1650 All,
1651}
1652
1653#[derive(Clone, Copy, Debug, Default)]
1654pub struct RewrapOptions {
1655 pub override_language_settings: bool,
1656 pub preserve_existing_whitespace: bool,
1657}
1658
1659impl Editor {
1660 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1661 let buffer = cx.new(|cx| Buffer::local("", cx));
1662 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1663 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1664 }
1665
1666 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1667 let buffer = cx.new(|cx| Buffer::local("", cx));
1668 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1669 Self::new(EditorMode::full(), buffer, None, window, cx)
1670 }
1671
1672 pub fn auto_height(
1673 min_lines: usize,
1674 max_lines: usize,
1675 window: &mut Window,
1676 cx: &mut Context<Self>,
1677 ) -> Self {
1678 let buffer = cx.new(|cx| Buffer::local("", cx));
1679 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1680 Self::new(
1681 EditorMode::AutoHeight {
1682 min_lines,
1683 max_lines: Some(max_lines),
1684 },
1685 buffer,
1686 None,
1687 window,
1688 cx,
1689 )
1690 }
1691
1692 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1693 /// The editor grows as tall as needed to fit its content.
1694 pub fn auto_height_unbounded(
1695 min_lines: usize,
1696 window: &mut Window,
1697 cx: &mut Context<Self>,
1698 ) -> Self {
1699 let buffer = cx.new(|cx| Buffer::local("", cx));
1700 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1701 Self::new(
1702 EditorMode::AutoHeight {
1703 min_lines,
1704 max_lines: None,
1705 },
1706 buffer,
1707 None,
1708 window,
1709 cx,
1710 )
1711 }
1712
1713 pub fn for_buffer(
1714 buffer: Entity<Buffer>,
1715 project: Option<Entity<Project>>,
1716 window: &mut Window,
1717 cx: &mut Context<Self>,
1718 ) -> Self {
1719 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1720 Self::new(EditorMode::full(), buffer, project, window, cx)
1721 }
1722
1723 pub fn for_multibuffer(
1724 buffer: Entity<MultiBuffer>,
1725 project: Option<Entity<Project>>,
1726 window: &mut Window,
1727 cx: &mut Context<Self>,
1728 ) -> Self {
1729 Self::new(EditorMode::full(), buffer, project, window, cx)
1730 }
1731
1732 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1733 let mut clone = Self::new(
1734 self.mode.clone(),
1735 self.buffer.clone(),
1736 self.project.clone(),
1737 window,
1738 cx,
1739 );
1740 self.display_map.update(cx, |display_map, cx| {
1741 let snapshot = display_map.snapshot(cx);
1742 clone.display_map.update(cx, |display_map, cx| {
1743 display_map.set_state(&snapshot, cx);
1744 });
1745 });
1746 clone.folds_did_change(cx);
1747 clone.selections.clone_state(&self.selections);
1748 clone.scroll_manager.clone_state(&self.scroll_manager);
1749 clone.searchable = self.searchable;
1750 clone.read_only = self.read_only;
1751 clone
1752 }
1753
1754 pub fn new(
1755 mode: EditorMode,
1756 buffer: Entity<MultiBuffer>,
1757 project: Option<Entity<Project>>,
1758 window: &mut Window,
1759 cx: &mut Context<Self>,
1760 ) -> Self {
1761 Editor::new_internal(mode, buffer, project, None, window, cx)
1762 }
1763
1764 fn new_internal(
1765 mode: EditorMode,
1766 buffer: Entity<MultiBuffer>,
1767 project: Option<Entity<Project>>,
1768 display_map: Option<Entity<DisplayMap>>,
1769 window: &mut Window,
1770 cx: &mut Context<Self>,
1771 ) -> Self {
1772 debug_assert!(
1773 display_map.is_none() || mode.is_minimap(),
1774 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1775 );
1776
1777 let full_mode = mode.is_full();
1778 let diagnostics_max_severity = if full_mode {
1779 EditorSettings::get_global(cx)
1780 .diagnostics_max_severity
1781 .unwrap_or(DiagnosticSeverity::Hint)
1782 } else {
1783 DiagnosticSeverity::Off
1784 };
1785 let style = window.text_style();
1786 let font_size = style.font_size.to_pixels(window.rem_size());
1787 let editor = cx.entity().downgrade();
1788 let fold_placeholder = FoldPlaceholder {
1789 constrain_width: true,
1790 render: Arc::new(move |fold_id, fold_range, cx| {
1791 let editor = editor.clone();
1792 div()
1793 .id(fold_id)
1794 .bg(cx.theme().colors().ghost_element_background)
1795 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1796 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1797 .rounded_xs()
1798 .size_full()
1799 .cursor_pointer()
1800 .child("⋯")
1801 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1802 .on_click(move |_, _window, cx| {
1803 editor
1804 .update(cx, |editor, cx| {
1805 editor.unfold_ranges(
1806 &[fold_range.start..fold_range.end],
1807 true,
1808 false,
1809 cx,
1810 );
1811 cx.stop_propagation();
1812 })
1813 .ok();
1814 })
1815 .into_any()
1816 }),
1817 merge_adjacent: true,
1818 ..FoldPlaceholder::default()
1819 };
1820 let display_map = display_map.unwrap_or_else(|| {
1821 cx.new(|cx| {
1822 DisplayMap::new(
1823 buffer.clone(),
1824 style.font(),
1825 font_size,
1826 None,
1827 FILE_HEADER_HEIGHT,
1828 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1829 fold_placeholder,
1830 diagnostics_max_severity,
1831 cx,
1832 )
1833 })
1834 });
1835
1836 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1837
1838 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1839
1840 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1841 .then(|| language_settings::SoftWrap::None);
1842
1843 let mut project_subscriptions = Vec::new();
1844 if mode.is_full() {
1845 if let Some(project) = project.as_ref() {
1846 project_subscriptions.push(cx.subscribe_in(
1847 project,
1848 window,
1849 |editor, _, event, window, cx| match event {
1850 project::Event::RefreshCodeLens => {
1851 // we always query lens with actions, without storing them, always refreshing them
1852 }
1853 project::Event::RefreshInlayHints => {
1854 editor
1855 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1856 }
1857 project::Event::LanguageServerAdded(..)
1858 | project::Event::LanguageServerRemoved(..) => {
1859 if editor.tasks_update_task.is_none() {
1860 editor.tasks_update_task =
1861 Some(editor.refresh_runnables(window, cx));
1862 }
1863 editor.update_lsp_data(true, None, window, cx);
1864 }
1865 project::Event::SnippetEdit(id, snippet_edits) => {
1866 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1867 let focus_handle = editor.focus_handle(cx);
1868 if focus_handle.is_focused(window) {
1869 let snapshot = buffer.read(cx).snapshot();
1870 for (range, snippet) in snippet_edits {
1871 let editor_range =
1872 language::range_from_lsp(*range).to_offset(&snapshot);
1873 editor
1874 .insert_snippet(
1875 &[editor_range],
1876 snippet.clone(),
1877 window,
1878 cx,
1879 )
1880 .ok();
1881 }
1882 }
1883 }
1884 }
1885 _ => {}
1886 },
1887 ));
1888 if let Some(task_inventory) = project
1889 .read(cx)
1890 .task_store()
1891 .read(cx)
1892 .task_inventory()
1893 .cloned()
1894 {
1895 project_subscriptions.push(cx.observe_in(
1896 &task_inventory,
1897 window,
1898 |editor, _, window, cx| {
1899 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1900 },
1901 ));
1902 };
1903
1904 project_subscriptions.push(cx.subscribe_in(
1905 &project.read(cx).breakpoint_store(),
1906 window,
1907 |editor, _, event, window, cx| match event {
1908 BreakpointStoreEvent::ClearDebugLines => {
1909 editor.clear_row_highlights::<ActiveDebugLine>();
1910 editor.refresh_inline_values(cx);
1911 }
1912 BreakpointStoreEvent::SetDebugLine => {
1913 if editor.go_to_active_debug_line(window, cx) {
1914 cx.stop_propagation();
1915 }
1916
1917 editor.refresh_inline_values(cx);
1918 }
1919 _ => {}
1920 },
1921 ));
1922 let git_store = project.read(cx).git_store().clone();
1923 let project = project.clone();
1924 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1925 match event {
1926 GitStoreEvent::RepositoryUpdated(
1927 _,
1928 RepositoryEvent::Updated {
1929 new_instance: true, ..
1930 },
1931 _,
1932 ) => {
1933 this.load_diff_task = Some(
1934 update_uncommitted_diff_for_buffer(
1935 cx.entity(),
1936 &project,
1937 this.buffer.read(cx).all_buffers(),
1938 this.buffer.clone(),
1939 cx,
1940 )
1941 .shared(),
1942 );
1943 }
1944 _ => {}
1945 }
1946 }));
1947 }
1948 }
1949
1950 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1951
1952 let inlay_hint_settings =
1953 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1954 let focus_handle = cx.focus_handle();
1955 cx.on_focus(&focus_handle, window, Self::handle_focus)
1956 .detach();
1957 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1958 .detach();
1959 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1960 .detach();
1961 cx.on_blur(&focus_handle, window, Self::handle_blur)
1962 .detach();
1963 cx.observe_pending_input(window, Self::observe_pending_input)
1964 .detach();
1965
1966 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1967 Some(false)
1968 } else {
1969 None
1970 };
1971
1972 let breakpoint_store = match (&mode, project.as_ref()) {
1973 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1974 _ => None,
1975 };
1976
1977 let mut code_action_providers = Vec::new();
1978 let mut load_uncommitted_diff = None;
1979 if let Some(project) = project.clone() {
1980 load_uncommitted_diff = Some(
1981 update_uncommitted_diff_for_buffer(
1982 cx.entity(),
1983 &project,
1984 buffer.read(cx).all_buffers(),
1985 buffer.clone(),
1986 cx,
1987 )
1988 .shared(),
1989 );
1990 code_action_providers.push(Rc::new(project) as Rc<_>);
1991 }
1992
1993 let mut editor = Self {
1994 focus_handle,
1995 show_cursor_when_unfocused: false,
1996 last_focused_descendant: None,
1997 buffer: buffer.clone(),
1998 display_map: display_map.clone(),
1999 selections,
2000 scroll_manager: ScrollManager::new(cx),
2001 columnar_selection_state: None,
2002 add_selections_state: None,
2003 select_next_state: None,
2004 select_prev_state: None,
2005 selection_history: SelectionHistory::default(),
2006 defer_selection_effects: false,
2007 deferred_selection_effects_state: None,
2008 autoclose_regions: Vec::new(),
2009 snippet_stack: InvalidationStack::default(),
2010 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2011 ime_transaction: None,
2012 active_diagnostics: ActiveDiagnostic::None,
2013 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2014 inline_diagnostics_update: Task::ready(()),
2015 inline_diagnostics: Vec::new(),
2016 soft_wrap_mode_override,
2017 diagnostics_max_severity,
2018 hard_wrap: None,
2019 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2020 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2021 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2022 project,
2023 blink_manager: blink_manager.clone(),
2024 show_local_selections: true,
2025 show_scrollbars: ScrollbarAxes {
2026 horizontal: full_mode,
2027 vertical: full_mode,
2028 },
2029 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2030 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
2031 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2032 show_gutter: mode.is_full(),
2033 show_line_numbers: None,
2034 use_relative_line_numbers: None,
2035 disable_expand_excerpt_buttons: false,
2036 show_git_diff_gutter: None,
2037 show_code_actions: None,
2038 show_runnables: None,
2039 show_breakpoints: None,
2040 show_wrap_guides: None,
2041 show_indent_guides,
2042 placeholder_text: None,
2043 highlight_order: 0,
2044 highlighted_rows: HashMap::default(),
2045 background_highlights: TreeMap::default(),
2046 gutter_highlights: TreeMap::default(),
2047 scrollbar_marker_state: ScrollbarMarkerState::default(),
2048 active_indent_guides_state: ActiveIndentGuidesState::default(),
2049 nav_history: None,
2050 context_menu: RefCell::new(None),
2051 context_menu_options: None,
2052 mouse_context_menu: None,
2053 completion_tasks: Vec::new(),
2054 inline_blame_popover: None,
2055 inline_blame_popover_show_task: None,
2056 signature_help_state: SignatureHelpState::default(),
2057 auto_signature_help: None,
2058 find_all_references_task_sources: Vec::new(),
2059 next_completion_id: 0,
2060 next_inlay_id: 0,
2061 code_action_providers,
2062 available_code_actions: None,
2063 code_actions_task: None,
2064 quick_selection_highlight_task: None,
2065 debounced_selection_highlight_task: None,
2066 document_highlights_task: None,
2067 linked_editing_range_task: None,
2068 pending_rename: None,
2069 searchable: true,
2070 cursor_shape: EditorSettings::get_global(cx)
2071 .cursor_shape
2072 .unwrap_or_default(),
2073 current_line_highlight: None,
2074 autoindent_mode: Some(AutoindentMode::EachLine),
2075 collapse_matches: false,
2076 workspace: None,
2077 input_enabled: true,
2078 use_modal_editing: mode.is_full(),
2079 read_only: mode.is_minimap(),
2080 use_autoclose: true,
2081 use_auto_surround: true,
2082 auto_replace_emoji_shortcode: false,
2083 jsx_tag_auto_close_enabled_in_any_buffer: false,
2084 leader_id: None,
2085 remote_id: None,
2086 hover_state: HoverState::default(),
2087 pending_mouse_down: None,
2088 hovered_link_state: None,
2089 edit_prediction_provider: None,
2090 active_inline_completion: None,
2091 stale_inline_completion_in_menu: None,
2092 edit_prediction_preview: EditPredictionPreview::Inactive {
2093 released_too_fast: false,
2094 },
2095 inline_diagnostics_enabled: mode.is_full(),
2096 diagnostics_enabled: mode.is_full(),
2097 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2098 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2099
2100 gutter_hovered: false,
2101 pixel_position_of_newest_cursor: None,
2102 last_bounds: None,
2103 last_position_map: None,
2104 expect_bounds_change: None,
2105 gutter_dimensions: GutterDimensions::default(),
2106 style: None,
2107 show_cursor_names: false,
2108 hovered_cursors: HashMap::default(),
2109 next_editor_action_id: EditorActionId::default(),
2110 editor_actions: Rc::default(),
2111 inline_completions_hidden_for_vim_mode: false,
2112 show_inline_completions_override: None,
2113 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
2114 edit_prediction_settings: EditPredictionSettings::Disabled,
2115 edit_prediction_indent_conflict: false,
2116 edit_prediction_requires_modifier_in_indent_conflict: true,
2117 custom_context_menu: None,
2118 show_git_blame_gutter: false,
2119 show_git_blame_inline: false,
2120 show_selection_menu: None,
2121 show_git_blame_inline_delay_task: None,
2122 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2123 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2124 serialize_dirty_buffers: !mode.is_minimap()
2125 && ProjectSettings::get_global(cx)
2126 .session
2127 .restore_unsaved_buffers,
2128 blame: None,
2129 blame_subscription: None,
2130 tasks: BTreeMap::default(),
2131
2132 breakpoint_store,
2133 gutter_breakpoint_indicator: (None, None),
2134 hovered_diff_hunk_row: None,
2135 _subscriptions: vec![
2136 cx.observe(&buffer, Self::on_buffer_changed),
2137 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2138 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2139 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2140 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2141 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2142 cx.observe_window_activation(window, |editor, window, cx| {
2143 let active = window.is_window_active();
2144 editor.blink_manager.update(cx, |blink_manager, cx| {
2145 if active {
2146 blink_manager.enable(cx);
2147 } else {
2148 blink_manager.disable(cx);
2149 }
2150 });
2151 if active {
2152 editor.show_mouse_cursor(cx);
2153 }
2154 }),
2155 ],
2156 tasks_update_task: None,
2157 pull_diagnostics_task: Task::ready(()),
2158 colors: None,
2159 next_color_inlay_id: 0,
2160 linked_edit_ranges: Default::default(),
2161 in_project_search: false,
2162 previous_search_ranges: None,
2163 breadcrumb_header: None,
2164 focused_block: None,
2165 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2166 addons: HashMap::default(),
2167 registered_buffers: HashMap::default(),
2168 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2169 selection_mark_mode: false,
2170 toggle_fold_multiple_buffers: Task::ready(()),
2171 serialize_selections: Task::ready(()),
2172 serialize_folds: Task::ready(()),
2173 text_style_refinement: None,
2174 load_diff_task: load_uncommitted_diff,
2175 temporary_diff_override: false,
2176 mouse_cursor_hidden: false,
2177 minimap: None,
2178 hide_mouse_mode: EditorSettings::get_global(cx)
2179 .hide_mouse
2180 .unwrap_or_default(),
2181 change_list: ChangeList::new(),
2182 mode,
2183 selection_drag_state: SelectionDragState::None,
2184 folding_newlines: Task::ready(()),
2185 };
2186 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2187 editor
2188 ._subscriptions
2189 .push(cx.observe(breakpoints, |_, _, cx| {
2190 cx.notify();
2191 }));
2192 }
2193 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2194 editor._subscriptions.extend(project_subscriptions);
2195
2196 editor._subscriptions.push(cx.subscribe_in(
2197 &cx.entity(),
2198 window,
2199 |editor, _, e: &EditorEvent, window, cx| match e {
2200 EditorEvent::ScrollPositionChanged { local, .. } => {
2201 if *local {
2202 let new_anchor = editor.scroll_manager.anchor();
2203 let snapshot = editor.snapshot(window, cx);
2204 editor.update_restoration_data(cx, move |data| {
2205 data.scroll_position = (
2206 new_anchor.top_row(&snapshot.buffer_snapshot),
2207 new_anchor.offset,
2208 );
2209 });
2210 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2211 editor.inline_blame_popover.take();
2212 }
2213 }
2214 EditorEvent::Edited { .. } => {
2215 if !vim_enabled(cx) {
2216 let (map, selections) = editor.selections.all_adjusted_display(cx);
2217 let pop_state = editor
2218 .change_list
2219 .last()
2220 .map(|previous| {
2221 previous.len() == selections.len()
2222 && previous.iter().enumerate().all(|(ix, p)| {
2223 p.to_display_point(&map).row()
2224 == selections[ix].head().row()
2225 })
2226 })
2227 .unwrap_or(false);
2228 let new_positions = selections
2229 .into_iter()
2230 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2231 .collect();
2232 editor
2233 .change_list
2234 .push_to_change_list(pop_state, new_positions);
2235 }
2236 }
2237 _ => (),
2238 },
2239 ));
2240
2241 if let Some(dap_store) = editor
2242 .project
2243 .as_ref()
2244 .map(|project| project.read(cx).dap_store())
2245 {
2246 let weak_editor = cx.weak_entity();
2247
2248 editor
2249 ._subscriptions
2250 .push(
2251 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2252 let session_entity = cx.entity();
2253 weak_editor
2254 .update(cx, |editor, cx| {
2255 editor._subscriptions.push(
2256 cx.subscribe(&session_entity, Self::on_debug_session_event),
2257 );
2258 })
2259 .ok();
2260 }),
2261 );
2262
2263 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2264 editor
2265 ._subscriptions
2266 .push(cx.subscribe(&session, Self::on_debug_session_event));
2267 }
2268 }
2269
2270 // skip adding the initial selection to selection history
2271 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2272 editor.end_selection(window, cx);
2273 editor.selection_history.mode = SelectionHistoryMode::Normal;
2274
2275 editor.scroll_manager.show_scrollbars(window, cx);
2276 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2277
2278 if full_mode {
2279 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2280 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2281
2282 if editor.git_blame_inline_enabled {
2283 editor.start_git_blame_inline(false, window, cx);
2284 }
2285
2286 editor.go_to_active_debug_line(window, cx);
2287
2288 if let Some(buffer) = buffer.read(cx).as_singleton() {
2289 if let Some(project) = editor.project.as_ref() {
2290 let handle = project.update(cx, |project, cx| {
2291 project.register_buffer_with_language_servers(&buffer, cx)
2292 });
2293 editor
2294 .registered_buffers
2295 .insert(buffer.read(cx).remote_id(), handle);
2296 }
2297 }
2298
2299 editor.minimap =
2300 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2301 editor.colors = Some(LspColorData::new(cx));
2302 editor.update_lsp_data(false, None, window, cx);
2303 }
2304
2305 editor.report_editor_event("Editor Opened", None, cx);
2306 editor
2307 }
2308
2309 pub fn deploy_mouse_context_menu(
2310 &mut self,
2311 position: gpui::Point<Pixels>,
2312 context_menu: Entity<ContextMenu>,
2313 window: &mut Window,
2314 cx: &mut Context<Self>,
2315 ) {
2316 self.mouse_context_menu = Some(MouseContextMenu::new(
2317 self,
2318 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2319 context_menu,
2320 window,
2321 cx,
2322 ));
2323 }
2324
2325 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2326 self.mouse_context_menu
2327 .as_ref()
2328 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2329 }
2330
2331 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2332 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2333 }
2334
2335 fn key_context_internal(
2336 &self,
2337 has_active_edit_prediction: bool,
2338 window: &Window,
2339 cx: &App,
2340 ) -> KeyContext {
2341 let mut key_context = KeyContext::new_with_defaults();
2342 key_context.add("Editor");
2343 let mode = match self.mode {
2344 EditorMode::SingleLine { .. } => "single_line",
2345 EditorMode::AutoHeight { .. } => "auto_height",
2346 EditorMode::Minimap { .. } => "minimap",
2347 EditorMode::Full { .. } => "full",
2348 };
2349
2350 if EditorSettings::jupyter_enabled(cx) {
2351 key_context.add("jupyter");
2352 }
2353
2354 key_context.set("mode", mode);
2355 if self.pending_rename.is_some() {
2356 key_context.add("renaming");
2357 }
2358
2359 match self.context_menu.borrow().as_ref() {
2360 Some(CodeContextMenu::Completions(_)) => {
2361 key_context.add("menu");
2362 key_context.add("showing_completions");
2363 }
2364 Some(CodeContextMenu::CodeActions(_)) => {
2365 key_context.add("menu");
2366 key_context.add("showing_code_actions")
2367 }
2368 None => {}
2369 }
2370
2371 if self.signature_help_state.has_multiple_signatures() {
2372 key_context.add("showing_signature_help");
2373 }
2374
2375 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2376 if !self.focus_handle(cx).contains_focused(window, cx)
2377 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2378 {
2379 for addon in self.addons.values() {
2380 addon.extend_key_context(&mut key_context, cx)
2381 }
2382 }
2383
2384 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2385 if let Some(extension) = singleton_buffer
2386 .read(cx)
2387 .file()
2388 .and_then(|file| file.path().extension()?.to_str())
2389 {
2390 key_context.set("extension", extension.to_string());
2391 }
2392 } else {
2393 key_context.add("multibuffer");
2394 }
2395
2396 if has_active_edit_prediction {
2397 if self.edit_prediction_in_conflict() {
2398 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2399 } else {
2400 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2401 key_context.add("copilot_suggestion");
2402 }
2403 }
2404
2405 if self.selection_mark_mode {
2406 key_context.add("selection_mode");
2407 }
2408
2409 key_context
2410 }
2411
2412 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2413 if self.mouse_cursor_hidden {
2414 self.mouse_cursor_hidden = false;
2415 cx.notify();
2416 }
2417 }
2418
2419 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2420 let hide_mouse_cursor = match origin {
2421 HideMouseCursorOrigin::TypingAction => {
2422 matches!(
2423 self.hide_mouse_mode,
2424 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2425 )
2426 }
2427 HideMouseCursorOrigin::MovementAction => {
2428 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2429 }
2430 };
2431 if self.mouse_cursor_hidden != hide_mouse_cursor {
2432 self.mouse_cursor_hidden = hide_mouse_cursor;
2433 cx.notify();
2434 }
2435 }
2436
2437 pub fn edit_prediction_in_conflict(&self) -> bool {
2438 if !self.show_edit_predictions_in_menu() {
2439 return false;
2440 }
2441
2442 let showing_completions = self
2443 .context_menu
2444 .borrow()
2445 .as_ref()
2446 .map_or(false, |context| {
2447 matches!(context, CodeContextMenu::Completions(_))
2448 });
2449
2450 showing_completions
2451 || self.edit_prediction_requires_modifier()
2452 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2453 // bindings to insert tab characters.
2454 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2455 }
2456
2457 pub fn accept_edit_prediction_keybind(
2458 &self,
2459 accept_partial: bool,
2460 window: &Window,
2461 cx: &App,
2462 ) -> AcceptEditPredictionBinding {
2463 let key_context = self.key_context_internal(true, window, cx);
2464 let in_conflict = self.edit_prediction_in_conflict();
2465
2466 let bindings = if accept_partial {
2467 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2468 } else {
2469 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2470 };
2471
2472 // TODO: if the binding contains multiple keystrokes, display all of them, not
2473 // just the first one.
2474 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2475 !in_conflict
2476 || binding
2477 .keystrokes()
2478 .first()
2479 .map_or(false, |keystroke| keystroke.modifiers.modified())
2480 }))
2481 }
2482
2483 pub fn new_file(
2484 workspace: &mut Workspace,
2485 _: &workspace::NewFile,
2486 window: &mut Window,
2487 cx: &mut Context<Workspace>,
2488 ) {
2489 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2490 "Failed to create buffer",
2491 window,
2492 cx,
2493 |e, _, _| match e.error_code() {
2494 ErrorCode::RemoteUpgradeRequired => Some(format!(
2495 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2496 e.error_tag("required").unwrap_or("the latest version")
2497 )),
2498 _ => None,
2499 },
2500 );
2501 }
2502
2503 pub fn new_in_workspace(
2504 workspace: &mut Workspace,
2505 window: &mut Window,
2506 cx: &mut Context<Workspace>,
2507 ) -> Task<Result<Entity<Editor>>> {
2508 let project = workspace.project().clone();
2509 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2510
2511 cx.spawn_in(window, async move |workspace, cx| {
2512 let buffer = create.await?;
2513 workspace.update_in(cx, |workspace, window, cx| {
2514 let editor =
2515 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2516 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2517 editor
2518 })
2519 })
2520 }
2521
2522 fn new_file_vertical(
2523 workspace: &mut Workspace,
2524 _: &workspace::NewFileSplitVertical,
2525 window: &mut Window,
2526 cx: &mut Context<Workspace>,
2527 ) {
2528 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2529 }
2530
2531 fn new_file_horizontal(
2532 workspace: &mut Workspace,
2533 _: &workspace::NewFileSplitHorizontal,
2534 window: &mut Window,
2535 cx: &mut Context<Workspace>,
2536 ) {
2537 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2538 }
2539
2540 fn new_file_in_direction(
2541 workspace: &mut Workspace,
2542 direction: SplitDirection,
2543 window: &mut Window,
2544 cx: &mut Context<Workspace>,
2545 ) {
2546 let project = workspace.project().clone();
2547 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2548
2549 cx.spawn_in(window, async move |workspace, cx| {
2550 let buffer = create.await?;
2551 workspace.update_in(cx, move |workspace, window, cx| {
2552 workspace.split_item(
2553 direction,
2554 Box::new(
2555 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2556 ),
2557 window,
2558 cx,
2559 )
2560 })?;
2561 anyhow::Ok(())
2562 })
2563 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2564 match e.error_code() {
2565 ErrorCode::RemoteUpgradeRequired => Some(format!(
2566 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2567 e.error_tag("required").unwrap_or("the latest version")
2568 )),
2569 _ => None,
2570 }
2571 });
2572 }
2573
2574 pub fn leader_id(&self) -> Option<CollaboratorId> {
2575 self.leader_id
2576 }
2577
2578 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2579 &self.buffer
2580 }
2581
2582 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2583 self.workspace.as_ref()?.0.upgrade()
2584 }
2585
2586 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2587 self.buffer().read(cx).title(cx)
2588 }
2589
2590 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2591 let git_blame_gutter_max_author_length = self
2592 .render_git_blame_gutter(cx)
2593 .then(|| {
2594 if let Some(blame) = self.blame.as_ref() {
2595 let max_author_length =
2596 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2597 Some(max_author_length)
2598 } else {
2599 None
2600 }
2601 })
2602 .flatten();
2603
2604 EditorSnapshot {
2605 mode: self.mode.clone(),
2606 show_gutter: self.show_gutter,
2607 show_line_numbers: self.show_line_numbers,
2608 show_git_diff_gutter: self.show_git_diff_gutter,
2609 show_code_actions: self.show_code_actions,
2610 show_runnables: self.show_runnables,
2611 show_breakpoints: self.show_breakpoints,
2612 git_blame_gutter_max_author_length,
2613 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2614 scroll_anchor: self.scroll_manager.anchor(),
2615 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2616 placeholder_text: self.placeholder_text.clone(),
2617 is_focused: self.focus_handle.is_focused(window),
2618 current_line_highlight: self
2619 .current_line_highlight
2620 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2621 gutter_hovered: self.gutter_hovered,
2622 }
2623 }
2624
2625 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2626 self.buffer.read(cx).language_at(point, cx)
2627 }
2628
2629 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2630 self.buffer.read(cx).read(cx).file_at(point).cloned()
2631 }
2632
2633 pub fn active_excerpt(
2634 &self,
2635 cx: &App,
2636 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2637 self.buffer
2638 .read(cx)
2639 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2640 }
2641
2642 pub fn mode(&self) -> &EditorMode {
2643 &self.mode
2644 }
2645
2646 pub fn set_mode(&mut self, mode: EditorMode) {
2647 self.mode = mode;
2648 }
2649
2650 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2651 self.collaboration_hub.as_deref()
2652 }
2653
2654 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2655 self.collaboration_hub = Some(hub);
2656 }
2657
2658 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2659 self.in_project_search = in_project_search;
2660 }
2661
2662 pub fn set_custom_context_menu(
2663 &mut self,
2664 f: impl 'static
2665 + Fn(
2666 &mut Self,
2667 DisplayPoint,
2668 &mut Window,
2669 &mut Context<Self>,
2670 ) -> Option<Entity<ui::ContextMenu>>,
2671 ) {
2672 self.custom_context_menu = Some(Box::new(f))
2673 }
2674
2675 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2676 self.completion_provider = provider;
2677 }
2678
2679 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2680 self.semantics_provider.clone()
2681 }
2682
2683 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2684 self.semantics_provider = provider;
2685 }
2686
2687 pub fn set_edit_prediction_provider<T>(
2688 &mut self,
2689 provider: Option<Entity<T>>,
2690 window: &mut Window,
2691 cx: &mut Context<Self>,
2692 ) where
2693 T: EditPredictionProvider,
2694 {
2695 self.edit_prediction_provider =
2696 provider.map(|provider| RegisteredInlineCompletionProvider {
2697 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2698 if this.focus_handle.is_focused(window) {
2699 this.update_visible_inline_completion(window, cx);
2700 }
2701 }),
2702 provider: Arc::new(provider),
2703 });
2704 self.update_edit_prediction_settings(cx);
2705 self.refresh_inline_completion(false, false, window, cx);
2706 }
2707
2708 pub fn placeholder_text(&self) -> Option<&str> {
2709 self.placeholder_text.as_deref()
2710 }
2711
2712 pub fn set_placeholder_text(
2713 &mut self,
2714 placeholder_text: impl Into<Arc<str>>,
2715 cx: &mut Context<Self>,
2716 ) {
2717 let placeholder_text = Some(placeholder_text.into());
2718 if self.placeholder_text != placeholder_text {
2719 self.placeholder_text = placeholder_text;
2720 cx.notify();
2721 }
2722 }
2723
2724 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2725 self.cursor_shape = cursor_shape;
2726
2727 // Disrupt blink for immediate user feedback that the cursor shape has changed
2728 self.blink_manager.update(cx, BlinkManager::show_cursor);
2729
2730 cx.notify();
2731 }
2732
2733 pub fn set_current_line_highlight(
2734 &mut self,
2735 current_line_highlight: Option<CurrentLineHighlight>,
2736 ) {
2737 self.current_line_highlight = current_line_highlight;
2738 }
2739
2740 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2741 self.collapse_matches = collapse_matches;
2742 }
2743
2744 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2745 let buffers = self.buffer.read(cx).all_buffers();
2746 let Some(project) = self.project.as_ref() else {
2747 return;
2748 };
2749 project.update(cx, |project, cx| {
2750 for buffer in buffers {
2751 self.registered_buffers
2752 .entry(buffer.read(cx).remote_id())
2753 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2754 }
2755 })
2756 }
2757
2758 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2759 if self.collapse_matches {
2760 return range.start..range.start;
2761 }
2762 range.clone()
2763 }
2764
2765 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2766 if self.display_map.read(cx).clip_at_line_ends != clip {
2767 self.display_map
2768 .update(cx, |map, _| map.clip_at_line_ends = clip);
2769 }
2770 }
2771
2772 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2773 self.input_enabled = input_enabled;
2774 }
2775
2776 pub fn set_inline_completions_hidden_for_vim_mode(
2777 &mut self,
2778 hidden: bool,
2779 window: &mut Window,
2780 cx: &mut Context<Self>,
2781 ) {
2782 if hidden != self.inline_completions_hidden_for_vim_mode {
2783 self.inline_completions_hidden_for_vim_mode = hidden;
2784 if hidden {
2785 self.update_visible_inline_completion(window, cx);
2786 } else {
2787 self.refresh_inline_completion(true, false, window, cx);
2788 }
2789 }
2790 }
2791
2792 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2793 self.menu_inline_completions_policy = value;
2794 }
2795
2796 pub fn set_autoindent(&mut self, autoindent: bool) {
2797 if autoindent {
2798 self.autoindent_mode = Some(AutoindentMode::EachLine);
2799 } else {
2800 self.autoindent_mode = None;
2801 }
2802 }
2803
2804 pub fn read_only(&self, cx: &App) -> bool {
2805 self.read_only || self.buffer.read(cx).read_only()
2806 }
2807
2808 pub fn set_read_only(&mut self, read_only: bool) {
2809 self.read_only = read_only;
2810 }
2811
2812 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2813 self.use_autoclose = autoclose;
2814 }
2815
2816 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2817 self.use_auto_surround = auto_surround;
2818 }
2819
2820 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2821 self.auto_replace_emoji_shortcode = auto_replace;
2822 }
2823
2824 pub fn toggle_edit_predictions(
2825 &mut self,
2826 _: &ToggleEditPrediction,
2827 window: &mut Window,
2828 cx: &mut Context<Self>,
2829 ) {
2830 if self.show_inline_completions_override.is_some() {
2831 self.set_show_edit_predictions(None, window, cx);
2832 } else {
2833 let show_edit_predictions = !self.edit_predictions_enabled();
2834 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2835 }
2836 }
2837
2838 pub fn set_show_edit_predictions(
2839 &mut self,
2840 show_edit_predictions: Option<bool>,
2841 window: &mut Window,
2842 cx: &mut Context<Self>,
2843 ) {
2844 self.show_inline_completions_override = show_edit_predictions;
2845 self.update_edit_prediction_settings(cx);
2846
2847 if let Some(false) = show_edit_predictions {
2848 self.discard_inline_completion(false, cx);
2849 } else {
2850 self.refresh_inline_completion(false, true, window, cx);
2851 }
2852 }
2853
2854 fn inline_completions_disabled_in_scope(
2855 &self,
2856 buffer: &Entity<Buffer>,
2857 buffer_position: language::Anchor,
2858 cx: &App,
2859 ) -> bool {
2860 let snapshot = buffer.read(cx).snapshot();
2861 let settings = snapshot.settings_at(buffer_position, cx);
2862
2863 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2864 return false;
2865 };
2866
2867 scope.override_name().map_or(false, |scope_name| {
2868 settings
2869 .edit_predictions_disabled_in
2870 .iter()
2871 .any(|s| s == scope_name)
2872 })
2873 }
2874
2875 pub fn set_use_modal_editing(&mut self, to: bool) {
2876 self.use_modal_editing = to;
2877 }
2878
2879 pub fn use_modal_editing(&self) -> bool {
2880 self.use_modal_editing
2881 }
2882
2883 fn selections_did_change(
2884 &mut self,
2885 local: bool,
2886 old_cursor_position: &Anchor,
2887 effects: SelectionEffects,
2888 window: &mut Window,
2889 cx: &mut Context<Self>,
2890 ) {
2891 window.invalidate_character_coordinates();
2892
2893 // Copy selections to primary selection buffer
2894 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2895 if local {
2896 let selections = self.selections.all::<usize>(cx);
2897 let buffer_handle = self.buffer.read(cx).read(cx);
2898
2899 let mut text = String::new();
2900 for (index, selection) in selections.iter().enumerate() {
2901 let text_for_selection = buffer_handle
2902 .text_for_range(selection.start..selection.end)
2903 .collect::<String>();
2904
2905 text.push_str(&text_for_selection);
2906 if index != selections.len() - 1 {
2907 text.push('\n');
2908 }
2909 }
2910
2911 if !text.is_empty() {
2912 cx.write_to_primary(ClipboardItem::new_string(text));
2913 }
2914 }
2915
2916 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2917 self.buffer.update(cx, |buffer, cx| {
2918 buffer.set_active_selections(
2919 &self.selections.disjoint_anchors(),
2920 self.selections.line_mode,
2921 self.cursor_shape,
2922 cx,
2923 )
2924 });
2925 }
2926 let display_map = self
2927 .display_map
2928 .update(cx, |display_map, cx| display_map.snapshot(cx));
2929 let buffer = &display_map.buffer_snapshot;
2930 if self.selections.count() == 1 {
2931 self.add_selections_state = None;
2932 }
2933 self.select_next_state = None;
2934 self.select_prev_state = None;
2935 self.select_syntax_node_history.try_clear();
2936 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2937 self.snippet_stack
2938 .invalidate(&self.selections.disjoint_anchors(), buffer);
2939 self.take_rename(false, window, cx);
2940
2941 let newest_selection = self.selections.newest_anchor();
2942 let new_cursor_position = newest_selection.head();
2943 let selection_start = newest_selection.start;
2944
2945 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
2946 self.push_to_nav_history(
2947 *old_cursor_position,
2948 Some(new_cursor_position.to_point(buffer)),
2949 false,
2950 effects.nav_history == Some(true),
2951 cx,
2952 );
2953 }
2954
2955 if local {
2956 if let Some(buffer_id) = new_cursor_position.buffer_id {
2957 if !self.registered_buffers.contains_key(&buffer_id) {
2958 if let Some(project) = self.project.as_ref() {
2959 project.update(cx, |project, cx| {
2960 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2961 return;
2962 };
2963 self.registered_buffers.insert(
2964 buffer_id,
2965 project.register_buffer_with_language_servers(&buffer, cx),
2966 );
2967 })
2968 }
2969 }
2970 }
2971
2972 let mut context_menu = self.context_menu.borrow_mut();
2973 let completion_menu = match context_menu.as_ref() {
2974 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2975 Some(CodeContextMenu::CodeActions(_)) => {
2976 *context_menu = None;
2977 None
2978 }
2979 None => None,
2980 };
2981 let completion_position = completion_menu.map(|menu| menu.initial_position);
2982 drop(context_menu);
2983
2984 if effects.completions {
2985 if let Some(completion_position) = completion_position {
2986 let start_offset = selection_start.to_offset(buffer);
2987 let position_matches = start_offset == completion_position.to_offset(buffer);
2988 let continue_showing = if position_matches {
2989 if self.snippet_stack.is_empty() {
2990 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
2991 } else {
2992 // Snippet choices can be shown even when the cursor is in whitespace.
2993 // Dismissing the menu with actions like backspace is handled by
2994 // invalidation regions.
2995 true
2996 }
2997 } else {
2998 false
2999 };
3000
3001 if continue_showing {
3002 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3003 } else {
3004 self.hide_context_menu(window, cx);
3005 }
3006 }
3007 }
3008
3009 hide_hover(self, cx);
3010
3011 if old_cursor_position.to_display_point(&display_map).row()
3012 != new_cursor_position.to_display_point(&display_map).row()
3013 {
3014 self.available_code_actions.take();
3015 }
3016 self.refresh_code_actions(window, cx);
3017 self.refresh_document_highlights(cx);
3018 self.refresh_selected_text_highlights(false, window, cx);
3019 refresh_matching_bracket_highlights(self, window, cx);
3020 self.update_visible_inline_completion(window, cx);
3021 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3022 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3023 self.inline_blame_popover.take();
3024 if self.git_blame_inline_enabled {
3025 self.start_inline_blame_timer(window, cx);
3026 }
3027 }
3028
3029 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3030 cx.emit(EditorEvent::SelectionsChanged { local });
3031
3032 let selections = &self.selections.disjoint;
3033 if selections.len() == 1 {
3034 cx.emit(SearchEvent::ActiveMatchChanged)
3035 }
3036 if local {
3037 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3038 let inmemory_selections = selections
3039 .iter()
3040 .map(|s| {
3041 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3042 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3043 })
3044 .collect();
3045 self.update_restoration_data(cx, |data| {
3046 data.selections = inmemory_selections;
3047 });
3048
3049 if WorkspaceSettings::get(None, cx).restore_on_startup
3050 != RestoreOnStartupBehavior::None
3051 {
3052 if let Some(workspace_id) =
3053 self.workspace.as_ref().and_then(|workspace| workspace.1)
3054 {
3055 let snapshot = self.buffer().read(cx).snapshot(cx);
3056 let selections = selections.clone();
3057 let background_executor = cx.background_executor().clone();
3058 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3059 self.serialize_selections = cx.background_spawn(async move {
3060 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3061 let db_selections = selections
3062 .iter()
3063 .map(|selection| {
3064 (
3065 selection.start.to_offset(&snapshot),
3066 selection.end.to_offset(&snapshot),
3067 )
3068 })
3069 .collect();
3070
3071 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3072 .await
3073 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3074 .log_err();
3075 });
3076 }
3077 }
3078 }
3079 }
3080
3081 cx.notify();
3082 }
3083
3084 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3085 use text::ToOffset as _;
3086 use text::ToPoint as _;
3087
3088 if self.mode.is_minimap()
3089 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3090 {
3091 return;
3092 }
3093
3094 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3095 return;
3096 };
3097
3098 let snapshot = singleton.read(cx).snapshot();
3099 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3100 let display_snapshot = display_map.snapshot(cx);
3101
3102 display_snapshot
3103 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3104 .map(|fold| {
3105 fold.range.start.text_anchor.to_point(&snapshot)
3106 ..fold.range.end.text_anchor.to_point(&snapshot)
3107 })
3108 .collect()
3109 });
3110 self.update_restoration_data(cx, |data| {
3111 data.folds = inmemory_folds;
3112 });
3113
3114 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3115 return;
3116 };
3117 let background_executor = cx.background_executor().clone();
3118 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3119 let db_folds = self.display_map.update(cx, |display_map, cx| {
3120 display_map
3121 .snapshot(cx)
3122 .folds_in_range(0..snapshot.len())
3123 .map(|fold| {
3124 (
3125 fold.range.start.text_anchor.to_offset(&snapshot),
3126 fold.range.end.text_anchor.to_offset(&snapshot),
3127 )
3128 })
3129 .collect()
3130 });
3131 self.serialize_folds = cx.background_spawn(async move {
3132 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3133 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3134 .await
3135 .with_context(|| {
3136 format!(
3137 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3138 )
3139 })
3140 .log_err();
3141 });
3142 }
3143
3144 pub fn sync_selections(
3145 &mut self,
3146 other: Entity<Editor>,
3147 cx: &mut Context<Self>,
3148 ) -> gpui::Subscription {
3149 let other_selections = other.read(cx).selections.disjoint.to_vec();
3150 self.selections.change_with(cx, |selections| {
3151 selections.select_anchors(other_selections);
3152 });
3153
3154 let other_subscription =
3155 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3156 EditorEvent::SelectionsChanged { local: true } => {
3157 let other_selections = other.read(cx).selections.disjoint.to_vec();
3158 if other_selections.is_empty() {
3159 return;
3160 }
3161 this.selections.change_with(cx, |selections| {
3162 selections.select_anchors(other_selections);
3163 });
3164 }
3165 _ => {}
3166 });
3167
3168 let this_subscription =
3169 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3170 EditorEvent::SelectionsChanged { local: true } => {
3171 let these_selections = this.selections.disjoint.to_vec();
3172 if these_selections.is_empty() {
3173 return;
3174 }
3175 other.update(cx, |other_editor, cx| {
3176 other_editor.selections.change_with(cx, |selections| {
3177 selections.select_anchors(these_selections);
3178 })
3179 });
3180 }
3181 _ => {}
3182 });
3183
3184 Subscription::join(other_subscription, this_subscription)
3185 }
3186
3187 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3188 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3189 /// effects of selection change occur at the end of the transaction.
3190 pub fn change_selections<R>(
3191 &mut self,
3192 effects: SelectionEffects,
3193 window: &mut Window,
3194 cx: &mut Context<Self>,
3195 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3196 ) -> R {
3197 if let Some(state) = &mut self.deferred_selection_effects_state {
3198 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3199 state.effects.completions = effects.completions;
3200 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3201 let (changed, result) = self.selections.change_with(cx, change);
3202 state.changed |= changed;
3203 return result;
3204 }
3205 let mut state = DeferredSelectionEffectsState {
3206 changed: false,
3207 effects,
3208 old_cursor_position: self.selections.newest_anchor().head(),
3209 history_entry: SelectionHistoryEntry {
3210 selections: self.selections.disjoint_anchors(),
3211 select_next_state: self.select_next_state.clone(),
3212 select_prev_state: self.select_prev_state.clone(),
3213 add_selections_state: self.add_selections_state.clone(),
3214 },
3215 };
3216 let (changed, result) = self.selections.change_with(cx, change);
3217 state.changed = state.changed || changed;
3218 if self.defer_selection_effects {
3219 self.deferred_selection_effects_state = Some(state);
3220 } else {
3221 self.apply_selection_effects(state, window, cx);
3222 }
3223 result
3224 }
3225
3226 /// Defers the effects of selection change, so that the effects of multiple calls to
3227 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3228 /// to selection history and the state of popovers based on selection position aren't
3229 /// erroneously updated.
3230 pub fn with_selection_effects_deferred<R>(
3231 &mut self,
3232 window: &mut Window,
3233 cx: &mut Context<Self>,
3234 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3235 ) -> R {
3236 let already_deferred = self.defer_selection_effects;
3237 self.defer_selection_effects = true;
3238 let result = update(self, window, cx);
3239 if !already_deferred {
3240 self.defer_selection_effects = false;
3241 if let Some(state) = self.deferred_selection_effects_state.take() {
3242 self.apply_selection_effects(state, window, cx);
3243 }
3244 }
3245 result
3246 }
3247
3248 fn apply_selection_effects(
3249 &mut self,
3250 state: DeferredSelectionEffectsState,
3251 window: &mut Window,
3252 cx: &mut Context<Self>,
3253 ) {
3254 if state.changed {
3255 self.selection_history.push(state.history_entry);
3256
3257 if let Some(autoscroll) = state.effects.scroll {
3258 self.request_autoscroll(autoscroll, cx);
3259 }
3260
3261 let old_cursor_position = &state.old_cursor_position;
3262
3263 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3264
3265 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3266 self.show_signature_help(&ShowSignatureHelp, window, cx);
3267 }
3268 }
3269 }
3270
3271 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3272 where
3273 I: IntoIterator<Item = (Range<S>, T)>,
3274 S: ToOffset,
3275 T: Into<Arc<str>>,
3276 {
3277 if self.read_only(cx) {
3278 return;
3279 }
3280
3281 self.buffer
3282 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3283 }
3284
3285 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3286 where
3287 I: IntoIterator<Item = (Range<S>, T)>,
3288 S: ToOffset,
3289 T: Into<Arc<str>>,
3290 {
3291 if self.read_only(cx) {
3292 return;
3293 }
3294
3295 self.buffer.update(cx, |buffer, cx| {
3296 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3297 });
3298 }
3299
3300 pub fn edit_with_block_indent<I, S, T>(
3301 &mut self,
3302 edits: I,
3303 original_indent_columns: Vec<Option<u32>>,
3304 cx: &mut Context<Self>,
3305 ) where
3306 I: IntoIterator<Item = (Range<S>, T)>,
3307 S: ToOffset,
3308 T: Into<Arc<str>>,
3309 {
3310 if self.read_only(cx) {
3311 return;
3312 }
3313
3314 self.buffer.update(cx, |buffer, cx| {
3315 buffer.edit(
3316 edits,
3317 Some(AutoindentMode::Block {
3318 original_indent_columns,
3319 }),
3320 cx,
3321 )
3322 });
3323 }
3324
3325 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3326 self.hide_context_menu(window, cx);
3327
3328 match phase {
3329 SelectPhase::Begin {
3330 position,
3331 add,
3332 click_count,
3333 } => self.begin_selection(position, add, click_count, window, cx),
3334 SelectPhase::BeginColumnar {
3335 position,
3336 goal_column,
3337 reset,
3338 mode,
3339 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3340 SelectPhase::Extend {
3341 position,
3342 click_count,
3343 } => self.extend_selection(position, click_count, window, cx),
3344 SelectPhase::Update {
3345 position,
3346 goal_column,
3347 scroll_delta,
3348 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3349 SelectPhase::End => self.end_selection(window, cx),
3350 }
3351 }
3352
3353 fn extend_selection(
3354 &mut self,
3355 position: DisplayPoint,
3356 click_count: usize,
3357 window: &mut Window,
3358 cx: &mut Context<Self>,
3359 ) {
3360 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3361 let tail = self.selections.newest::<usize>(cx).tail();
3362 self.begin_selection(position, false, click_count, window, cx);
3363
3364 let position = position.to_offset(&display_map, Bias::Left);
3365 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3366
3367 let mut pending_selection = self
3368 .selections
3369 .pending_anchor()
3370 .expect("extend_selection not called with pending selection");
3371 if position >= tail {
3372 pending_selection.start = tail_anchor;
3373 } else {
3374 pending_selection.end = tail_anchor;
3375 pending_selection.reversed = true;
3376 }
3377
3378 let mut pending_mode = self.selections.pending_mode().unwrap();
3379 match &mut pending_mode {
3380 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3381 _ => {}
3382 }
3383
3384 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3385 SelectionEffects::scroll(Autoscroll::fit())
3386 } else {
3387 SelectionEffects::no_scroll()
3388 };
3389
3390 self.change_selections(effects, window, cx, |s| {
3391 s.set_pending(pending_selection, pending_mode)
3392 });
3393 }
3394
3395 fn begin_selection(
3396 &mut self,
3397 position: DisplayPoint,
3398 add: bool,
3399 click_count: usize,
3400 window: &mut Window,
3401 cx: &mut Context<Self>,
3402 ) {
3403 if !self.focus_handle.is_focused(window) {
3404 self.last_focused_descendant = None;
3405 window.focus(&self.focus_handle);
3406 }
3407
3408 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3409 let buffer = &display_map.buffer_snapshot;
3410 let position = display_map.clip_point(position, Bias::Left);
3411
3412 let start;
3413 let end;
3414 let mode;
3415 let mut auto_scroll;
3416 match click_count {
3417 1 => {
3418 start = buffer.anchor_before(position.to_point(&display_map));
3419 end = start;
3420 mode = SelectMode::Character;
3421 auto_scroll = true;
3422 }
3423 2 => {
3424 let position = display_map
3425 .clip_point(position, Bias::Left)
3426 .to_offset(&display_map, Bias::Left);
3427 let (range, _) = buffer.surrounding_word(position, false);
3428 start = buffer.anchor_before(range.start);
3429 end = buffer.anchor_before(range.end);
3430 mode = SelectMode::Word(start..end);
3431 auto_scroll = true;
3432 }
3433 3 => {
3434 let position = display_map
3435 .clip_point(position, Bias::Left)
3436 .to_point(&display_map);
3437 let line_start = display_map.prev_line_boundary(position).0;
3438 let next_line_start = buffer.clip_point(
3439 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3440 Bias::Left,
3441 );
3442 start = buffer.anchor_before(line_start);
3443 end = buffer.anchor_before(next_line_start);
3444 mode = SelectMode::Line(start..end);
3445 auto_scroll = true;
3446 }
3447 _ => {
3448 start = buffer.anchor_before(0);
3449 end = buffer.anchor_before(buffer.len());
3450 mode = SelectMode::All;
3451 auto_scroll = false;
3452 }
3453 }
3454 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3455
3456 let point_to_delete: Option<usize> = {
3457 let selected_points: Vec<Selection<Point>> =
3458 self.selections.disjoint_in_range(start..end, cx);
3459
3460 if !add || click_count > 1 {
3461 None
3462 } else if !selected_points.is_empty() {
3463 Some(selected_points[0].id)
3464 } else {
3465 let clicked_point_already_selected =
3466 self.selections.disjoint.iter().find(|selection| {
3467 selection.start.to_point(buffer) == start.to_point(buffer)
3468 || selection.end.to_point(buffer) == end.to_point(buffer)
3469 });
3470
3471 clicked_point_already_selected.map(|selection| selection.id)
3472 }
3473 };
3474
3475 let selections_count = self.selections.count();
3476 let effects = if auto_scroll {
3477 SelectionEffects::default()
3478 } else {
3479 SelectionEffects::no_scroll()
3480 };
3481
3482 self.change_selections(effects, window, cx, |s| {
3483 if let Some(point_to_delete) = point_to_delete {
3484 s.delete(point_to_delete);
3485
3486 if selections_count == 1 {
3487 s.set_pending_anchor_range(start..end, mode);
3488 }
3489 } else {
3490 if !add {
3491 s.clear_disjoint();
3492 }
3493
3494 s.set_pending_anchor_range(start..end, mode);
3495 }
3496 });
3497 }
3498
3499 fn begin_columnar_selection(
3500 &mut self,
3501 position: DisplayPoint,
3502 goal_column: u32,
3503 reset: bool,
3504 mode: ColumnarMode,
3505 window: &mut Window,
3506 cx: &mut Context<Self>,
3507 ) {
3508 if !self.focus_handle.is_focused(window) {
3509 self.last_focused_descendant = None;
3510 window.focus(&self.focus_handle);
3511 }
3512
3513 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3514
3515 if reset {
3516 let pointer_position = display_map
3517 .buffer_snapshot
3518 .anchor_before(position.to_point(&display_map));
3519
3520 self.change_selections(
3521 SelectionEffects::scroll(Autoscroll::newest()),
3522 window,
3523 cx,
3524 |s| {
3525 s.clear_disjoint();
3526 s.set_pending_anchor_range(
3527 pointer_position..pointer_position,
3528 SelectMode::Character,
3529 );
3530 },
3531 );
3532 };
3533
3534 let tail = self.selections.newest::<Point>(cx).tail();
3535 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3536 self.columnar_selection_state = match mode {
3537 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3538 selection_tail: selection_anchor,
3539 display_point: if reset {
3540 if position.column() != goal_column {
3541 Some(DisplayPoint::new(position.row(), goal_column))
3542 } else {
3543 None
3544 }
3545 } else {
3546 None
3547 },
3548 }),
3549 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3550 selection_tail: selection_anchor,
3551 }),
3552 };
3553
3554 if !reset {
3555 self.select_columns(position, goal_column, &display_map, window, cx);
3556 }
3557 }
3558
3559 fn update_selection(
3560 &mut self,
3561 position: DisplayPoint,
3562 goal_column: u32,
3563 scroll_delta: gpui::Point<f32>,
3564 window: &mut Window,
3565 cx: &mut Context<Self>,
3566 ) {
3567 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3568
3569 if self.columnar_selection_state.is_some() {
3570 self.select_columns(position, goal_column, &display_map, window, cx);
3571 } else if let Some(mut pending) = self.selections.pending_anchor() {
3572 let buffer = &display_map.buffer_snapshot;
3573 let head;
3574 let tail;
3575 let mode = self.selections.pending_mode().unwrap();
3576 match &mode {
3577 SelectMode::Character => {
3578 head = position.to_point(&display_map);
3579 tail = pending.tail().to_point(buffer);
3580 }
3581 SelectMode::Word(original_range) => {
3582 let offset = display_map
3583 .clip_point(position, Bias::Left)
3584 .to_offset(&display_map, Bias::Left);
3585 let original_range = original_range.to_offset(buffer);
3586
3587 let head_offset = if buffer.is_inside_word(offset, false)
3588 || original_range.contains(&offset)
3589 {
3590 let (word_range, _) = buffer.surrounding_word(offset, false);
3591 if word_range.start < original_range.start {
3592 word_range.start
3593 } else {
3594 word_range.end
3595 }
3596 } else {
3597 offset
3598 };
3599
3600 head = head_offset.to_point(buffer);
3601 if head_offset <= original_range.start {
3602 tail = original_range.end.to_point(buffer);
3603 } else {
3604 tail = original_range.start.to_point(buffer);
3605 }
3606 }
3607 SelectMode::Line(original_range) => {
3608 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3609
3610 let position = display_map
3611 .clip_point(position, Bias::Left)
3612 .to_point(&display_map);
3613 let line_start = display_map.prev_line_boundary(position).0;
3614 let next_line_start = buffer.clip_point(
3615 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3616 Bias::Left,
3617 );
3618
3619 if line_start < original_range.start {
3620 head = line_start
3621 } else {
3622 head = next_line_start
3623 }
3624
3625 if head <= original_range.start {
3626 tail = original_range.end;
3627 } else {
3628 tail = original_range.start;
3629 }
3630 }
3631 SelectMode::All => {
3632 return;
3633 }
3634 };
3635
3636 if head < tail {
3637 pending.start = buffer.anchor_before(head);
3638 pending.end = buffer.anchor_before(tail);
3639 pending.reversed = true;
3640 } else {
3641 pending.start = buffer.anchor_before(tail);
3642 pending.end = buffer.anchor_before(head);
3643 pending.reversed = false;
3644 }
3645
3646 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3647 s.set_pending(pending, mode);
3648 });
3649 } else {
3650 log::error!("update_selection dispatched with no pending selection");
3651 return;
3652 }
3653
3654 self.apply_scroll_delta(scroll_delta, window, cx);
3655 cx.notify();
3656 }
3657
3658 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3659 self.columnar_selection_state.take();
3660 if self.selections.pending_anchor().is_some() {
3661 let selections = self.selections.all::<usize>(cx);
3662 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3663 s.select(selections);
3664 s.clear_pending();
3665 });
3666 }
3667 }
3668
3669 fn select_columns(
3670 &mut self,
3671 head: DisplayPoint,
3672 goal_column: u32,
3673 display_map: &DisplaySnapshot,
3674 window: &mut Window,
3675 cx: &mut Context<Self>,
3676 ) {
3677 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3678 return;
3679 };
3680
3681 let tail = match columnar_state {
3682 ColumnarSelectionState::FromMouse {
3683 selection_tail,
3684 display_point,
3685 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(&display_map)),
3686 ColumnarSelectionState::FromSelection { selection_tail } => {
3687 selection_tail.to_display_point(&display_map)
3688 }
3689 };
3690
3691 let start_row = cmp::min(tail.row(), head.row());
3692 let end_row = cmp::max(tail.row(), head.row());
3693 let start_column = cmp::min(tail.column(), goal_column);
3694 let end_column = cmp::max(tail.column(), goal_column);
3695 let reversed = start_column < tail.column();
3696
3697 let selection_ranges = (start_row.0..=end_row.0)
3698 .map(DisplayRow)
3699 .filter_map(|row| {
3700 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3701 || start_column <= display_map.line_len(row))
3702 && !display_map.is_block_line(row)
3703 {
3704 let start = display_map
3705 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3706 .to_point(display_map);
3707 let end = display_map
3708 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3709 .to_point(display_map);
3710 if reversed {
3711 Some(end..start)
3712 } else {
3713 Some(start..end)
3714 }
3715 } else {
3716 None
3717 }
3718 })
3719 .collect::<Vec<_>>();
3720
3721 let ranges = match columnar_state {
3722 ColumnarSelectionState::FromMouse { .. } => {
3723 let mut non_empty_ranges = selection_ranges
3724 .iter()
3725 .filter(|selection_range| selection_range.start != selection_range.end)
3726 .peekable();
3727 if non_empty_ranges.peek().is_some() {
3728 non_empty_ranges.cloned().collect()
3729 } else {
3730 selection_ranges
3731 }
3732 }
3733 _ => selection_ranges,
3734 };
3735
3736 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3737 s.select_ranges(ranges);
3738 });
3739 cx.notify();
3740 }
3741
3742 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3743 self.selections
3744 .all_adjusted(cx)
3745 .iter()
3746 .any(|selection| !selection.is_empty())
3747 }
3748
3749 pub fn has_pending_nonempty_selection(&self) -> bool {
3750 let pending_nonempty_selection = match self.selections.pending_anchor() {
3751 Some(Selection { start, end, .. }) => start != end,
3752 None => false,
3753 };
3754
3755 pending_nonempty_selection
3756 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3757 }
3758
3759 pub fn has_pending_selection(&self) -> bool {
3760 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3761 }
3762
3763 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3764 self.selection_mark_mode = false;
3765 self.selection_drag_state = SelectionDragState::None;
3766
3767 if self.clear_expanded_diff_hunks(cx) {
3768 cx.notify();
3769 return;
3770 }
3771 if self.dismiss_menus_and_popups(true, window, cx) {
3772 return;
3773 }
3774
3775 if self.mode.is_full()
3776 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3777 {
3778 return;
3779 }
3780
3781 cx.propagate();
3782 }
3783
3784 pub fn dismiss_menus_and_popups(
3785 &mut self,
3786 is_user_requested: bool,
3787 window: &mut Window,
3788 cx: &mut Context<Self>,
3789 ) -> bool {
3790 if self.take_rename(false, window, cx).is_some() {
3791 return true;
3792 }
3793
3794 if hide_hover(self, cx) {
3795 return true;
3796 }
3797
3798 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3799 return true;
3800 }
3801
3802 if self.hide_context_menu(window, cx).is_some() {
3803 return true;
3804 }
3805
3806 if self.mouse_context_menu.take().is_some() {
3807 return true;
3808 }
3809
3810 if is_user_requested && self.discard_inline_completion(true, cx) {
3811 return true;
3812 }
3813
3814 if self.snippet_stack.pop().is_some() {
3815 return true;
3816 }
3817
3818 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3819 self.dismiss_diagnostics(cx);
3820 return true;
3821 }
3822
3823 false
3824 }
3825
3826 fn linked_editing_ranges_for(
3827 &self,
3828 selection: Range<text::Anchor>,
3829 cx: &App,
3830 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3831 if self.linked_edit_ranges.is_empty() {
3832 return None;
3833 }
3834 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3835 selection.end.buffer_id.and_then(|end_buffer_id| {
3836 if selection.start.buffer_id != Some(end_buffer_id) {
3837 return None;
3838 }
3839 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3840 let snapshot = buffer.read(cx).snapshot();
3841 self.linked_edit_ranges
3842 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3843 .map(|ranges| (ranges, snapshot, buffer))
3844 })?;
3845 use text::ToOffset as TO;
3846 // find offset from the start of current range to current cursor position
3847 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3848
3849 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3850 let start_difference = start_offset - start_byte_offset;
3851 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3852 let end_difference = end_offset - start_byte_offset;
3853 // Current range has associated linked ranges.
3854 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3855 for range in linked_ranges.iter() {
3856 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3857 let end_offset = start_offset + end_difference;
3858 let start_offset = start_offset + start_difference;
3859 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3860 continue;
3861 }
3862 if self.selections.disjoint_anchor_ranges().any(|s| {
3863 if s.start.buffer_id != selection.start.buffer_id
3864 || s.end.buffer_id != selection.end.buffer_id
3865 {
3866 return false;
3867 }
3868 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3869 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3870 }) {
3871 continue;
3872 }
3873 let start = buffer_snapshot.anchor_after(start_offset);
3874 let end = buffer_snapshot.anchor_after(end_offset);
3875 linked_edits
3876 .entry(buffer.clone())
3877 .or_default()
3878 .push(start..end);
3879 }
3880 Some(linked_edits)
3881 }
3882
3883 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3884 let text: Arc<str> = text.into();
3885
3886 if self.read_only(cx) {
3887 return;
3888 }
3889
3890 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3891
3892 let selections = self.selections.all_adjusted(cx);
3893 let mut bracket_inserted = false;
3894 let mut edits = Vec::new();
3895 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3896 let mut new_selections = Vec::with_capacity(selections.len());
3897 let mut new_autoclose_regions = Vec::new();
3898 let snapshot = self.buffer.read(cx).read(cx);
3899 let mut clear_linked_edit_ranges = false;
3900
3901 for (selection, autoclose_region) in
3902 self.selections_with_autoclose_regions(selections, &snapshot)
3903 {
3904 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3905 // Determine if the inserted text matches the opening or closing
3906 // bracket of any of this language's bracket pairs.
3907 let mut bracket_pair = None;
3908 let mut is_bracket_pair_start = false;
3909 let mut is_bracket_pair_end = false;
3910 if !text.is_empty() {
3911 let mut bracket_pair_matching_end = None;
3912 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3913 // and they are removing the character that triggered IME popup.
3914 for (pair, enabled) in scope.brackets() {
3915 if !pair.close && !pair.surround {
3916 continue;
3917 }
3918
3919 if enabled && pair.start.ends_with(text.as_ref()) {
3920 let prefix_len = pair.start.len() - text.len();
3921 let preceding_text_matches_prefix = prefix_len == 0
3922 || (selection.start.column >= (prefix_len as u32)
3923 && snapshot.contains_str_at(
3924 Point::new(
3925 selection.start.row,
3926 selection.start.column - (prefix_len as u32),
3927 ),
3928 &pair.start[..prefix_len],
3929 ));
3930 if preceding_text_matches_prefix {
3931 bracket_pair = Some(pair.clone());
3932 is_bracket_pair_start = true;
3933 break;
3934 }
3935 }
3936 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3937 {
3938 // take first bracket pair matching end, but don't break in case a later bracket
3939 // pair matches start
3940 bracket_pair_matching_end = Some(pair.clone());
3941 }
3942 }
3943 if let Some(end) = bracket_pair_matching_end
3944 && bracket_pair.is_none()
3945 {
3946 bracket_pair = Some(end);
3947 is_bracket_pair_end = true;
3948 }
3949 }
3950
3951 if let Some(bracket_pair) = bracket_pair {
3952 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3953 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3954 let auto_surround =
3955 self.use_auto_surround && snapshot_settings.use_auto_surround;
3956 if selection.is_empty() {
3957 if is_bracket_pair_start {
3958 // If the inserted text is a suffix of an opening bracket and the
3959 // selection is preceded by the rest of the opening bracket, then
3960 // insert the closing bracket.
3961 let following_text_allows_autoclose = snapshot
3962 .chars_at(selection.start)
3963 .next()
3964 .map_or(true, |c| scope.should_autoclose_before(c));
3965
3966 let preceding_text_allows_autoclose = selection.start.column == 0
3967 || snapshot.reversed_chars_at(selection.start).next().map_or(
3968 true,
3969 |c| {
3970 bracket_pair.start != bracket_pair.end
3971 || !snapshot
3972 .char_classifier_at(selection.start)
3973 .is_word(c)
3974 },
3975 );
3976
3977 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3978 && bracket_pair.start.len() == 1
3979 {
3980 let target = bracket_pair.start.chars().next().unwrap();
3981 let current_line_count = snapshot
3982 .reversed_chars_at(selection.start)
3983 .take_while(|&c| c != '\n')
3984 .filter(|&c| c == target)
3985 .count();
3986 current_line_count % 2 == 1
3987 } else {
3988 false
3989 };
3990
3991 if autoclose
3992 && bracket_pair.close
3993 && following_text_allows_autoclose
3994 && preceding_text_allows_autoclose
3995 && !is_closing_quote
3996 {
3997 let anchor = snapshot.anchor_before(selection.end);
3998 new_selections.push((selection.map(|_| anchor), text.len()));
3999 new_autoclose_regions.push((
4000 anchor,
4001 text.len(),
4002 selection.id,
4003 bracket_pair.clone(),
4004 ));
4005 edits.push((
4006 selection.range(),
4007 format!("{}{}", text, bracket_pair.end).into(),
4008 ));
4009 bracket_inserted = true;
4010 continue;
4011 }
4012 }
4013
4014 if let Some(region) = autoclose_region {
4015 // If the selection is followed by an auto-inserted closing bracket,
4016 // then don't insert that closing bracket again; just move the selection
4017 // past the closing bracket.
4018 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4019 && text.as_ref() == region.pair.end.as_str();
4020 if should_skip {
4021 let anchor = snapshot.anchor_after(selection.end);
4022 new_selections
4023 .push((selection.map(|_| anchor), region.pair.end.len()));
4024 continue;
4025 }
4026 }
4027
4028 let always_treat_brackets_as_autoclosed = snapshot
4029 .language_settings_at(selection.start, cx)
4030 .always_treat_brackets_as_autoclosed;
4031 if always_treat_brackets_as_autoclosed
4032 && is_bracket_pair_end
4033 && snapshot.contains_str_at(selection.end, text.as_ref())
4034 {
4035 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4036 // and the inserted text is a closing bracket and the selection is followed
4037 // by the closing bracket then move the selection past the closing bracket.
4038 let anchor = snapshot.anchor_after(selection.end);
4039 new_selections.push((selection.map(|_| anchor), text.len()));
4040 continue;
4041 }
4042 }
4043 // If an opening bracket is 1 character long and is typed while
4044 // text is selected, then surround that text with the bracket pair.
4045 else if auto_surround
4046 && bracket_pair.surround
4047 && is_bracket_pair_start
4048 && bracket_pair.start.chars().count() == 1
4049 {
4050 edits.push((selection.start..selection.start, text.clone()));
4051 edits.push((
4052 selection.end..selection.end,
4053 bracket_pair.end.as_str().into(),
4054 ));
4055 bracket_inserted = true;
4056 new_selections.push((
4057 Selection {
4058 id: selection.id,
4059 start: snapshot.anchor_after(selection.start),
4060 end: snapshot.anchor_before(selection.end),
4061 reversed: selection.reversed,
4062 goal: selection.goal,
4063 },
4064 0,
4065 ));
4066 continue;
4067 }
4068 }
4069 }
4070
4071 if self.auto_replace_emoji_shortcode
4072 && selection.is_empty()
4073 && text.as_ref().ends_with(':')
4074 {
4075 if let Some(possible_emoji_short_code) =
4076 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4077 {
4078 if !possible_emoji_short_code.is_empty() {
4079 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
4080 let emoji_shortcode_start = Point::new(
4081 selection.start.row,
4082 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4083 );
4084
4085 // Remove shortcode from buffer
4086 edits.push((
4087 emoji_shortcode_start..selection.start,
4088 "".to_string().into(),
4089 ));
4090 new_selections.push((
4091 Selection {
4092 id: selection.id,
4093 start: snapshot.anchor_after(emoji_shortcode_start),
4094 end: snapshot.anchor_before(selection.start),
4095 reversed: selection.reversed,
4096 goal: selection.goal,
4097 },
4098 0,
4099 ));
4100
4101 // Insert emoji
4102 let selection_start_anchor = snapshot.anchor_after(selection.start);
4103 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4104 edits.push((selection.start..selection.end, emoji.to_string().into()));
4105
4106 continue;
4107 }
4108 }
4109 }
4110 }
4111
4112 // If not handling any auto-close operation, then just replace the selected
4113 // text with the given input and move the selection to the end of the
4114 // newly inserted text.
4115 let anchor = snapshot.anchor_after(selection.end);
4116 if !self.linked_edit_ranges.is_empty() {
4117 let start_anchor = snapshot.anchor_before(selection.start);
4118
4119 let is_word_char = text.chars().next().map_or(true, |char| {
4120 let classifier = snapshot
4121 .char_classifier_at(start_anchor.to_offset(&snapshot))
4122 .ignore_punctuation(true);
4123 classifier.is_word(char)
4124 });
4125
4126 if is_word_char {
4127 if let Some(ranges) = self
4128 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4129 {
4130 for (buffer, edits) in ranges {
4131 linked_edits
4132 .entry(buffer.clone())
4133 .or_default()
4134 .extend(edits.into_iter().map(|range| (range, text.clone())));
4135 }
4136 }
4137 } else {
4138 clear_linked_edit_ranges = true;
4139 }
4140 }
4141
4142 new_selections.push((selection.map(|_| anchor), 0));
4143 edits.push((selection.start..selection.end, text.clone()));
4144 }
4145
4146 drop(snapshot);
4147
4148 self.transact(window, cx, |this, window, cx| {
4149 if clear_linked_edit_ranges {
4150 this.linked_edit_ranges.clear();
4151 }
4152 let initial_buffer_versions =
4153 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4154
4155 this.buffer.update(cx, |buffer, cx| {
4156 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4157 });
4158 for (buffer, edits) in linked_edits {
4159 buffer.update(cx, |buffer, cx| {
4160 let snapshot = buffer.snapshot();
4161 let edits = edits
4162 .into_iter()
4163 .map(|(range, text)| {
4164 use text::ToPoint as TP;
4165 let end_point = TP::to_point(&range.end, &snapshot);
4166 let start_point = TP::to_point(&range.start, &snapshot);
4167 (start_point..end_point, text)
4168 })
4169 .sorted_by_key(|(range, _)| range.start);
4170 buffer.edit(edits, None, cx);
4171 })
4172 }
4173 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4174 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4175 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4176 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4177 .zip(new_selection_deltas)
4178 .map(|(selection, delta)| Selection {
4179 id: selection.id,
4180 start: selection.start + delta,
4181 end: selection.end + delta,
4182 reversed: selection.reversed,
4183 goal: SelectionGoal::None,
4184 })
4185 .collect::<Vec<_>>();
4186
4187 let mut i = 0;
4188 for (position, delta, selection_id, pair) in new_autoclose_regions {
4189 let position = position.to_offset(&map.buffer_snapshot) + delta;
4190 let start = map.buffer_snapshot.anchor_before(position);
4191 let end = map.buffer_snapshot.anchor_after(position);
4192 while let Some(existing_state) = this.autoclose_regions.get(i) {
4193 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4194 Ordering::Less => i += 1,
4195 Ordering::Greater => break,
4196 Ordering::Equal => {
4197 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4198 Ordering::Less => i += 1,
4199 Ordering::Equal => break,
4200 Ordering::Greater => break,
4201 }
4202 }
4203 }
4204 }
4205 this.autoclose_regions.insert(
4206 i,
4207 AutocloseRegion {
4208 selection_id,
4209 range: start..end,
4210 pair,
4211 },
4212 );
4213 }
4214
4215 let had_active_inline_completion = this.has_active_inline_completion();
4216 this.change_selections(
4217 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4218 window,
4219 cx,
4220 |s| s.select(new_selections),
4221 );
4222
4223 if !bracket_inserted {
4224 if let Some(on_type_format_task) =
4225 this.trigger_on_type_formatting(text.to_string(), window, cx)
4226 {
4227 on_type_format_task.detach_and_log_err(cx);
4228 }
4229 }
4230
4231 let editor_settings = EditorSettings::get_global(cx);
4232 if bracket_inserted
4233 && (editor_settings.auto_signature_help
4234 || editor_settings.show_signature_help_after_edits)
4235 {
4236 this.show_signature_help(&ShowSignatureHelp, window, cx);
4237 }
4238
4239 let trigger_in_words =
4240 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4241 if this.hard_wrap.is_some() {
4242 let latest: Range<Point> = this.selections.newest(cx).range();
4243 if latest.is_empty()
4244 && this
4245 .buffer()
4246 .read(cx)
4247 .snapshot(cx)
4248 .line_len(MultiBufferRow(latest.start.row))
4249 == latest.start.column
4250 {
4251 this.rewrap_impl(
4252 RewrapOptions {
4253 override_language_settings: true,
4254 preserve_existing_whitespace: true,
4255 },
4256 cx,
4257 )
4258 }
4259 }
4260 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4261 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4262 this.refresh_inline_completion(true, false, window, cx);
4263 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4264 });
4265 }
4266
4267 fn find_possible_emoji_shortcode_at_position(
4268 snapshot: &MultiBufferSnapshot,
4269 position: Point,
4270 ) -> Option<String> {
4271 let mut chars = Vec::new();
4272 let mut found_colon = false;
4273 for char in snapshot.reversed_chars_at(position).take(100) {
4274 // Found a possible emoji shortcode in the middle of the buffer
4275 if found_colon {
4276 if char.is_whitespace() {
4277 chars.reverse();
4278 return Some(chars.iter().collect());
4279 }
4280 // If the previous character is not a whitespace, we are in the middle of a word
4281 // and we only want to complete the shortcode if the word is made up of other emojis
4282 let mut containing_word = String::new();
4283 for ch in snapshot
4284 .reversed_chars_at(position)
4285 .skip(chars.len() + 1)
4286 .take(100)
4287 {
4288 if ch.is_whitespace() {
4289 break;
4290 }
4291 containing_word.push(ch);
4292 }
4293 let containing_word = containing_word.chars().rev().collect::<String>();
4294 if util::word_consists_of_emojis(containing_word.as_str()) {
4295 chars.reverse();
4296 return Some(chars.iter().collect());
4297 }
4298 }
4299
4300 if char.is_whitespace() || !char.is_ascii() {
4301 return None;
4302 }
4303 if char == ':' {
4304 found_colon = true;
4305 } else {
4306 chars.push(char);
4307 }
4308 }
4309 // Found a possible emoji shortcode at the beginning of the buffer
4310 chars.reverse();
4311 Some(chars.iter().collect())
4312 }
4313
4314 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4315 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4316 self.transact(window, cx, |this, window, cx| {
4317 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4318 let selections = this.selections.all::<usize>(cx);
4319 let multi_buffer = this.buffer.read(cx);
4320 let buffer = multi_buffer.snapshot(cx);
4321 selections
4322 .iter()
4323 .map(|selection| {
4324 let start_point = selection.start.to_point(&buffer);
4325 let mut existing_indent =
4326 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4327 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4328 let start = selection.start;
4329 let end = selection.end;
4330 let selection_is_empty = start == end;
4331 let language_scope = buffer.language_scope_at(start);
4332 let (
4333 comment_delimiter,
4334 doc_delimiter,
4335 insert_extra_newline,
4336 indent_on_newline,
4337 indent_on_extra_newline,
4338 ) = if let Some(language) = &language_scope {
4339 let mut insert_extra_newline =
4340 insert_extra_newline_brackets(&buffer, start..end, language)
4341 || insert_extra_newline_tree_sitter(&buffer, start..end);
4342
4343 // Comment extension on newline is allowed only for cursor selections
4344 let comment_delimiter = maybe!({
4345 if !selection_is_empty {
4346 return None;
4347 }
4348
4349 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4350 return None;
4351 }
4352
4353 let delimiters = language.line_comment_prefixes();
4354 let max_len_of_delimiter =
4355 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4356 let (snapshot, range) =
4357 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4358
4359 let num_of_whitespaces = snapshot
4360 .chars_for_range(range.clone())
4361 .take_while(|c| c.is_whitespace())
4362 .count();
4363 let comment_candidate = snapshot
4364 .chars_for_range(range)
4365 .skip(num_of_whitespaces)
4366 .take(max_len_of_delimiter)
4367 .collect::<String>();
4368 let (delimiter, trimmed_len) = delimiters
4369 .iter()
4370 .filter_map(|delimiter| {
4371 let prefix = delimiter.trim_end();
4372 if comment_candidate.starts_with(prefix) {
4373 Some((delimiter, prefix.len()))
4374 } else {
4375 None
4376 }
4377 })
4378 .max_by_key(|(_, len)| *len)?;
4379
4380 let cursor_is_placed_after_comment_marker =
4381 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4382 if cursor_is_placed_after_comment_marker {
4383 Some(delimiter.clone())
4384 } else {
4385 None
4386 }
4387 });
4388
4389 let mut indent_on_newline = IndentSize::spaces(0);
4390 let mut indent_on_extra_newline = IndentSize::spaces(0);
4391
4392 let doc_delimiter = maybe!({
4393 if !selection_is_empty {
4394 return None;
4395 }
4396
4397 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4398 return None;
4399 }
4400
4401 let DocumentationConfig {
4402 start: start_tag,
4403 end: end_tag,
4404 prefix: delimiter,
4405 tab_size: len,
4406 } = language.documentation()?;
4407
4408 let is_within_block_comment = buffer
4409 .language_scope_at(start_point)
4410 .is_some_and(|scope| scope.override_name() == Some("comment"));
4411 if !is_within_block_comment {
4412 return None;
4413 }
4414
4415 let (snapshot, range) =
4416 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4417
4418 let num_of_whitespaces = snapshot
4419 .chars_for_range(range.clone())
4420 .take_while(|c| c.is_whitespace())
4421 .count();
4422
4423 // 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.
4424 let column = start_point.column;
4425 let cursor_is_after_start_tag = {
4426 let start_tag_len = start_tag.len();
4427 let start_tag_line = snapshot
4428 .chars_for_range(range.clone())
4429 .skip(num_of_whitespaces)
4430 .take(start_tag_len)
4431 .collect::<String>();
4432 if start_tag_line.starts_with(start_tag.as_ref()) {
4433 num_of_whitespaces + start_tag_len <= column as usize
4434 } else {
4435 false
4436 }
4437 };
4438
4439 let cursor_is_after_delimiter = {
4440 let delimiter_trim = delimiter.trim_end();
4441 let delimiter_line = snapshot
4442 .chars_for_range(range.clone())
4443 .skip(num_of_whitespaces)
4444 .take(delimiter_trim.len())
4445 .collect::<String>();
4446 if delimiter_line.starts_with(delimiter_trim) {
4447 num_of_whitespaces + delimiter_trim.len() <= column as usize
4448 } else {
4449 false
4450 }
4451 };
4452
4453 let cursor_is_before_end_tag_if_exists = {
4454 let mut char_position = 0u32;
4455 let mut end_tag_offset = None;
4456
4457 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4458 if let Some(byte_pos) = chunk.find(&**end_tag) {
4459 let chars_before_match =
4460 chunk[..byte_pos].chars().count() as u32;
4461 end_tag_offset =
4462 Some(char_position + chars_before_match);
4463 break 'outer;
4464 }
4465 char_position += chunk.chars().count() as u32;
4466 }
4467
4468 if let Some(end_tag_offset) = end_tag_offset {
4469 let cursor_is_before_end_tag = column <= end_tag_offset;
4470 if cursor_is_after_start_tag {
4471 if cursor_is_before_end_tag {
4472 insert_extra_newline = true;
4473 }
4474 let cursor_is_at_start_of_end_tag =
4475 column == end_tag_offset;
4476 if cursor_is_at_start_of_end_tag {
4477 indent_on_extra_newline.len = (*len).into();
4478 }
4479 }
4480 cursor_is_before_end_tag
4481 } else {
4482 true
4483 }
4484 };
4485
4486 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4487 && cursor_is_before_end_tag_if_exists
4488 {
4489 if cursor_is_after_start_tag {
4490 indent_on_newline.len = (*len).into();
4491 }
4492 Some(delimiter.clone())
4493 } else {
4494 None
4495 }
4496 });
4497
4498 (
4499 comment_delimiter,
4500 doc_delimiter,
4501 insert_extra_newline,
4502 indent_on_newline,
4503 indent_on_extra_newline,
4504 )
4505 } else {
4506 (
4507 None,
4508 None,
4509 false,
4510 IndentSize::default(),
4511 IndentSize::default(),
4512 )
4513 };
4514
4515 let prevent_auto_indent = doc_delimiter.is_some();
4516 let delimiter = comment_delimiter.or(doc_delimiter);
4517
4518 let capacity_for_delimiter =
4519 delimiter.as_deref().map(str::len).unwrap_or_default();
4520 let mut new_text = String::with_capacity(
4521 1 + capacity_for_delimiter
4522 + existing_indent.len as usize
4523 + indent_on_newline.len as usize
4524 + indent_on_extra_newline.len as usize,
4525 );
4526 new_text.push('\n');
4527 new_text.extend(existing_indent.chars());
4528 new_text.extend(indent_on_newline.chars());
4529
4530 if let Some(delimiter) = &delimiter {
4531 new_text.push_str(delimiter);
4532 }
4533
4534 if insert_extra_newline {
4535 new_text.push('\n');
4536 new_text.extend(existing_indent.chars());
4537 new_text.extend(indent_on_extra_newline.chars());
4538 }
4539
4540 let anchor = buffer.anchor_after(end);
4541 let new_selection = selection.map(|_| anchor);
4542 (
4543 ((start..end, new_text), prevent_auto_indent),
4544 (insert_extra_newline, new_selection),
4545 )
4546 })
4547 .unzip()
4548 };
4549
4550 let mut auto_indent_edits = Vec::new();
4551 let mut edits = Vec::new();
4552 for (edit, prevent_auto_indent) in edits_with_flags {
4553 if prevent_auto_indent {
4554 edits.push(edit);
4555 } else {
4556 auto_indent_edits.push(edit);
4557 }
4558 }
4559 if !edits.is_empty() {
4560 this.edit(edits, cx);
4561 }
4562 if !auto_indent_edits.is_empty() {
4563 this.edit_with_autoindent(auto_indent_edits, cx);
4564 }
4565
4566 let buffer = this.buffer.read(cx).snapshot(cx);
4567 let new_selections = selection_info
4568 .into_iter()
4569 .map(|(extra_newline_inserted, new_selection)| {
4570 let mut cursor = new_selection.end.to_point(&buffer);
4571 if extra_newline_inserted {
4572 cursor.row -= 1;
4573 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4574 }
4575 new_selection.map(|_| cursor)
4576 })
4577 .collect();
4578
4579 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4580 this.refresh_inline_completion(true, false, window, cx);
4581 });
4582 }
4583
4584 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4585 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4586
4587 let buffer = self.buffer.read(cx);
4588 let snapshot = buffer.snapshot(cx);
4589
4590 let mut edits = Vec::new();
4591 let mut rows = Vec::new();
4592
4593 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4594 let cursor = selection.head();
4595 let row = cursor.row;
4596
4597 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4598
4599 let newline = "\n".to_string();
4600 edits.push((start_of_line..start_of_line, newline));
4601
4602 rows.push(row + rows_inserted as u32);
4603 }
4604
4605 self.transact(window, cx, |editor, window, cx| {
4606 editor.edit(edits, cx);
4607
4608 editor.change_selections(Default::default(), window, cx, |s| {
4609 let mut index = 0;
4610 s.move_cursors_with(|map, _, _| {
4611 let row = rows[index];
4612 index += 1;
4613
4614 let point = Point::new(row, 0);
4615 let boundary = map.next_line_boundary(point).1;
4616 let clipped = map.clip_point(boundary, Bias::Left);
4617
4618 (clipped, SelectionGoal::None)
4619 });
4620 });
4621
4622 let mut indent_edits = Vec::new();
4623 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4624 for row in rows {
4625 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4626 for (row, indent) in indents {
4627 if indent.len == 0 {
4628 continue;
4629 }
4630
4631 let text = match indent.kind {
4632 IndentKind::Space => " ".repeat(indent.len as usize),
4633 IndentKind::Tab => "\t".repeat(indent.len as usize),
4634 };
4635 let point = Point::new(row.0, 0);
4636 indent_edits.push((point..point, text));
4637 }
4638 }
4639 editor.edit(indent_edits, cx);
4640 });
4641 }
4642
4643 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4644 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4645
4646 let buffer = self.buffer.read(cx);
4647 let snapshot = buffer.snapshot(cx);
4648
4649 let mut edits = Vec::new();
4650 let mut rows = Vec::new();
4651 let mut rows_inserted = 0;
4652
4653 for selection in self.selections.all_adjusted(cx) {
4654 let cursor = selection.head();
4655 let row = cursor.row;
4656
4657 let point = Point::new(row + 1, 0);
4658 let start_of_line = snapshot.clip_point(point, Bias::Left);
4659
4660 let newline = "\n".to_string();
4661 edits.push((start_of_line..start_of_line, newline));
4662
4663 rows_inserted += 1;
4664 rows.push(row + rows_inserted);
4665 }
4666
4667 self.transact(window, cx, |editor, window, cx| {
4668 editor.edit(edits, cx);
4669
4670 editor.change_selections(Default::default(), window, cx, |s| {
4671 let mut index = 0;
4672 s.move_cursors_with(|map, _, _| {
4673 let row = rows[index];
4674 index += 1;
4675
4676 let point = Point::new(row, 0);
4677 let boundary = map.next_line_boundary(point).1;
4678 let clipped = map.clip_point(boundary, Bias::Left);
4679
4680 (clipped, SelectionGoal::None)
4681 });
4682 });
4683
4684 let mut indent_edits = Vec::new();
4685 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4686 for row in rows {
4687 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4688 for (row, indent) in indents {
4689 if indent.len == 0 {
4690 continue;
4691 }
4692
4693 let text = match indent.kind {
4694 IndentKind::Space => " ".repeat(indent.len as usize),
4695 IndentKind::Tab => "\t".repeat(indent.len as usize),
4696 };
4697 let point = Point::new(row.0, 0);
4698 indent_edits.push((point..point, text));
4699 }
4700 }
4701 editor.edit(indent_edits, cx);
4702 });
4703 }
4704
4705 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4706 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4707 original_indent_columns: Vec::new(),
4708 });
4709 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4710 }
4711
4712 fn insert_with_autoindent_mode(
4713 &mut self,
4714 text: &str,
4715 autoindent_mode: Option<AutoindentMode>,
4716 window: &mut Window,
4717 cx: &mut Context<Self>,
4718 ) {
4719 if self.read_only(cx) {
4720 return;
4721 }
4722
4723 let text: Arc<str> = text.into();
4724 self.transact(window, cx, |this, window, cx| {
4725 let old_selections = this.selections.all_adjusted(cx);
4726 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4727 let anchors = {
4728 let snapshot = buffer.read(cx);
4729 old_selections
4730 .iter()
4731 .map(|s| {
4732 let anchor = snapshot.anchor_after(s.head());
4733 s.map(|_| anchor)
4734 })
4735 .collect::<Vec<_>>()
4736 };
4737 buffer.edit(
4738 old_selections
4739 .iter()
4740 .map(|s| (s.start..s.end, text.clone())),
4741 autoindent_mode,
4742 cx,
4743 );
4744 anchors
4745 });
4746
4747 this.change_selections(Default::default(), window, cx, |s| {
4748 s.select_anchors(selection_anchors);
4749 });
4750
4751 cx.notify();
4752 });
4753 }
4754
4755 fn trigger_completion_on_input(
4756 &mut self,
4757 text: &str,
4758 trigger_in_words: bool,
4759 window: &mut Window,
4760 cx: &mut Context<Self>,
4761 ) {
4762 let completions_source = self
4763 .context_menu
4764 .borrow()
4765 .as_ref()
4766 .and_then(|menu| match menu {
4767 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4768 CodeContextMenu::CodeActions(_) => None,
4769 });
4770
4771 match completions_source {
4772 Some(CompletionsMenuSource::Words) => {
4773 self.show_word_completions(&ShowWordCompletions, window, cx)
4774 }
4775 Some(CompletionsMenuSource::Normal)
4776 | Some(CompletionsMenuSource::SnippetChoices)
4777 | None
4778 if self.is_completion_trigger(
4779 text,
4780 trigger_in_words,
4781 completions_source.is_some(),
4782 cx,
4783 ) =>
4784 {
4785 self.show_completions(
4786 &ShowCompletions {
4787 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4788 },
4789 window,
4790 cx,
4791 )
4792 }
4793 _ => {
4794 self.hide_context_menu(window, cx);
4795 }
4796 }
4797 }
4798
4799 fn is_completion_trigger(
4800 &self,
4801 text: &str,
4802 trigger_in_words: bool,
4803 menu_is_open: bool,
4804 cx: &mut Context<Self>,
4805 ) -> bool {
4806 let position = self.selections.newest_anchor().head();
4807 let multibuffer = self.buffer.read(cx);
4808 let Some(buffer) = position
4809 .buffer_id
4810 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4811 else {
4812 return false;
4813 };
4814
4815 if let Some(completion_provider) = &self.completion_provider {
4816 completion_provider.is_completion_trigger(
4817 &buffer,
4818 position.text_anchor,
4819 text,
4820 trigger_in_words,
4821 menu_is_open,
4822 cx,
4823 )
4824 } else {
4825 false
4826 }
4827 }
4828
4829 /// If any empty selections is touching the start of its innermost containing autoclose
4830 /// region, expand it to select the brackets.
4831 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4832 let selections = self.selections.all::<usize>(cx);
4833 let buffer = self.buffer.read(cx).read(cx);
4834 let new_selections = self
4835 .selections_with_autoclose_regions(selections, &buffer)
4836 .map(|(mut selection, region)| {
4837 if !selection.is_empty() {
4838 return selection;
4839 }
4840
4841 if let Some(region) = region {
4842 let mut range = region.range.to_offset(&buffer);
4843 if selection.start == range.start && range.start >= region.pair.start.len() {
4844 range.start -= region.pair.start.len();
4845 if buffer.contains_str_at(range.start, ®ion.pair.start)
4846 && buffer.contains_str_at(range.end, ®ion.pair.end)
4847 {
4848 range.end += region.pair.end.len();
4849 selection.start = range.start;
4850 selection.end = range.end;
4851
4852 return selection;
4853 }
4854 }
4855 }
4856
4857 let always_treat_brackets_as_autoclosed = buffer
4858 .language_settings_at(selection.start, cx)
4859 .always_treat_brackets_as_autoclosed;
4860
4861 if !always_treat_brackets_as_autoclosed {
4862 return selection;
4863 }
4864
4865 if let Some(scope) = buffer.language_scope_at(selection.start) {
4866 for (pair, enabled) in scope.brackets() {
4867 if !enabled || !pair.close {
4868 continue;
4869 }
4870
4871 if buffer.contains_str_at(selection.start, &pair.end) {
4872 let pair_start_len = pair.start.len();
4873 if buffer.contains_str_at(
4874 selection.start.saturating_sub(pair_start_len),
4875 &pair.start,
4876 ) {
4877 selection.start -= pair_start_len;
4878 selection.end += pair.end.len();
4879
4880 return selection;
4881 }
4882 }
4883 }
4884 }
4885
4886 selection
4887 })
4888 .collect();
4889
4890 drop(buffer);
4891 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
4892 selections.select(new_selections)
4893 });
4894 }
4895
4896 /// Iterate the given selections, and for each one, find the smallest surrounding
4897 /// autoclose region. This uses the ordering of the selections and the autoclose
4898 /// regions to avoid repeated comparisons.
4899 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4900 &'a self,
4901 selections: impl IntoIterator<Item = Selection<D>>,
4902 buffer: &'a MultiBufferSnapshot,
4903 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4904 let mut i = 0;
4905 let mut regions = self.autoclose_regions.as_slice();
4906 selections.into_iter().map(move |selection| {
4907 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4908
4909 let mut enclosing = None;
4910 while let Some(pair_state) = regions.get(i) {
4911 if pair_state.range.end.to_offset(buffer) < range.start {
4912 regions = ®ions[i + 1..];
4913 i = 0;
4914 } else if pair_state.range.start.to_offset(buffer) > range.end {
4915 break;
4916 } else {
4917 if pair_state.selection_id == selection.id {
4918 enclosing = Some(pair_state);
4919 }
4920 i += 1;
4921 }
4922 }
4923
4924 (selection, enclosing)
4925 })
4926 }
4927
4928 /// Remove any autoclose regions that no longer contain their selection.
4929 fn invalidate_autoclose_regions(
4930 &mut self,
4931 mut selections: &[Selection<Anchor>],
4932 buffer: &MultiBufferSnapshot,
4933 ) {
4934 self.autoclose_regions.retain(|state| {
4935 let mut i = 0;
4936 while let Some(selection) = selections.get(i) {
4937 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4938 selections = &selections[1..];
4939 continue;
4940 }
4941 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4942 break;
4943 }
4944 if selection.id == state.selection_id {
4945 return true;
4946 } else {
4947 i += 1;
4948 }
4949 }
4950 false
4951 });
4952 }
4953
4954 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4955 let offset = position.to_offset(buffer);
4956 let (word_range, kind) = buffer.surrounding_word(offset, true);
4957 if offset > word_range.start && kind == Some(CharKind::Word) {
4958 Some(
4959 buffer
4960 .text_for_range(word_range.start..offset)
4961 .collect::<String>(),
4962 )
4963 } else {
4964 None
4965 }
4966 }
4967
4968 pub fn toggle_inline_values(
4969 &mut self,
4970 _: &ToggleInlineValues,
4971 _: &mut Window,
4972 cx: &mut Context<Self>,
4973 ) {
4974 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4975
4976 self.refresh_inline_values(cx);
4977 }
4978
4979 pub fn toggle_inlay_hints(
4980 &mut self,
4981 _: &ToggleInlayHints,
4982 _: &mut Window,
4983 cx: &mut Context<Self>,
4984 ) {
4985 self.refresh_inlay_hints(
4986 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4987 cx,
4988 );
4989 }
4990
4991 pub fn inlay_hints_enabled(&self) -> bool {
4992 self.inlay_hint_cache.enabled
4993 }
4994
4995 pub fn inline_values_enabled(&self) -> bool {
4996 self.inline_value_cache.enabled
4997 }
4998
4999 #[cfg(any(test, feature = "test-support"))]
5000 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5001 self.display_map
5002 .read(cx)
5003 .current_inlays()
5004 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5005 .cloned()
5006 .collect()
5007 }
5008
5009 #[cfg(any(test, feature = "test-support"))]
5010 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5011 self.display_map
5012 .read(cx)
5013 .current_inlays()
5014 .cloned()
5015 .collect()
5016 }
5017
5018 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5019 if self.semantics_provider.is_none() || !self.mode.is_full() {
5020 return;
5021 }
5022
5023 let reason_description = reason.description();
5024 let ignore_debounce = matches!(
5025 reason,
5026 InlayHintRefreshReason::SettingsChange(_)
5027 | InlayHintRefreshReason::Toggle(_)
5028 | InlayHintRefreshReason::ExcerptsRemoved(_)
5029 | InlayHintRefreshReason::ModifiersChanged(_)
5030 );
5031 let (invalidate_cache, required_languages) = match reason {
5032 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5033 match self.inlay_hint_cache.modifiers_override(enabled) {
5034 Some(enabled) => {
5035 if enabled {
5036 (InvalidationStrategy::RefreshRequested, None)
5037 } else {
5038 self.splice_inlays(
5039 &self
5040 .visible_inlay_hints(cx)
5041 .iter()
5042 .map(|inlay| inlay.id)
5043 .collect::<Vec<InlayId>>(),
5044 Vec::new(),
5045 cx,
5046 );
5047 return;
5048 }
5049 }
5050 None => return,
5051 }
5052 }
5053 InlayHintRefreshReason::Toggle(enabled) => {
5054 if self.inlay_hint_cache.toggle(enabled) {
5055 if enabled {
5056 (InvalidationStrategy::RefreshRequested, None)
5057 } else {
5058 self.splice_inlays(
5059 &self
5060 .visible_inlay_hints(cx)
5061 .iter()
5062 .map(|inlay| inlay.id)
5063 .collect::<Vec<InlayId>>(),
5064 Vec::new(),
5065 cx,
5066 );
5067 return;
5068 }
5069 } else {
5070 return;
5071 }
5072 }
5073 InlayHintRefreshReason::SettingsChange(new_settings) => {
5074 match self.inlay_hint_cache.update_settings(
5075 &self.buffer,
5076 new_settings,
5077 self.visible_inlay_hints(cx),
5078 cx,
5079 ) {
5080 ControlFlow::Break(Some(InlaySplice {
5081 to_remove,
5082 to_insert,
5083 })) => {
5084 self.splice_inlays(&to_remove, to_insert, cx);
5085 return;
5086 }
5087 ControlFlow::Break(None) => return,
5088 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5089 }
5090 }
5091 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5092 if let Some(InlaySplice {
5093 to_remove,
5094 to_insert,
5095 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5096 {
5097 self.splice_inlays(&to_remove, to_insert, cx);
5098 }
5099 self.display_map.update(cx, |display_map, _| {
5100 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5101 });
5102 return;
5103 }
5104 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5105 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5106 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5107 }
5108 InlayHintRefreshReason::RefreshRequested => {
5109 (InvalidationStrategy::RefreshRequested, None)
5110 }
5111 };
5112
5113 if let Some(InlaySplice {
5114 to_remove,
5115 to_insert,
5116 }) = self.inlay_hint_cache.spawn_hint_refresh(
5117 reason_description,
5118 self.visible_excerpts(required_languages.as_ref(), cx),
5119 invalidate_cache,
5120 ignore_debounce,
5121 cx,
5122 ) {
5123 self.splice_inlays(&to_remove, to_insert, cx);
5124 }
5125 }
5126
5127 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5128 self.display_map
5129 .read(cx)
5130 .current_inlays()
5131 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5132 .cloned()
5133 .collect()
5134 }
5135
5136 pub fn visible_excerpts(
5137 &self,
5138 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5139 cx: &mut Context<Editor>,
5140 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5141 let Some(project) = self.project.as_ref() else {
5142 return HashMap::default();
5143 };
5144 let project = project.read(cx);
5145 let multi_buffer = self.buffer().read(cx);
5146 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5147 let multi_buffer_visible_start = self
5148 .scroll_manager
5149 .anchor()
5150 .anchor
5151 .to_point(&multi_buffer_snapshot);
5152 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5153 multi_buffer_visible_start
5154 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5155 Bias::Left,
5156 );
5157 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5158 multi_buffer_snapshot
5159 .range_to_buffer_ranges(multi_buffer_visible_range)
5160 .into_iter()
5161 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5162 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5163 let buffer_file = project::File::from_dyn(buffer.file())?;
5164 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5165 let worktree_entry = buffer_worktree
5166 .read(cx)
5167 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5168 if worktree_entry.is_ignored {
5169 return None;
5170 }
5171
5172 let language = buffer.language()?;
5173 if let Some(restrict_to_languages) = restrict_to_languages {
5174 if !restrict_to_languages.contains(language) {
5175 return None;
5176 }
5177 }
5178 Some((
5179 excerpt_id,
5180 (
5181 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5182 buffer.version().clone(),
5183 excerpt_visible_range,
5184 ),
5185 ))
5186 })
5187 .collect()
5188 }
5189
5190 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5191 TextLayoutDetails {
5192 text_system: window.text_system().clone(),
5193 editor_style: self.style.clone().unwrap(),
5194 rem_size: window.rem_size(),
5195 scroll_anchor: self.scroll_manager.anchor(),
5196 visible_rows: self.visible_line_count(),
5197 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5198 }
5199 }
5200
5201 pub fn splice_inlays(
5202 &self,
5203 to_remove: &[InlayId],
5204 to_insert: Vec<Inlay>,
5205 cx: &mut Context<Self>,
5206 ) {
5207 self.display_map.update(cx, |display_map, cx| {
5208 display_map.splice_inlays(to_remove, to_insert, cx)
5209 });
5210 cx.notify();
5211 }
5212
5213 fn trigger_on_type_formatting(
5214 &self,
5215 input: String,
5216 window: &mut Window,
5217 cx: &mut Context<Self>,
5218 ) -> Option<Task<Result<()>>> {
5219 if input.len() != 1 {
5220 return None;
5221 }
5222
5223 let project = self.project.as_ref()?;
5224 let position = self.selections.newest_anchor().head();
5225 let (buffer, buffer_position) = self
5226 .buffer
5227 .read(cx)
5228 .text_anchor_for_position(position, cx)?;
5229
5230 let settings = language_settings::language_settings(
5231 buffer
5232 .read(cx)
5233 .language_at(buffer_position)
5234 .map(|l| l.name()),
5235 buffer.read(cx).file(),
5236 cx,
5237 );
5238 if !settings.use_on_type_format {
5239 return None;
5240 }
5241
5242 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5243 // hence we do LSP request & edit on host side only — add formats to host's history.
5244 let push_to_lsp_host_history = true;
5245 // If this is not the host, append its history with new edits.
5246 let push_to_client_history = project.read(cx).is_via_collab();
5247
5248 let on_type_formatting = project.update(cx, |project, cx| {
5249 project.on_type_format(
5250 buffer.clone(),
5251 buffer_position,
5252 input,
5253 push_to_lsp_host_history,
5254 cx,
5255 )
5256 });
5257 Some(cx.spawn_in(window, async move |editor, cx| {
5258 if let Some(transaction) = on_type_formatting.await? {
5259 if push_to_client_history {
5260 buffer
5261 .update(cx, |buffer, _| {
5262 buffer.push_transaction(transaction, Instant::now());
5263 buffer.finalize_last_transaction();
5264 })
5265 .ok();
5266 }
5267 editor.update(cx, |editor, cx| {
5268 editor.refresh_document_highlights(cx);
5269 })?;
5270 }
5271 Ok(())
5272 }))
5273 }
5274
5275 pub fn show_word_completions(
5276 &mut self,
5277 _: &ShowWordCompletions,
5278 window: &mut Window,
5279 cx: &mut Context<Self>,
5280 ) {
5281 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5282 }
5283
5284 pub fn show_completions(
5285 &mut self,
5286 options: &ShowCompletions,
5287 window: &mut Window,
5288 cx: &mut Context<Self>,
5289 ) {
5290 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5291 }
5292
5293 fn open_or_update_completions_menu(
5294 &mut self,
5295 requested_source: Option<CompletionsMenuSource>,
5296 trigger: Option<&str>,
5297 window: &mut Window,
5298 cx: &mut Context<Self>,
5299 ) {
5300 if self.pending_rename.is_some() {
5301 return;
5302 }
5303
5304 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5305
5306 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5307 // inserted and selected. To handle that case, the start of the selection is used so that
5308 // the menu starts with all choices.
5309 let position = self
5310 .selections
5311 .newest_anchor()
5312 .start
5313 .bias_right(&multibuffer_snapshot);
5314 if position.diff_base_anchor.is_some() {
5315 return;
5316 }
5317 let (buffer, buffer_position) =
5318 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5319 output
5320 } else {
5321 return;
5322 };
5323 let buffer_snapshot = buffer.read(cx).snapshot();
5324
5325 let query: Option<Arc<String>> =
5326 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5327
5328 drop(multibuffer_snapshot);
5329
5330 let provider = match requested_source {
5331 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5332 Some(CompletionsMenuSource::Words) => None,
5333 Some(CompletionsMenuSource::SnippetChoices) => {
5334 log::error!("bug: SnippetChoices requested_source is not handled");
5335 None
5336 }
5337 };
5338
5339 let sort_completions = provider
5340 .as_ref()
5341 .map_or(false, |provider| provider.sort_completions());
5342
5343 let filter_completions = provider
5344 .as_ref()
5345 .map_or(true, |provider| provider.filter_completions());
5346
5347 let trigger_kind = match trigger {
5348 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5349 CompletionTriggerKind::TRIGGER_CHARACTER
5350 }
5351 _ => CompletionTriggerKind::INVOKED,
5352 };
5353 let completion_context = CompletionContext {
5354 trigger_character: trigger.and_then(|trigger| {
5355 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5356 Some(String::from(trigger))
5357 } else {
5358 None
5359 }
5360 }),
5361 trigger_kind,
5362 };
5363
5364 // Hide the current completions menu when a trigger char is typed. Without this, cached
5365 // completions from before the trigger char may be reused (#32774). Snippet choices could
5366 // involve trigger chars, so this is skipped in that case.
5367 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5368 {
5369 let menu_is_open = matches!(
5370 self.context_menu.borrow().as_ref(),
5371 Some(CodeContextMenu::Completions(_))
5372 );
5373 if menu_is_open {
5374 self.hide_context_menu(window, cx);
5375 }
5376 }
5377
5378 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5379 if filter_completions {
5380 menu.filter(query.clone(), provider.clone(), window, cx);
5381 }
5382 // When `is_incomplete` is false, no need to re-query completions when the current query
5383 // is a suffix of the initial query.
5384 if !menu.is_incomplete {
5385 // If the new query is a suffix of the old query (typing more characters) and
5386 // the previous result was complete, the existing completions can be filtered.
5387 //
5388 // Note that this is always true for snippet completions.
5389 let query_matches = match (&menu.initial_query, &query) {
5390 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5391 (None, _) => true,
5392 _ => false,
5393 };
5394 if query_matches {
5395 let position_matches = if menu.initial_position == position {
5396 true
5397 } else {
5398 let snapshot = self.buffer.read(cx).read(cx);
5399 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5400 };
5401 if position_matches {
5402 return;
5403 }
5404 }
5405 }
5406 };
5407
5408 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5409 buffer_snapshot.surrounding_word(buffer_position)
5410 {
5411 let word_to_exclude = buffer_snapshot
5412 .text_for_range(word_range.clone())
5413 .collect::<String>();
5414 (
5415 buffer_snapshot.anchor_before(word_range.start)
5416 ..buffer_snapshot.anchor_after(buffer_position),
5417 Some(word_to_exclude),
5418 )
5419 } else {
5420 (buffer_position..buffer_position, None)
5421 };
5422
5423 let language = buffer_snapshot
5424 .language_at(buffer_position)
5425 .map(|language| language.name());
5426
5427 let completion_settings =
5428 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5429
5430 let show_completion_documentation = buffer_snapshot
5431 .settings_at(buffer_position, cx)
5432 .show_completion_documentation;
5433
5434 // The document can be large, so stay in reasonable bounds when searching for words,
5435 // otherwise completion pop-up might be slow to appear.
5436 const WORD_LOOKUP_ROWS: u32 = 5_000;
5437 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5438 let min_word_search = buffer_snapshot.clip_point(
5439 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5440 Bias::Left,
5441 );
5442 let max_word_search = buffer_snapshot.clip_point(
5443 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5444 Bias::Right,
5445 );
5446 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5447 ..buffer_snapshot.point_to_offset(max_word_search);
5448
5449 let skip_digits = query
5450 .as_ref()
5451 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5452
5453 let (mut words, provider_responses) = match &provider {
5454 Some(provider) => {
5455 let provider_responses = provider.completions(
5456 position.excerpt_id,
5457 &buffer,
5458 buffer_position,
5459 completion_context,
5460 window,
5461 cx,
5462 );
5463
5464 let words = match completion_settings.words {
5465 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5466 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5467 .background_spawn(async move {
5468 buffer_snapshot.words_in_range(WordsQuery {
5469 fuzzy_contents: None,
5470 range: word_search_range,
5471 skip_digits,
5472 })
5473 }),
5474 };
5475
5476 (words, provider_responses)
5477 }
5478 None => (
5479 cx.background_spawn(async move {
5480 buffer_snapshot.words_in_range(WordsQuery {
5481 fuzzy_contents: None,
5482 range: word_search_range,
5483 skip_digits,
5484 })
5485 }),
5486 Task::ready(Ok(Vec::new())),
5487 ),
5488 };
5489
5490 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5491
5492 let id = post_inc(&mut self.next_completion_id);
5493 let task = cx.spawn_in(window, async move |editor, cx| {
5494 let Ok(()) = editor.update(cx, |this, _| {
5495 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5496 }) else {
5497 return;
5498 };
5499
5500 // TODO: Ideally completions from different sources would be selectively re-queried, so
5501 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5502 let mut completions = Vec::new();
5503 let mut is_incomplete = false;
5504 if let Some(provider_responses) = provider_responses.await.log_err() {
5505 if !provider_responses.is_empty() {
5506 for response in provider_responses {
5507 completions.extend(response.completions);
5508 is_incomplete = is_incomplete || response.is_incomplete;
5509 }
5510 if completion_settings.words == WordsCompletionMode::Fallback {
5511 words = Task::ready(BTreeMap::default());
5512 }
5513 }
5514 }
5515
5516 let mut words = words.await;
5517 if let Some(word_to_exclude) = &word_to_exclude {
5518 words.remove(word_to_exclude);
5519 }
5520 for lsp_completion in &completions {
5521 words.remove(&lsp_completion.new_text);
5522 }
5523 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5524 replace_range: word_replace_range.clone(),
5525 new_text: word.clone(),
5526 label: CodeLabel::plain(word, None),
5527 icon_path: None,
5528 documentation: None,
5529 source: CompletionSource::BufferWord {
5530 word_range,
5531 resolved: false,
5532 },
5533 insert_text_mode: Some(InsertTextMode::AS_IS),
5534 confirm: None,
5535 }));
5536
5537 let menu = if completions.is_empty() {
5538 None
5539 } else {
5540 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5541 let languages = editor
5542 .workspace
5543 .as_ref()
5544 .and_then(|(workspace, _)| workspace.upgrade())
5545 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5546 let menu = CompletionsMenu::new(
5547 id,
5548 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5549 sort_completions,
5550 show_completion_documentation,
5551 position,
5552 query.clone(),
5553 is_incomplete,
5554 buffer.clone(),
5555 completions.into(),
5556 snippet_sort_order,
5557 languages,
5558 language,
5559 cx,
5560 );
5561
5562 let query = if filter_completions { query } else { None };
5563 let matches_task = if let Some(query) = query {
5564 menu.do_async_filtering(query, cx)
5565 } else {
5566 Task::ready(menu.unfiltered_matches())
5567 };
5568 (menu, matches_task)
5569 }) else {
5570 return;
5571 };
5572
5573 let matches = matches_task.await;
5574
5575 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5576 // Newer menu already set, so exit.
5577 match editor.context_menu.borrow().as_ref() {
5578 Some(CodeContextMenu::Completions(prev_menu)) => {
5579 if prev_menu.id > id {
5580 return;
5581 }
5582 }
5583 _ => {}
5584 };
5585
5586 // Only valid to take prev_menu because it the new menu is immediately set
5587 // below, or the menu is hidden.
5588 match editor.context_menu.borrow_mut().take() {
5589 Some(CodeContextMenu::Completions(prev_menu)) => {
5590 let position_matches =
5591 if prev_menu.initial_position == menu.initial_position {
5592 true
5593 } else {
5594 let snapshot = editor.buffer.read(cx).read(cx);
5595 prev_menu.initial_position.to_offset(&snapshot)
5596 == menu.initial_position.to_offset(&snapshot)
5597 };
5598 if position_matches {
5599 // Preserve markdown cache before `set_filter_results` because it will
5600 // try to populate the documentation cache.
5601 menu.preserve_markdown_cache(prev_menu);
5602 }
5603 }
5604 _ => {}
5605 };
5606
5607 menu.set_filter_results(matches, provider, window, cx);
5608 }) else {
5609 return;
5610 };
5611
5612 menu.visible().then_some(menu)
5613 };
5614
5615 editor
5616 .update_in(cx, |editor, window, cx| {
5617 if editor.focus_handle.is_focused(window) {
5618 if let Some(menu) = menu {
5619 *editor.context_menu.borrow_mut() =
5620 Some(CodeContextMenu::Completions(menu));
5621
5622 crate::hover_popover::hide_hover(editor, cx);
5623 if editor.show_edit_predictions_in_menu() {
5624 editor.update_visible_inline_completion(window, cx);
5625 } else {
5626 editor.discard_inline_completion(false, cx);
5627 }
5628
5629 cx.notify();
5630 return;
5631 }
5632 }
5633
5634 if editor.completion_tasks.len() <= 1 {
5635 // If there are no more completion tasks and the last menu was empty, we should hide it.
5636 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5637 // If it was already hidden and we don't show inline completions in the menu, we should
5638 // also show the inline-completion when available.
5639 if was_hidden && editor.show_edit_predictions_in_menu() {
5640 editor.update_visible_inline_completion(window, cx);
5641 }
5642 }
5643 })
5644 .ok();
5645 });
5646
5647 self.completion_tasks.push((id, task));
5648 }
5649
5650 #[cfg(feature = "test-support")]
5651 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5652 let menu = self.context_menu.borrow();
5653 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5654 let completions = menu.completions.borrow();
5655 Some(completions.to_vec())
5656 } else {
5657 None
5658 }
5659 }
5660
5661 pub fn with_completions_menu_matching_id<R>(
5662 &self,
5663 id: CompletionId,
5664 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5665 ) -> R {
5666 let mut context_menu = self.context_menu.borrow_mut();
5667 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5668 return f(None);
5669 };
5670 if completions_menu.id != id {
5671 return f(None);
5672 }
5673 f(Some(completions_menu))
5674 }
5675
5676 pub fn confirm_completion(
5677 &mut self,
5678 action: &ConfirmCompletion,
5679 window: &mut Window,
5680 cx: &mut Context<Self>,
5681 ) -> Option<Task<Result<()>>> {
5682 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5683 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5684 }
5685
5686 pub fn confirm_completion_insert(
5687 &mut self,
5688 _: &ConfirmCompletionInsert,
5689 window: &mut Window,
5690 cx: &mut Context<Self>,
5691 ) -> Option<Task<Result<()>>> {
5692 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5693 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5694 }
5695
5696 pub fn confirm_completion_replace(
5697 &mut self,
5698 _: &ConfirmCompletionReplace,
5699 window: &mut Window,
5700 cx: &mut Context<Self>,
5701 ) -> Option<Task<Result<()>>> {
5702 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5703 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5704 }
5705
5706 pub fn compose_completion(
5707 &mut self,
5708 action: &ComposeCompletion,
5709 window: &mut Window,
5710 cx: &mut Context<Self>,
5711 ) -> Option<Task<Result<()>>> {
5712 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5713 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5714 }
5715
5716 fn do_completion(
5717 &mut self,
5718 item_ix: Option<usize>,
5719 intent: CompletionIntent,
5720 window: &mut Window,
5721 cx: &mut Context<Editor>,
5722 ) -> Option<Task<Result<()>>> {
5723 use language::ToOffset as _;
5724
5725 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5726 else {
5727 return None;
5728 };
5729
5730 let candidate_id = {
5731 let entries = completions_menu.entries.borrow();
5732 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5733 if self.show_edit_predictions_in_menu() {
5734 self.discard_inline_completion(true, cx);
5735 }
5736 mat.candidate_id
5737 };
5738
5739 let completion = completions_menu
5740 .completions
5741 .borrow()
5742 .get(candidate_id)?
5743 .clone();
5744 cx.stop_propagation();
5745
5746 let buffer_handle = completions_menu.buffer.clone();
5747
5748 let CompletionEdit {
5749 new_text,
5750 snippet,
5751 replace_range,
5752 } = process_completion_for_edit(
5753 &completion,
5754 intent,
5755 &buffer_handle,
5756 &completions_menu.initial_position.text_anchor,
5757 cx,
5758 );
5759
5760 let buffer = buffer_handle.read(cx);
5761 let snapshot = self.buffer.read(cx).snapshot(cx);
5762 let newest_anchor = self.selections.newest_anchor();
5763 let replace_range_multibuffer = {
5764 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5765 let multibuffer_anchor = snapshot
5766 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5767 .unwrap()
5768 ..snapshot
5769 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5770 .unwrap();
5771 multibuffer_anchor.start.to_offset(&snapshot)
5772 ..multibuffer_anchor.end.to_offset(&snapshot)
5773 };
5774 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5775 return None;
5776 }
5777
5778 let old_text = buffer
5779 .text_for_range(replace_range.clone())
5780 .collect::<String>();
5781 let lookbehind = newest_anchor
5782 .start
5783 .text_anchor
5784 .to_offset(buffer)
5785 .saturating_sub(replace_range.start);
5786 let lookahead = replace_range
5787 .end
5788 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5789 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5790 let suffix = &old_text[lookbehind.min(old_text.len())..];
5791
5792 let selections = self.selections.all::<usize>(cx);
5793 let mut ranges = Vec::new();
5794 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5795
5796 for selection in &selections {
5797 let range = if selection.id == newest_anchor.id {
5798 replace_range_multibuffer.clone()
5799 } else {
5800 let mut range = selection.range();
5801
5802 // if prefix is present, don't duplicate it
5803 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5804 range.start = range.start.saturating_sub(lookbehind);
5805
5806 // if suffix is also present, mimic the newest cursor and replace it
5807 if selection.id != newest_anchor.id
5808 && snapshot.contains_str_at(range.end, suffix)
5809 {
5810 range.end += lookahead;
5811 }
5812 }
5813 range
5814 };
5815
5816 ranges.push(range.clone());
5817
5818 if !self.linked_edit_ranges.is_empty() {
5819 let start_anchor = snapshot.anchor_before(range.start);
5820 let end_anchor = snapshot.anchor_after(range.end);
5821 if let Some(ranges) = self
5822 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5823 {
5824 for (buffer, edits) in ranges {
5825 linked_edits
5826 .entry(buffer.clone())
5827 .or_default()
5828 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5829 }
5830 }
5831 }
5832 }
5833
5834 let common_prefix_len = old_text
5835 .chars()
5836 .zip(new_text.chars())
5837 .take_while(|(a, b)| a == b)
5838 .map(|(a, _)| a.len_utf8())
5839 .sum::<usize>();
5840
5841 cx.emit(EditorEvent::InputHandled {
5842 utf16_range_to_replace: None,
5843 text: new_text[common_prefix_len..].into(),
5844 });
5845
5846 self.transact(window, cx, |this, window, cx| {
5847 if let Some(mut snippet) = snippet {
5848 snippet.text = new_text.to_string();
5849 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5850 } else {
5851 this.buffer.update(cx, |buffer, cx| {
5852 let auto_indent = match completion.insert_text_mode {
5853 Some(InsertTextMode::AS_IS) => None,
5854 _ => this.autoindent_mode.clone(),
5855 };
5856 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5857 buffer.edit(edits, auto_indent, cx);
5858 });
5859 }
5860 for (buffer, edits) in linked_edits {
5861 buffer.update(cx, |buffer, cx| {
5862 let snapshot = buffer.snapshot();
5863 let edits = edits
5864 .into_iter()
5865 .map(|(range, text)| {
5866 use text::ToPoint as TP;
5867 let end_point = TP::to_point(&range.end, &snapshot);
5868 let start_point = TP::to_point(&range.start, &snapshot);
5869 (start_point..end_point, text)
5870 })
5871 .sorted_by_key(|(range, _)| range.start);
5872 buffer.edit(edits, None, cx);
5873 })
5874 }
5875
5876 this.refresh_inline_completion(true, false, window, cx);
5877 });
5878
5879 let show_new_completions_on_confirm = completion
5880 .confirm
5881 .as_ref()
5882 .map_or(false, |confirm| confirm(intent, window, cx));
5883 if show_new_completions_on_confirm {
5884 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5885 }
5886
5887 let provider = self.completion_provider.as_ref()?;
5888 drop(completion);
5889 let apply_edits = provider.apply_additional_edits_for_completion(
5890 buffer_handle,
5891 completions_menu.completions.clone(),
5892 candidate_id,
5893 true,
5894 cx,
5895 );
5896
5897 let editor_settings = EditorSettings::get_global(cx);
5898 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5899 // After the code completion is finished, users often want to know what signatures are needed.
5900 // so we should automatically call signature_help
5901 self.show_signature_help(&ShowSignatureHelp, window, cx);
5902 }
5903
5904 Some(cx.foreground_executor().spawn(async move {
5905 apply_edits.await?;
5906 Ok(())
5907 }))
5908 }
5909
5910 pub fn toggle_code_actions(
5911 &mut self,
5912 action: &ToggleCodeActions,
5913 window: &mut Window,
5914 cx: &mut Context<Self>,
5915 ) {
5916 let quick_launch = action.quick_launch;
5917 let mut context_menu = self.context_menu.borrow_mut();
5918 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5919 if code_actions.deployed_from == action.deployed_from {
5920 // Toggle if we're selecting the same one
5921 *context_menu = None;
5922 cx.notify();
5923 return;
5924 } else {
5925 // Otherwise, clear it and start a new one
5926 *context_menu = None;
5927 cx.notify();
5928 }
5929 }
5930 drop(context_menu);
5931 let snapshot = self.snapshot(window, cx);
5932 let deployed_from = action.deployed_from.clone();
5933 let action = action.clone();
5934 self.completion_tasks.clear();
5935 self.discard_inline_completion(false, cx);
5936
5937 let multibuffer_point = match &action.deployed_from {
5938 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5939 DisplayPoint::new(*row, 0).to_point(&snapshot)
5940 }
5941 _ => self.selections.newest::<Point>(cx).head(),
5942 };
5943 let Some((buffer, buffer_row)) = snapshot
5944 .buffer_snapshot
5945 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5946 .and_then(|(buffer_snapshot, range)| {
5947 self.buffer()
5948 .read(cx)
5949 .buffer(buffer_snapshot.remote_id())
5950 .map(|buffer| (buffer, range.start.row))
5951 })
5952 else {
5953 return;
5954 };
5955 let buffer_id = buffer.read(cx).remote_id();
5956 let tasks = self
5957 .tasks
5958 .get(&(buffer_id, buffer_row))
5959 .map(|t| Arc::new(t.to_owned()));
5960
5961 if !self.focus_handle.is_focused(window) {
5962 return;
5963 }
5964 let project = self.project.clone();
5965
5966 let code_actions_task = match deployed_from {
5967 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
5968 _ => self.code_actions(buffer_row, window, cx),
5969 };
5970
5971 let runnable_task = match deployed_from {
5972 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
5973 _ => {
5974 let mut task_context_task = Task::ready(None);
5975 if let Some(tasks) = &tasks {
5976 if let Some(project) = project {
5977 task_context_task =
5978 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
5979 }
5980 }
5981
5982 cx.spawn_in(window, {
5983 let buffer = buffer.clone();
5984 async move |editor, cx| {
5985 let task_context = task_context_task.await;
5986
5987 let resolved_tasks =
5988 tasks
5989 .zip(task_context.clone())
5990 .map(|(tasks, task_context)| ResolvedTasks {
5991 templates: tasks.resolve(&task_context).collect(),
5992 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5993 multibuffer_point.row,
5994 tasks.column,
5995 )),
5996 });
5997 let debug_scenarios = editor
5998 .update(cx, |editor, cx| {
5999 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6000 })?
6001 .await;
6002 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6003 }
6004 })
6005 }
6006 };
6007
6008 cx.spawn_in(window, async move |editor, cx| {
6009 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6010 let code_actions = code_actions_task.await;
6011 let spawn_straight_away = quick_launch
6012 && resolved_tasks
6013 .as_ref()
6014 .map_or(false, |tasks| tasks.templates.len() == 1)
6015 && code_actions
6016 .as_ref()
6017 .map_or(true, |actions| actions.is_empty())
6018 && debug_scenarios.is_empty();
6019
6020 editor.update_in(cx, |editor, window, cx| {
6021 crate::hover_popover::hide_hover(editor, cx);
6022 let actions = CodeActionContents::new(
6023 resolved_tasks,
6024 code_actions,
6025 debug_scenarios,
6026 task_context.unwrap_or_default(),
6027 );
6028
6029 // Don't show the menu if there are no actions available
6030 if actions.is_empty() {
6031 cx.notify();
6032 return Task::ready(Ok(()));
6033 }
6034
6035 *editor.context_menu.borrow_mut() =
6036 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6037 buffer,
6038 actions,
6039 selected_item: Default::default(),
6040 scroll_handle: UniformListScrollHandle::default(),
6041 deployed_from,
6042 }));
6043 cx.notify();
6044 if spawn_straight_away {
6045 if let Some(task) = editor.confirm_code_action(
6046 &ConfirmCodeAction { item_ix: Some(0) },
6047 window,
6048 cx,
6049 ) {
6050 return task;
6051 }
6052 }
6053
6054 Task::ready(Ok(()))
6055 })
6056 })
6057 .detach_and_log_err(cx);
6058 }
6059
6060 fn debug_scenarios(
6061 &mut self,
6062 resolved_tasks: &Option<ResolvedTasks>,
6063 buffer: &Entity<Buffer>,
6064 cx: &mut App,
6065 ) -> Task<Vec<task::DebugScenario>> {
6066 maybe!({
6067 let project = self.project.as_ref()?;
6068 let dap_store = project.read(cx).dap_store();
6069 let mut scenarios = vec![];
6070 let resolved_tasks = resolved_tasks.as_ref()?;
6071 let buffer = buffer.read(cx);
6072 let language = buffer.language()?;
6073 let file = buffer.file();
6074 let debug_adapter = language_settings(language.name().into(), file, cx)
6075 .debuggers
6076 .first()
6077 .map(SharedString::from)
6078 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6079
6080 dap_store.update(cx, |dap_store, cx| {
6081 for (_, task) in &resolved_tasks.templates {
6082 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6083 task.original_task().clone(),
6084 debug_adapter.clone().into(),
6085 task.display_label().to_owned().into(),
6086 cx,
6087 );
6088 scenarios.push(maybe_scenario);
6089 }
6090 });
6091 Some(cx.background_spawn(async move {
6092 let scenarios = futures::future::join_all(scenarios)
6093 .await
6094 .into_iter()
6095 .flatten()
6096 .collect::<Vec<_>>();
6097 scenarios
6098 }))
6099 })
6100 .unwrap_or_else(|| Task::ready(vec![]))
6101 }
6102
6103 fn code_actions(
6104 &mut self,
6105 buffer_row: u32,
6106 window: &mut Window,
6107 cx: &mut Context<Self>,
6108 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6109 let mut task = self.code_actions_task.take();
6110 cx.spawn_in(window, async move |editor, cx| {
6111 while let Some(prev_task) = task {
6112 prev_task.await.log_err();
6113 task = editor
6114 .update(cx, |this, _| this.code_actions_task.take())
6115 .ok()?;
6116 }
6117
6118 editor
6119 .update(cx, |editor, cx| {
6120 editor
6121 .available_code_actions
6122 .clone()
6123 .and_then(|(location, code_actions)| {
6124 let snapshot = location.buffer.read(cx).snapshot();
6125 let point_range = location.range.to_point(&snapshot);
6126 let point_range = point_range.start.row..=point_range.end.row;
6127 if point_range.contains(&buffer_row) {
6128 Some(code_actions)
6129 } else {
6130 None
6131 }
6132 })
6133 })
6134 .ok()
6135 .flatten()
6136 })
6137 }
6138
6139 pub fn confirm_code_action(
6140 &mut self,
6141 action: &ConfirmCodeAction,
6142 window: &mut Window,
6143 cx: &mut Context<Self>,
6144 ) -> Option<Task<Result<()>>> {
6145 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6146
6147 let actions_menu =
6148 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6149 menu
6150 } else {
6151 return None;
6152 };
6153
6154 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6155 let action = actions_menu.actions.get(action_ix)?;
6156 let title = action.label();
6157 let buffer = actions_menu.buffer;
6158 let workspace = self.workspace()?;
6159
6160 match action {
6161 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6162 workspace.update(cx, |workspace, cx| {
6163 workspace.schedule_resolved_task(
6164 task_source_kind,
6165 resolved_task,
6166 false,
6167 window,
6168 cx,
6169 );
6170
6171 Some(Task::ready(Ok(())))
6172 })
6173 }
6174 CodeActionsItem::CodeAction {
6175 excerpt_id,
6176 action,
6177 provider,
6178 } => {
6179 let apply_code_action =
6180 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6181 let workspace = workspace.downgrade();
6182 Some(cx.spawn_in(window, async move |editor, cx| {
6183 let project_transaction = apply_code_action.await?;
6184 Self::open_project_transaction(
6185 &editor,
6186 workspace,
6187 project_transaction,
6188 title,
6189 cx,
6190 )
6191 .await
6192 }))
6193 }
6194 CodeActionsItem::DebugScenario(scenario) => {
6195 let context = actions_menu.actions.context.clone();
6196
6197 workspace.update(cx, |workspace, cx| {
6198 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6199 workspace.start_debug_session(
6200 scenario,
6201 context,
6202 Some(buffer),
6203 None,
6204 window,
6205 cx,
6206 );
6207 });
6208 Some(Task::ready(Ok(())))
6209 }
6210 }
6211 }
6212
6213 pub async fn open_project_transaction(
6214 this: &WeakEntity<Editor>,
6215 workspace: WeakEntity<Workspace>,
6216 transaction: ProjectTransaction,
6217 title: String,
6218 cx: &mut AsyncWindowContext,
6219 ) -> Result<()> {
6220 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6221 cx.update(|_, cx| {
6222 entries.sort_unstable_by_key(|(buffer, _)| {
6223 buffer.read(cx).file().map(|f| f.path().clone())
6224 });
6225 })?;
6226
6227 // If the project transaction's edits are all contained within this editor, then
6228 // avoid opening a new editor to display them.
6229
6230 if let Some((buffer, transaction)) = entries.first() {
6231 if entries.len() == 1 {
6232 let excerpt = this.update(cx, |editor, cx| {
6233 editor
6234 .buffer()
6235 .read(cx)
6236 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6237 })?;
6238 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6239 if excerpted_buffer == *buffer {
6240 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6241 let excerpt_range = excerpt_range.to_offset(buffer);
6242 buffer
6243 .edited_ranges_for_transaction::<usize>(transaction)
6244 .all(|range| {
6245 excerpt_range.start <= range.start
6246 && excerpt_range.end >= range.end
6247 })
6248 })?;
6249
6250 if all_edits_within_excerpt {
6251 return Ok(());
6252 }
6253 }
6254 }
6255 }
6256 } else {
6257 return Ok(());
6258 }
6259
6260 let mut ranges_to_highlight = Vec::new();
6261 let excerpt_buffer = cx.new(|cx| {
6262 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6263 for (buffer_handle, transaction) in &entries {
6264 let edited_ranges = buffer_handle
6265 .read(cx)
6266 .edited_ranges_for_transaction::<Point>(transaction)
6267 .collect::<Vec<_>>();
6268 let (ranges, _) = multibuffer.set_excerpts_for_path(
6269 PathKey::for_buffer(buffer_handle, cx),
6270 buffer_handle.clone(),
6271 edited_ranges,
6272 DEFAULT_MULTIBUFFER_CONTEXT,
6273 cx,
6274 );
6275
6276 ranges_to_highlight.extend(ranges);
6277 }
6278 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6279 multibuffer
6280 })?;
6281
6282 workspace.update_in(cx, |workspace, window, cx| {
6283 let project = workspace.project().clone();
6284 let editor =
6285 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6286 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6287 editor.update(cx, |editor, cx| {
6288 editor.highlight_background::<Self>(
6289 &ranges_to_highlight,
6290 |theme| theme.colors().editor_highlighted_line_background,
6291 cx,
6292 );
6293 });
6294 })?;
6295
6296 Ok(())
6297 }
6298
6299 pub fn clear_code_action_providers(&mut self) {
6300 self.code_action_providers.clear();
6301 self.available_code_actions.take();
6302 }
6303
6304 pub fn add_code_action_provider(
6305 &mut self,
6306 provider: Rc<dyn CodeActionProvider>,
6307 window: &mut Window,
6308 cx: &mut Context<Self>,
6309 ) {
6310 if self
6311 .code_action_providers
6312 .iter()
6313 .any(|existing_provider| existing_provider.id() == provider.id())
6314 {
6315 return;
6316 }
6317
6318 self.code_action_providers.push(provider);
6319 self.refresh_code_actions(window, cx);
6320 }
6321
6322 pub fn remove_code_action_provider(
6323 &mut self,
6324 id: Arc<str>,
6325 window: &mut Window,
6326 cx: &mut Context<Self>,
6327 ) {
6328 self.code_action_providers
6329 .retain(|provider| provider.id() != id);
6330 self.refresh_code_actions(window, cx);
6331 }
6332
6333 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6334 !self.code_action_providers.is_empty()
6335 && EditorSettings::get_global(cx).toolbar.code_actions
6336 }
6337
6338 pub fn has_available_code_actions(&self) -> bool {
6339 self.available_code_actions
6340 .as_ref()
6341 .is_some_and(|(_, actions)| !actions.is_empty())
6342 }
6343
6344 fn render_inline_code_actions(
6345 &self,
6346 icon_size: ui::IconSize,
6347 display_row: DisplayRow,
6348 is_active: bool,
6349 cx: &mut Context<Self>,
6350 ) -> AnyElement {
6351 let show_tooltip = !self.context_menu_visible();
6352 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6353 .icon_size(icon_size)
6354 .shape(ui::IconButtonShape::Square)
6355 .style(ButtonStyle::Transparent)
6356 .icon_color(ui::Color::Hidden)
6357 .toggle_state(is_active)
6358 .when(show_tooltip, |this| {
6359 this.tooltip({
6360 let focus_handle = self.focus_handle.clone();
6361 move |window, cx| {
6362 Tooltip::for_action_in(
6363 "Toggle Code Actions",
6364 &ToggleCodeActions {
6365 deployed_from: None,
6366 quick_launch: false,
6367 },
6368 &focus_handle,
6369 window,
6370 cx,
6371 )
6372 }
6373 })
6374 })
6375 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6376 window.focus(&editor.focus_handle(cx));
6377 editor.toggle_code_actions(
6378 &crate::actions::ToggleCodeActions {
6379 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6380 display_row,
6381 )),
6382 quick_launch: false,
6383 },
6384 window,
6385 cx,
6386 );
6387 }))
6388 .into_any_element()
6389 }
6390
6391 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6392 &self.context_menu
6393 }
6394
6395 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6396 let newest_selection = self.selections.newest_anchor().clone();
6397 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6398 let buffer = self.buffer.read(cx);
6399 if newest_selection.head().diff_base_anchor.is_some() {
6400 return None;
6401 }
6402 let (start_buffer, start) =
6403 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6404 let (end_buffer, end) =
6405 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6406 if start_buffer != end_buffer {
6407 return None;
6408 }
6409
6410 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6411 cx.background_executor()
6412 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6413 .await;
6414
6415 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6416 let providers = this.code_action_providers.clone();
6417 let tasks = this
6418 .code_action_providers
6419 .iter()
6420 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6421 .collect::<Vec<_>>();
6422 (providers, tasks)
6423 })?;
6424
6425 let mut actions = Vec::new();
6426 for (provider, provider_actions) in
6427 providers.into_iter().zip(future::join_all(tasks).await)
6428 {
6429 if let Some(provider_actions) = provider_actions.log_err() {
6430 actions.extend(provider_actions.into_iter().map(|action| {
6431 AvailableCodeAction {
6432 excerpt_id: newest_selection.start.excerpt_id,
6433 action,
6434 provider: provider.clone(),
6435 }
6436 }));
6437 }
6438 }
6439
6440 this.update(cx, |this, cx| {
6441 this.available_code_actions = if actions.is_empty() {
6442 None
6443 } else {
6444 Some((
6445 Location {
6446 buffer: start_buffer,
6447 range: start..end,
6448 },
6449 actions.into(),
6450 ))
6451 };
6452 cx.notify();
6453 })
6454 }));
6455 None
6456 }
6457
6458 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6459 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6460 self.show_git_blame_inline = false;
6461
6462 self.show_git_blame_inline_delay_task =
6463 Some(cx.spawn_in(window, async move |this, cx| {
6464 cx.background_executor().timer(delay).await;
6465
6466 this.update(cx, |this, cx| {
6467 this.show_git_blame_inline = true;
6468 cx.notify();
6469 })
6470 .log_err();
6471 }));
6472 }
6473 }
6474
6475 fn show_blame_popover(
6476 &mut self,
6477 blame_entry: &BlameEntry,
6478 position: gpui::Point<Pixels>,
6479 cx: &mut Context<Self>,
6480 ) {
6481 if let Some(state) = &mut self.inline_blame_popover {
6482 state.hide_task.take();
6483 } else {
6484 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6485 let blame_entry = blame_entry.clone();
6486 let show_task = cx.spawn(async move |editor, cx| {
6487 cx.background_executor()
6488 .timer(std::time::Duration::from_millis(delay))
6489 .await;
6490 editor
6491 .update(cx, |editor, cx| {
6492 editor.inline_blame_popover_show_task.take();
6493 let Some(blame) = editor.blame.as_ref() else {
6494 return;
6495 };
6496 let blame = blame.read(cx);
6497 let details = blame.details_for_entry(&blame_entry);
6498 let markdown = cx.new(|cx| {
6499 Markdown::new(
6500 details
6501 .as_ref()
6502 .map(|message| message.message.clone())
6503 .unwrap_or_default(),
6504 None,
6505 None,
6506 cx,
6507 )
6508 });
6509 editor.inline_blame_popover = Some(InlineBlamePopover {
6510 position,
6511 hide_task: None,
6512 popover_bounds: None,
6513 popover_state: InlineBlamePopoverState {
6514 scroll_handle: ScrollHandle::new(),
6515 commit_message: details,
6516 markdown,
6517 },
6518 });
6519 cx.notify();
6520 })
6521 .ok();
6522 });
6523 self.inline_blame_popover_show_task = Some(show_task);
6524 }
6525 }
6526
6527 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6528 self.inline_blame_popover_show_task.take();
6529 if let Some(state) = &mut self.inline_blame_popover {
6530 let hide_task = cx.spawn(async move |editor, cx| {
6531 cx.background_executor()
6532 .timer(std::time::Duration::from_millis(100))
6533 .await;
6534 editor
6535 .update(cx, |editor, cx| {
6536 editor.inline_blame_popover.take();
6537 cx.notify();
6538 })
6539 .ok();
6540 });
6541 state.hide_task = Some(hide_task);
6542 }
6543 }
6544
6545 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6546 if self.pending_rename.is_some() {
6547 return None;
6548 }
6549
6550 let provider = self.semantics_provider.clone()?;
6551 let buffer = self.buffer.read(cx);
6552 let newest_selection = self.selections.newest_anchor().clone();
6553 let cursor_position = newest_selection.head();
6554 let (cursor_buffer, cursor_buffer_position) =
6555 buffer.text_anchor_for_position(cursor_position, cx)?;
6556 let (tail_buffer, tail_buffer_position) =
6557 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6558 if cursor_buffer != tail_buffer {
6559 return None;
6560 }
6561
6562 let snapshot = cursor_buffer.read(cx).snapshot();
6563 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6564 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6565 if start_word_range != end_word_range {
6566 self.document_highlights_task.take();
6567 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6568 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6569 return None;
6570 }
6571
6572 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6573 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6574 cx.background_executor()
6575 .timer(Duration::from_millis(debounce))
6576 .await;
6577
6578 let highlights = if let Some(highlights) = cx
6579 .update(|cx| {
6580 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6581 })
6582 .ok()
6583 .flatten()
6584 {
6585 highlights.await.log_err()
6586 } else {
6587 None
6588 };
6589
6590 if let Some(highlights) = highlights {
6591 this.update(cx, |this, cx| {
6592 if this.pending_rename.is_some() {
6593 return;
6594 }
6595
6596 let buffer_id = cursor_position.buffer_id;
6597 let buffer = this.buffer.read(cx);
6598 if !buffer
6599 .text_anchor_for_position(cursor_position, cx)
6600 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6601 {
6602 return;
6603 }
6604
6605 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6606 let mut write_ranges = Vec::new();
6607 let mut read_ranges = Vec::new();
6608 for highlight in highlights {
6609 for (excerpt_id, excerpt_range) in
6610 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6611 {
6612 let start = highlight
6613 .range
6614 .start
6615 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6616 let end = highlight
6617 .range
6618 .end
6619 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6620 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6621 continue;
6622 }
6623
6624 let range = Anchor {
6625 buffer_id,
6626 excerpt_id,
6627 text_anchor: start,
6628 diff_base_anchor: None,
6629 }..Anchor {
6630 buffer_id,
6631 excerpt_id,
6632 text_anchor: end,
6633 diff_base_anchor: None,
6634 };
6635 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6636 write_ranges.push(range);
6637 } else {
6638 read_ranges.push(range);
6639 }
6640 }
6641 }
6642
6643 this.highlight_background::<DocumentHighlightRead>(
6644 &read_ranges,
6645 |theme| theme.colors().editor_document_highlight_read_background,
6646 cx,
6647 );
6648 this.highlight_background::<DocumentHighlightWrite>(
6649 &write_ranges,
6650 |theme| theme.colors().editor_document_highlight_write_background,
6651 cx,
6652 );
6653 cx.notify();
6654 })
6655 .log_err();
6656 }
6657 }));
6658 None
6659 }
6660
6661 fn prepare_highlight_query_from_selection(
6662 &mut self,
6663 cx: &mut Context<Editor>,
6664 ) -> Option<(String, Range<Anchor>)> {
6665 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6666 return None;
6667 }
6668 if !EditorSettings::get_global(cx).selection_highlight {
6669 return None;
6670 }
6671 if self.selections.count() != 1 || self.selections.line_mode {
6672 return None;
6673 }
6674 let selection = self.selections.newest::<Point>(cx);
6675 if selection.is_empty() || selection.start.row != selection.end.row {
6676 return None;
6677 }
6678 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6679 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6680 let query = multi_buffer_snapshot
6681 .text_for_range(selection_anchor_range.clone())
6682 .collect::<String>();
6683 if query.trim().is_empty() {
6684 return None;
6685 }
6686 Some((query, selection_anchor_range))
6687 }
6688
6689 fn update_selection_occurrence_highlights(
6690 &mut self,
6691 query_text: String,
6692 query_range: Range<Anchor>,
6693 multi_buffer_range_to_query: Range<Point>,
6694 use_debounce: bool,
6695 window: &mut Window,
6696 cx: &mut Context<Editor>,
6697 ) -> Task<()> {
6698 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6699 cx.spawn_in(window, async move |editor, cx| {
6700 if use_debounce {
6701 cx.background_executor()
6702 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6703 .await;
6704 }
6705 let match_task = cx.background_spawn(async move {
6706 let buffer_ranges = multi_buffer_snapshot
6707 .range_to_buffer_ranges(multi_buffer_range_to_query)
6708 .into_iter()
6709 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6710 let mut match_ranges = Vec::new();
6711 let Ok(regex) = project::search::SearchQuery::text(
6712 query_text.clone(),
6713 false,
6714 false,
6715 false,
6716 Default::default(),
6717 Default::default(),
6718 false,
6719 None,
6720 ) else {
6721 return Vec::default();
6722 };
6723 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6724 match_ranges.extend(
6725 regex
6726 .search(&buffer_snapshot, Some(search_range.clone()))
6727 .await
6728 .into_iter()
6729 .filter_map(|match_range| {
6730 let match_start = buffer_snapshot
6731 .anchor_after(search_range.start + match_range.start);
6732 let match_end = buffer_snapshot
6733 .anchor_before(search_range.start + match_range.end);
6734 let match_anchor_range = Anchor::range_in_buffer(
6735 excerpt_id,
6736 buffer_snapshot.remote_id(),
6737 match_start..match_end,
6738 );
6739 (match_anchor_range != query_range).then_some(match_anchor_range)
6740 }),
6741 );
6742 }
6743 match_ranges
6744 });
6745 let match_ranges = match_task.await;
6746 editor
6747 .update_in(cx, |editor, _, cx| {
6748 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6749 if !match_ranges.is_empty() {
6750 editor.highlight_background::<SelectedTextHighlight>(
6751 &match_ranges,
6752 |theme| theme.colors().editor_document_highlight_bracket_background,
6753 cx,
6754 )
6755 }
6756 })
6757 .log_err();
6758 })
6759 }
6760
6761 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6762 struct NewlineFold;
6763 let type_id = std::any::TypeId::of::<NewlineFold>();
6764 if !self.mode.is_single_line() {
6765 return;
6766 }
6767 let snapshot = self.snapshot(window, cx);
6768 if snapshot.buffer_snapshot.max_point().row == 0 {
6769 return;
6770 }
6771 let task = cx.background_spawn(async move {
6772 let new_newlines = snapshot
6773 .buffer_chars_at(0)
6774 .filter_map(|(c, i)| {
6775 if c == '\n' {
6776 Some(
6777 snapshot.buffer_snapshot.anchor_after(i)
6778 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6779 )
6780 } else {
6781 None
6782 }
6783 })
6784 .collect::<Vec<_>>();
6785 let existing_newlines = snapshot
6786 .folds_in_range(0..snapshot.buffer_snapshot.len())
6787 .filter_map(|fold| {
6788 if fold.placeholder.type_tag == Some(type_id) {
6789 Some(fold.range.start..fold.range.end)
6790 } else {
6791 None
6792 }
6793 })
6794 .collect::<Vec<_>>();
6795
6796 (new_newlines, existing_newlines)
6797 });
6798 self.folding_newlines = cx.spawn(async move |this, cx| {
6799 let (new_newlines, existing_newlines) = task.await;
6800 if new_newlines == existing_newlines {
6801 return;
6802 }
6803 let placeholder = FoldPlaceholder {
6804 render: Arc::new(move |_, _, cx| {
6805 div()
6806 .bg(cx.theme().status().hint_background)
6807 .border_b_1()
6808 .size_full()
6809 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6810 .border_color(cx.theme().status().hint)
6811 .child("\\n")
6812 .into_any()
6813 }),
6814 constrain_width: false,
6815 merge_adjacent: false,
6816 type_tag: Some(type_id),
6817 };
6818 let creases = new_newlines
6819 .into_iter()
6820 .map(|range| Crease::simple(range, placeholder.clone()))
6821 .collect();
6822 this.update(cx, |this, cx| {
6823 this.display_map.update(cx, |display_map, cx| {
6824 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6825 display_map.fold(creases, cx);
6826 });
6827 })
6828 .ok();
6829 });
6830 }
6831
6832 fn refresh_selected_text_highlights(
6833 &mut self,
6834 on_buffer_edit: bool,
6835 window: &mut Window,
6836 cx: &mut Context<Editor>,
6837 ) {
6838 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6839 else {
6840 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6841 self.quick_selection_highlight_task.take();
6842 self.debounced_selection_highlight_task.take();
6843 return;
6844 };
6845 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6846 if on_buffer_edit
6847 || self
6848 .quick_selection_highlight_task
6849 .as_ref()
6850 .map_or(true, |(prev_anchor_range, _)| {
6851 prev_anchor_range != &query_range
6852 })
6853 {
6854 let multi_buffer_visible_start = self
6855 .scroll_manager
6856 .anchor()
6857 .anchor
6858 .to_point(&multi_buffer_snapshot);
6859 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6860 multi_buffer_visible_start
6861 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6862 Bias::Left,
6863 );
6864 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6865 self.quick_selection_highlight_task = Some((
6866 query_range.clone(),
6867 self.update_selection_occurrence_highlights(
6868 query_text.clone(),
6869 query_range.clone(),
6870 multi_buffer_visible_range,
6871 false,
6872 window,
6873 cx,
6874 ),
6875 ));
6876 }
6877 if on_buffer_edit
6878 || self
6879 .debounced_selection_highlight_task
6880 .as_ref()
6881 .map_or(true, |(prev_anchor_range, _)| {
6882 prev_anchor_range != &query_range
6883 })
6884 {
6885 let multi_buffer_start = multi_buffer_snapshot
6886 .anchor_before(0)
6887 .to_point(&multi_buffer_snapshot);
6888 let multi_buffer_end = multi_buffer_snapshot
6889 .anchor_after(multi_buffer_snapshot.len())
6890 .to_point(&multi_buffer_snapshot);
6891 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6892 self.debounced_selection_highlight_task = Some((
6893 query_range.clone(),
6894 self.update_selection_occurrence_highlights(
6895 query_text,
6896 query_range,
6897 multi_buffer_full_range,
6898 true,
6899 window,
6900 cx,
6901 ),
6902 ));
6903 }
6904 }
6905
6906 pub fn refresh_inline_completion(
6907 &mut self,
6908 debounce: bool,
6909 user_requested: bool,
6910 window: &mut Window,
6911 cx: &mut Context<Self>,
6912 ) -> Option<()> {
6913 let provider = self.edit_prediction_provider()?;
6914 let cursor = self.selections.newest_anchor().head();
6915 let (buffer, cursor_buffer_position) =
6916 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6917
6918 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6919 self.discard_inline_completion(false, cx);
6920 return None;
6921 }
6922
6923 if !user_requested
6924 && (!self.should_show_edit_predictions()
6925 || !self.is_focused(window)
6926 || buffer.read(cx).is_empty())
6927 {
6928 self.discard_inline_completion(false, cx);
6929 return None;
6930 }
6931
6932 self.update_visible_inline_completion(window, cx);
6933 provider.refresh(
6934 self.project.clone(),
6935 buffer,
6936 cursor_buffer_position,
6937 debounce,
6938 cx,
6939 );
6940 Some(())
6941 }
6942
6943 fn show_edit_predictions_in_menu(&self) -> bool {
6944 match self.edit_prediction_settings {
6945 EditPredictionSettings::Disabled => false,
6946 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6947 }
6948 }
6949
6950 pub fn edit_predictions_enabled(&self) -> bool {
6951 match self.edit_prediction_settings {
6952 EditPredictionSettings::Disabled => false,
6953 EditPredictionSettings::Enabled { .. } => true,
6954 }
6955 }
6956
6957 fn edit_prediction_requires_modifier(&self) -> bool {
6958 match self.edit_prediction_settings {
6959 EditPredictionSettings::Disabled => false,
6960 EditPredictionSettings::Enabled {
6961 preview_requires_modifier,
6962 ..
6963 } => preview_requires_modifier,
6964 }
6965 }
6966
6967 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6968 if self.edit_prediction_provider.is_none() {
6969 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6970 } else {
6971 let selection = self.selections.newest_anchor();
6972 let cursor = selection.head();
6973
6974 if let Some((buffer, cursor_buffer_position)) =
6975 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6976 {
6977 self.edit_prediction_settings =
6978 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6979 }
6980 }
6981 }
6982
6983 fn edit_prediction_settings_at_position(
6984 &self,
6985 buffer: &Entity<Buffer>,
6986 buffer_position: language::Anchor,
6987 cx: &App,
6988 ) -> EditPredictionSettings {
6989 if !self.mode.is_full()
6990 || !self.show_inline_completions_override.unwrap_or(true)
6991 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6992 {
6993 return EditPredictionSettings::Disabled;
6994 }
6995
6996 let buffer = buffer.read(cx);
6997
6998 let file = buffer.file();
6999
7000 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7001 return EditPredictionSettings::Disabled;
7002 };
7003
7004 let by_provider = matches!(
7005 self.menu_inline_completions_policy,
7006 MenuInlineCompletionsPolicy::ByProvider
7007 );
7008
7009 let show_in_menu = by_provider
7010 && self
7011 .edit_prediction_provider
7012 .as_ref()
7013 .map_or(false, |provider| {
7014 provider.provider.show_completions_in_menu()
7015 });
7016
7017 let preview_requires_modifier =
7018 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7019
7020 EditPredictionSettings::Enabled {
7021 show_in_menu,
7022 preview_requires_modifier,
7023 }
7024 }
7025
7026 fn should_show_edit_predictions(&self) -> bool {
7027 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7028 }
7029
7030 pub fn edit_prediction_preview_is_active(&self) -> bool {
7031 matches!(
7032 self.edit_prediction_preview,
7033 EditPredictionPreview::Active { .. }
7034 )
7035 }
7036
7037 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7038 let cursor = self.selections.newest_anchor().head();
7039 if let Some((buffer, cursor_position)) =
7040 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7041 {
7042 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7043 } else {
7044 false
7045 }
7046 }
7047
7048 pub fn supports_minimap(&self, cx: &App) -> bool {
7049 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7050 }
7051
7052 fn edit_predictions_enabled_in_buffer(
7053 &self,
7054 buffer: &Entity<Buffer>,
7055 buffer_position: language::Anchor,
7056 cx: &App,
7057 ) -> bool {
7058 maybe!({
7059 if self.read_only(cx) {
7060 return Some(false);
7061 }
7062 let provider = self.edit_prediction_provider()?;
7063 if !provider.is_enabled(&buffer, buffer_position, cx) {
7064 return Some(false);
7065 }
7066 let buffer = buffer.read(cx);
7067 let Some(file) = buffer.file() else {
7068 return Some(true);
7069 };
7070 let settings = all_language_settings(Some(file), cx);
7071 Some(settings.edit_predictions_enabled_for_file(file, cx))
7072 })
7073 .unwrap_or(false)
7074 }
7075
7076 fn cycle_inline_completion(
7077 &mut self,
7078 direction: Direction,
7079 window: &mut Window,
7080 cx: &mut Context<Self>,
7081 ) -> Option<()> {
7082 let provider = self.edit_prediction_provider()?;
7083 let cursor = self.selections.newest_anchor().head();
7084 let (buffer, cursor_buffer_position) =
7085 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7086 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7087 return None;
7088 }
7089
7090 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7091 self.update_visible_inline_completion(window, cx);
7092
7093 Some(())
7094 }
7095
7096 pub fn show_inline_completion(
7097 &mut self,
7098 _: &ShowEditPrediction,
7099 window: &mut Window,
7100 cx: &mut Context<Self>,
7101 ) {
7102 if !self.has_active_inline_completion() {
7103 self.refresh_inline_completion(false, true, window, cx);
7104 return;
7105 }
7106
7107 self.update_visible_inline_completion(window, cx);
7108 }
7109
7110 pub fn display_cursor_names(
7111 &mut self,
7112 _: &DisplayCursorNames,
7113 window: &mut Window,
7114 cx: &mut Context<Self>,
7115 ) {
7116 self.show_cursor_names(window, cx);
7117 }
7118
7119 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7120 self.show_cursor_names = true;
7121 cx.notify();
7122 cx.spawn_in(window, async move |this, cx| {
7123 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7124 this.update(cx, |this, cx| {
7125 this.show_cursor_names = false;
7126 cx.notify()
7127 })
7128 .ok()
7129 })
7130 .detach();
7131 }
7132
7133 pub fn next_edit_prediction(
7134 &mut self,
7135 _: &NextEditPrediction,
7136 window: &mut Window,
7137 cx: &mut Context<Self>,
7138 ) {
7139 if self.has_active_inline_completion() {
7140 self.cycle_inline_completion(Direction::Next, window, cx);
7141 } else {
7142 let is_copilot_disabled = self
7143 .refresh_inline_completion(false, true, window, cx)
7144 .is_none();
7145 if is_copilot_disabled {
7146 cx.propagate();
7147 }
7148 }
7149 }
7150
7151 pub fn previous_edit_prediction(
7152 &mut self,
7153 _: &PreviousEditPrediction,
7154 window: &mut Window,
7155 cx: &mut Context<Self>,
7156 ) {
7157 if self.has_active_inline_completion() {
7158 self.cycle_inline_completion(Direction::Prev, window, cx);
7159 } else {
7160 let is_copilot_disabled = self
7161 .refresh_inline_completion(false, true, window, cx)
7162 .is_none();
7163 if is_copilot_disabled {
7164 cx.propagate();
7165 }
7166 }
7167 }
7168
7169 pub fn accept_edit_prediction(
7170 &mut self,
7171 _: &AcceptEditPrediction,
7172 window: &mut Window,
7173 cx: &mut Context<Self>,
7174 ) {
7175 if self.show_edit_predictions_in_menu() {
7176 self.hide_context_menu(window, cx);
7177 }
7178
7179 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7180 return;
7181 };
7182
7183 self.report_inline_completion_event(
7184 active_inline_completion.completion_id.clone(),
7185 true,
7186 cx,
7187 );
7188
7189 match &active_inline_completion.completion {
7190 InlineCompletion::Move { target, .. } => {
7191 let target = *target;
7192
7193 if let Some(position_map) = &self.last_position_map {
7194 if position_map
7195 .visible_row_range
7196 .contains(&target.to_display_point(&position_map.snapshot).row())
7197 || !self.edit_prediction_requires_modifier()
7198 {
7199 self.unfold_ranges(&[target..target], true, false, cx);
7200 // Note that this is also done in vim's handler of the Tab action.
7201 self.change_selections(
7202 SelectionEffects::scroll(Autoscroll::newest()),
7203 window,
7204 cx,
7205 |selections| {
7206 selections.select_anchor_ranges([target..target]);
7207 },
7208 );
7209 self.clear_row_highlights::<EditPredictionPreview>();
7210
7211 self.edit_prediction_preview
7212 .set_previous_scroll_position(None);
7213 } else {
7214 self.edit_prediction_preview
7215 .set_previous_scroll_position(Some(
7216 position_map.snapshot.scroll_anchor,
7217 ));
7218
7219 self.highlight_rows::<EditPredictionPreview>(
7220 target..target,
7221 cx.theme().colors().editor_highlighted_line_background,
7222 RowHighlightOptions {
7223 autoscroll: true,
7224 ..Default::default()
7225 },
7226 cx,
7227 );
7228 self.request_autoscroll(Autoscroll::fit(), cx);
7229 }
7230 }
7231 }
7232 InlineCompletion::Edit { edits, .. } => {
7233 if let Some(provider) = self.edit_prediction_provider() {
7234 provider.accept(cx);
7235 }
7236
7237 // Store the transaction ID and selections before applying the edit
7238 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7239
7240 let snapshot = self.buffer.read(cx).snapshot(cx);
7241 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7242
7243 self.buffer.update(cx, |buffer, cx| {
7244 buffer.edit(edits.iter().cloned(), None, cx)
7245 });
7246
7247 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7248 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7249 });
7250
7251 let selections = self.selections.disjoint_anchors();
7252 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7253 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7254 if has_new_transaction {
7255 self.selection_history
7256 .insert_transaction(transaction_id_now, selections);
7257 }
7258 }
7259
7260 self.update_visible_inline_completion(window, cx);
7261 if self.active_inline_completion.is_none() {
7262 self.refresh_inline_completion(true, true, window, cx);
7263 }
7264
7265 cx.notify();
7266 }
7267 }
7268
7269 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7270 }
7271
7272 pub fn accept_partial_inline_completion(
7273 &mut self,
7274 _: &AcceptPartialEditPrediction,
7275 window: &mut Window,
7276 cx: &mut Context<Self>,
7277 ) {
7278 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7279 return;
7280 };
7281 if self.selections.count() != 1 {
7282 return;
7283 }
7284
7285 self.report_inline_completion_event(
7286 active_inline_completion.completion_id.clone(),
7287 true,
7288 cx,
7289 );
7290
7291 match &active_inline_completion.completion {
7292 InlineCompletion::Move { target, .. } => {
7293 let target = *target;
7294 self.change_selections(
7295 SelectionEffects::scroll(Autoscroll::newest()),
7296 window,
7297 cx,
7298 |selections| {
7299 selections.select_anchor_ranges([target..target]);
7300 },
7301 );
7302 }
7303 InlineCompletion::Edit { edits, .. } => {
7304 // Find an insertion that starts at the cursor position.
7305 let snapshot = self.buffer.read(cx).snapshot(cx);
7306 let cursor_offset = self.selections.newest::<usize>(cx).head();
7307 let insertion = edits.iter().find_map(|(range, text)| {
7308 let range = range.to_offset(&snapshot);
7309 if range.is_empty() && range.start == cursor_offset {
7310 Some(text)
7311 } else {
7312 None
7313 }
7314 });
7315
7316 if let Some(text) = insertion {
7317 let mut partial_completion = text
7318 .chars()
7319 .by_ref()
7320 .take_while(|c| c.is_alphabetic())
7321 .collect::<String>();
7322 if partial_completion.is_empty() {
7323 partial_completion = text
7324 .chars()
7325 .by_ref()
7326 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7327 .collect::<String>();
7328 }
7329
7330 cx.emit(EditorEvent::InputHandled {
7331 utf16_range_to_replace: None,
7332 text: partial_completion.clone().into(),
7333 });
7334
7335 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7336
7337 self.refresh_inline_completion(true, true, window, cx);
7338 cx.notify();
7339 } else {
7340 self.accept_edit_prediction(&Default::default(), window, cx);
7341 }
7342 }
7343 }
7344 }
7345
7346 fn discard_inline_completion(
7347 &mut self,
7348 should_report_inline_completion_event: bool,
7349 cx: &mut Context<Self>,
7350 ) -> bool {
7351 if should_report_inline_completion_event {
7352 let completion_id = self
7353 .active_inline_completion
7354 .as_ref()
7355 .and_then(|active_completion| active_completion.completion_id.clone());
7356
7357 self.report_inline_completion_event(completion_id, false, cx);
7358 }
7359
7360 if let Some(provider) = self.edit_prediction_provider() {
7361 provider.discard(cx);
7362 }
7363
7364 self.take_active_inline_completion(cx)
7365 }
7366
7367 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7368 let Some(provider) = self.edit_prediction_provider() else {
7369 return;
7370 };
7371
7372 let Some((_, buffer, _)) = self
7373 .buffer
7374 .read(cx)
7375 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7376 else {
7377 return;
7378 };
7379
7380 let extension = buffer
7381 .read(cx)
7382 .file()
7383 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7384
7385 let event_type = match accepted {
7386 true => "Edit Prediction Accepted",
7387 false => "Edit Prediction Discarded",
7388 };
7389 telemetry::event!(
7390 event_type,
7391 provider = provider.name(),
7392 prediction_id = id,
7393 suggestion_accepted = accepted,
7394 file_extension = extension,
7395 );
7396 }
7397
7398 pub fn has_active_inline_completion(&self) -> bool {
7399 self.active_inline_completion.is_some()
7400 }
7401
7402 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7403 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7404 return false;
7405 };
7406
7407 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7408 self.clear_highlights::<InlineCompletionHighlight>(cx);
7409 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7410 true
7411 }
7412
7413 /// Returns true when we're displaying the edit prediction popover below the cursor
7414 /// like we are not previewing and the LSP autocomplete menu is visible
7415 /// or we are in `when_holding_modifier` mode.
7416 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7417 if self.edit_prediction_preview_is_active()
7418 || !self.show_edit_predictions_in_menu()
7419 || !self.edit_predictions_enabled()
7420 {
7421 return false;
7422 }
7423
7424 if self.has_visible_completions_menu() {
7425 return true;
7426 }
7427
7428 has_completion && self.edit_prediction_requires_modifier()
7429 }
7430
7431 fn handle_modifiers_changed(
7432 &mut self,
7433 modifiers: Modifiers,
7434 position_map: &PositionMap,
7435 window: &mut Window,
7436 cx: &mut Context<Self>,
7437 ) {
7438 if self.show_edit_predictions_in_menu() {
7439 self.update_edit_prediction_preview(&modifiers, window, cx);
7440 }
7441
7442 self.update_selection_mode(&modifiers, position_map, window, cx);
7443
7444 let mouse_position = window.mouse_position();
7445 if !position_map.text_hitbox.is_hovered(window) {
7446 return;
7447 }
7448
7449 self.update_hovered_link(
7450 position_map.point_for_position(mouse_position),
7451 &position_map.snapshot,
7452 modifiers,
7453 window,
7454 cx,
7455 )
7456 }
7457
7458 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7459 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7460 if invert {
7461 match multi_cursor_setting {
7462 MultiCursorModifier::Alt => modifiers.alt,
7463 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7464 }
7465 } else {
7466 match multi_cursor_setting {
7467 MultiCursorModifier::Alt => modifiers.secondary(),
7468 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7469 }
7470 }
7471 }
7472
7473 fn columnar_selection_mode(
7474 modifiers: &Modifiers,
7475 cx: &mut Context<Self>,
7476 ) -> Option<ColumnarMode> {
7477 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7478 if Self::multi_cursor_modifier(false, modifiers, cx) {
7479 Some(ColumnarMode::FromMouse)
7480 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7481 Some(ColumnarMode::FromSelection)
7482 } else {
7483 None
7484 }
7485 } else {
7486 None
7487 }
7488 }
7489
7490 fn update_selection_mode(
7491 &mut self,
7492 modifiers: &Modifiers,
7493 position_map: &PositionMap,
7494 window: &mut Window,
7495 cx: &mut Context<Self>,
7496 ) {
7497 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7498 return;
7499 };
7500 if self.selections.pending.is_none() {
7501 return;
7502 }
7503
7504 let mouse_position = window.mouse_position();
7505 let point_for_position = position_map.point_for_position(mouse_position);
7506 let position = point_for_position.previous_valid;
7507
7508 self.select(
7509 SelectPhase::BeginColumnar {
7510 position,
7511 reset: false,
7512 mode,
7513 goal_column: point_for_position.exact_unclipped.column(),
7514 },
7515 window,
7516 cx,
7517 );
7518 }
7519
7520 fn update_edit_prediction_preview(
7521 &mut self,
7522 modifiers: &Modifiers,
7523 window: &mut Window,
7524 cx: &mut Context<Self>,
7525 ) {
7526 let mut modifiers_held = false;
7527 if let Some(accept_keystroke) = self
7528 .accept_edit_prediction_keybind(false, window, cx)
7529 .keystroke()
7530 {
7531 modifiers_held = modifiers_held
7532 || (&accept_keystroke.modifiers == modifiers
7533 && accept_keystroke.modifiers.modified());
7534 };
7535 if let Some(accept_partial_keystroke) = self
7536 .accept_edit_prediction_keybind(true, window, cx)
7537 .keystroke()
7538 {
7539 modifiers_held = modifiers_held
7540 || (&accept_partial_keystroke.modifiers == modifiers
7541 && accept_partial_keystroke.modifiers.modified());
7542 }
7543
7544 if modifiers_held {
7545 if matches!(
7546 self.edit_prediction_preview,
7547 EditPredictionPreview::Inactive { .. }
7548 ) {
7549 self.edit_prediction_preview = EditPredictionPreview::Active {
7550 previous_scroll_position: None,
7551 since: Instant::now(),
7552 };
7553
7554 self.update_visible_inline_completion(window, cx);
7555 cx.notify();
7556 }
7557 } else if let EditPredictionPreview::Active {
7558 previous_scroll_position,
7559 since,
7560 } = self.edit_prediction_preview
7561 {
7562 if let (Some(previous_scroll_position), Some(position_map)) =
7563 (previous_scroll_position, self.last_position_map.as_ref())
7564 {
7565 self.set_scroll_position(
7566 previous_scroll_position
7567 .scroll_position(&position_map.snapshot.display_snapshot),
7568 window,
7569 cx,
7570 );
7571 }
7572
7573 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7574 released_too_fast: since.elapsed() < Duration::from_millis(200),
7575 };
7576 self.clear_row_highlights::<EditPredictionPreview>();
7577 self.update_visible_inline_completion(window, cx);
7578 cx.notify();
7579 }
7580 }
7581
7582 fn update_visible_inline_completion(
7583 &mut self,
7584 _window: &mut Window,
7585 cx: &mut Context<Self>,
7586 ) -> Option<()> {
7587 let selection = self.selections.newest_anchor();
7588 let cursor = selection.head();
7589 let multibuffer = self.buffer.read(cx).snapshot(cx);
7590 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7591 let excerpt_id = cursor.excerpt_id;
7592
7593 let show_in_menu = self.show_edit_predictions_in_menu();
7594 let completions_menu_has_precedence = !show_in_menu
7595 && (self.context_menu.borrow().is_some()
7596 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7597
7598 if completions_menu_has_precedence
7599 || !offset_selection.is_empty()
7600 || self
7601 .active_inline_completion
7602 .as_ref()
7603 .map_or(false, |completion| {
7604 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7605 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7606 !invalidation_range.contains(&offset_selection.head())
7607 })
7608 {
7609 self.discard_inline_completion(false, cx);
7610 return None;
7611 }
7612
7613 self.take_active_inline_completion(cx);
7614 let Some(provider) = self.edit_prediction_provider() else {
7615 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7616 return None;
7617 };
7618
7619 let (buffer, cursor_buffer_position) =
7620 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7621
7622 self.edit_prediction_settings =
7623 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7624
7625 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7626
7627 if self.edit_prediction_indent_conflict {
7628 let cursor_point = cursor.to_point(&multibuffer);
7629
7630 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7631
7632 if let Some((_, indent)) = indents.iter().next() {
7633 if indent.len == cursor_point.column {
7634 self.edit_prediction_indent_conflict = false;
7635 }
7636 }
7637 }
7638
7639 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7640 let edits = inline_completion
7641 .edits
7642 .into_iter()
7643 .flat_map(|(range, new_text)| {
7644 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7645 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7646 Some((start..end, new_text))
7647 })
7648 .collect::<Vec<_>>();
7649 if edits.is_empty() {
7650 return None;
7651 }
7652
7653 let first_edit_start = edits.first().unwrap().0.start;
7654 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7655 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7656
7657 let last_edit_end = edits.last().unwrap().0.end;
7658 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7659 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7660
7661 let cursor_row = cursor.to_point(&multibuffer).row;
7662
7663 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7664
7665 let mut inlay_ids = Vec::new();
7666 let invalidation_row_range;
7667 let move_invalidation_row_range = if cursor_row < edit_start_row {
7668 Some(cursor_row..edit_end_row)
7669 } else if cursor_row > edit_end_row {
7670 Some(edit_start_row..cursor_row)
7671 } else {
7672 None
7673 };
7674 let is_move =
7675 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7676 let completion = if is_move {
7677 invalidation_row_range =
7678 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7679 let target = first_edit_start;
7680 InlineCompletion::Move { target, snapshot }
7681 } else {
7682 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7683 && !self.inline_completions_hidden_for_vim_mode;
7684
7685 if show_completions_in_buffer {
7686 if edits
7687 .iter()
7688 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7689 {
7690 let mut inlays = Vec::new();
7691 for (range, new_text) in &edits {
7692 let inlay = Inlay::inline_completion(
7693 post_inc(&mut self.next_inlay_id),
7694 range.start,
7695 new_text.as_str(),
7696 );
7697 inlay_ids.push(inlay.id);
7698 inlays.push(inlay);
7699 }
7700
7701 self.splice_inlays(&[], inlays, cx);
7702 } else {
7703 let background_color = cx.theme().status().deleted_background;
7704 self.highlight_text::<InlineCompletionHighlight>(
7705 edits.iter().map(|(range, _)| range.clone()).collect(),
7706 HighlightStyle {
7707 background_color: Some(background_color),
7708 ..Default::default()
7709 },
7710 cx,
7711 );
7712 }
7713 }
7714
7715 invalidation_row_range = edit_start_row..edit_end_row;
7716
7717 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7718 if provider.show_tab_accept_marker() {
7719 EditDisplayMode::TabAccept
7720 } else {
7721 EditDisplayMode::Inline
7722 }
7723 } else {
7724 EditDisplayMode::DiffPopover
7725 };
7726
7727 InlineCompletion::Edit {
7728 edits,
7729 edit_preview: inline_completion.edit_preview,
7730 display_mode,
7731 snapshot,
7732 }
7733 };
7734
7735 let invalidation_range = multibuffer
7736 .anchor_before(Point::new(invalidation_row_range.start, 0))
7737 ..multibuffer.anchor_after(Point::new(
7738 invalidation_row_range.end,
7739 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7740 ));
7741
7742 self.stale_inline_completion_in_menu = None;
7743 self.active_inline_completion = Some(InlineCompletionState {
7744 inlay_ids,
7745 completion,
7746 completion_id: inline_completion.id,
7747 invalidation_range,
7748 });
7749
7750 cx.notify();
7751
7752 Some(())
7753 }
7754
7755 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7756 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7757 }
7758
7759 fn clear_tasks(&mut self) {
7760 self.tasks.clear()
7761 }
7762
7763 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7764 if self.tasks.insert(key, value).is_some() {
7765 // This case should hopefully be rare, but just in case...
7766 log::error!(
7767 "multiple different run targets found on a single line, only the last target will be rendered"
7768 )
7769 }
7770 }
7771
7772 /// Get all display points of breakpoints that will be rendered within editor
7773 ///
7774 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7775 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7776 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7777 fn active_breakpoints(
7778 &self,
7779 range: Range<DisplayRow>,
7780 window: &mut Window,
7781 cx: &mut Context<Self>,
7782 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7783 let mut breakpoint_display_points = HashMap::default();
7784
7785 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7786 return breakpoint_display_points;
7787 };
7788
7789 let snapshot = self.snapshot(window, cx);
7790
7791 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7792 let Some(project) = self.project.as_ref() else {
7793 return breakpoint_display_points;
7794 };
7795
7796 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7797 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7798
7799 for (buffer_snapshot, range, excerpt_id) in
7800 multi_buffer_snapshot.range_to_buffer_ranges(range)
7801 {
7802 let Some(buffer) = project
7803 .read(cx)
7804 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7805 else {
7806 continue;
7807 };
7808 let breakpoints = breakpoint_store.read(cx).breakpoints(
7809 &buffer,
7810 Some(
7811 buffer_snapshot.anchor_before(range.start)
7812 ..buffer_snapshot.anchor_after(range.end),
7813 ),
7814 buffer_snapshot,
7815 cx,
7816 );
7817 for (breakpoint, state) in breakpoints {
7818 let multi_buffer_anchor =
7819 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7820 let position = multi_buffer_anchor
7821 .to_point(&multi_buffer_snapshot)
7822 .to_display_point(&snapshot);
7823
7824 breakpoint_display_points.insert(
7825 position.row(),
7826 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7827 );
7828 }
7829 }
7830
7831 breakpoint_display_points
7832 }
7833
7834 fn breakpoint_context_menu(
7835 &self,
7836 anchor: Anchor,
7837 window: &mut Window,
7838 cx: &mut Context<Self>,
7839 ) -> Entity<ui::ContextMenu> {
7840 let weak_editor = cx.weak_entity();
7841 let focus_handle = self.focus_handle(cx);
7842
7843 let row = self
7844 .buffer
7845 .read(cx)
7846 .snapshot(cx)
7847 .summary_for_anchor::<Point>(&anchor)
7848 .row;
7849
7850 let breakpoint = self
7851 .breakpoint_at_row(row, window, cx)
7852 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7853
7854 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7855 "Edit Log Breakpoint"
7856 } else {
7857 "Set Log Breakpoint"
7858 };
7859
7860 let condition_breakpoint_msg = if breakpoint
7861 .as_ref()
7862 .is_some_and(|bp| bp.1.condition.is_some())
7863 {
7864 "Edit Condition Breakpoint"
7865 } else {
7866 "Set Condition Breakpoint"
7867 };
7868
7869 let hit_condition_breakpoint_msg = if breakpoint
7870 .as_ref()
7871 .is_some_and(|bp| bp.1.hit_condition.is_some())
7872 {
7873 "Edit Hit Condition Breakpoint"
7874 } else {
7875 "Set Hit Condition Breakpoint"
7876 };
7877
7878 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7879 "Unset Breakpoint"
7880 } else {
7881 "Set Breakpoint"
7882 };
7883
7884 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7885
7886 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7887 BreakpointState::Enabled => Some("Disable"),
7888 BreakpointState::Disabled => Some("Enable"),
7889 });
7890
7891 let (anchor, breakpoint) =
7892 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7893
7894 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7895 menu.on_blur_subscription(Subscription::new(|| {}))
7896 .context(focus_handle)
7897 .when(run_to_cursor, |this| {
7898 let weak_editor = weak_editor.clone();
7899 this.entry("Run to cursor", None, move |window, cx| {
7900 weak_editor
7901 .update(cx, |editor, cx| {
7902 editor.change_selections(
7903 SelectionEffects::no_scroll(),
7904 window,
7905 cx,
7906 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
7907 );
7908 })
7909 .ok();
7910
7911 window.dispatch_action(Box::new(RunToCursor), cx);
7912 })
7913 .separator()
7914 })
7915 .when_some(toggle_state_msg, |this, msg| {
7916 this.entry(msg, None, {
7917 let weak_editor = weak_editor.clone();
7918 let breakpoint = breakpoint.clone();
7919 move |_window, cx| {
7920 weak_editor
7921 .update(cx, |this, cx| {
7922 this.edit_breakpoint_at_anchor(
7923 anchor,
7924 breakpoint.as_ref().clone(),
7925 BreakpointEditAction::InvertState,
7926 cx,
7927 );
7928 })
7929 .log_err();
7930 }
7931 })
7932 })
7933 .entry(set_breakpoint_msg, None, {
7934 let weak_editor = weak_editor.clone();
7935 let breakpoint = breakpoint.clone();
7936 move |_window, cx| {
7937 weak_editor
7938 .update(cx, |this, cx| {
7939 this.edit_breakpoint_at_anchor(
7940 anchor,
7941 breakpoint.as_ref().clone(),
7942 BreakpointEditAction::Toggle,
7943 cx,
7944 );
7945 })
7946 .log_err();
7947 }
7948 })
7949 .entry(log_breakpoint_msg, None, {
7950 let breakpoint = breakpoint.clone();
7951 let weak_editor = weak_editor.clone();
7952 move |window, cx| {
7953 weak_editor
7954 .update(cx, |this, cx| {
7955 this.add_edit_breakpoint_block(
7956 anchor,
7957 breakpoint.as_ref(),
7958 BreakpointPromptEditAction::Log,
7959 window,
7960 cx,
7961 );
7962 })
7963 .log_err();
7964 }
7965 })
7966 .entry(condition_breakpoint_msg, None, {
7967 let breakpoint = breakpoint.clone();
7968 let weak_editor = weak_editor.clone();
7969 move |window, cx| {
7970 weak_editor
7971 .update(cx, |this, cx| {
7972 this.add_edit_breakpoint_block(
7973 anchor,
7974 breakpoint.as_ref(),
7975 BreakpointPromptEditAction::Condition,
7976 window,
7977 cx,
7978 );
7979 })
7980 .log_err();
7981 }
7982 })
7983 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7984 weak_editor
7985 .update(cx, |this, cx| {
7986 this.add_edit_breakpoint_block(
7987 anchor,
7988 breakpoint.as_ref(),
7989 BreakpointPromptEditAction::HitCondition,
7990 window,
7991 cx,
7992 );
7993 })
7994 .log_err();
7995 })
7996 })
7997 }
7998
7999 fn render_breakpoint(
8000 &self,
8001 position: Anchor,
8002 row: DisplayRow,
8003 breakpoint: &Breakpoint,
8004 state: Option<BreakpointSessionState>,
8005 cx: &mut Context<Self>,
8006 ) -> IconButton {
8007 let is_rejected = state.is_some_and(|s| !s.verified);
8008 // Is it a breakpoint that shows up when hovering over gutter?
8009 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8010 (false, false),
8011 |PhantomBreakpointIndicator {
8012 is_active,
8013 display_row,
8014 collides_with_existing_breakpoint,
8015 }| {
8016 (
8017 is_active && display_row == row,
8018 collides_with_existing_breakpoint,
8019 )
8020 },
8021 );
8022
8023 let (color, icon) = {
8024 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8025 (false, false) => ui::IconName::DebugBreakpoint,
8026 (true, false) => ui::IconName::DebugLogBreakpoint,
8027 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8028 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8029 };
8030
8031 let color = if is_phantom {
8032 Color::Hint
8033 } else if is_rejected {
8034 Color::Disabled
8035 } else {
8036 Color::Debugger
8037 };
8038
8039 (color, icon)
8040 };
8041
8042 let breakpoint = Arc::from(breakpoint.clone());
8043
8044 let alt_as_text = gpui::Keystroke {
8045 modifiers: Modifiers::secondary_key(),
8046 ..Default::default()
8047 };
8048 let primary_action_text = if breakpoint.is_disabled() {
8049 "Enable breakpoint"
8050 } else if is_phantom && !collides_with_existing {
8051 "Set breakpoint"
8052 } else {
8053 "Unset breakpoint"
8054 };
8055 let focus_handle = self.focus_handle.clone();
8056
8057 let meta = if is_rejected {
8058 SharedString::from("No executable code is associated with this line.")
8059 } else if collides_with_existing && !breakpoint.is_disabled() {
8060 SharedString::from(format!(
8061 "{alt_as_text}-click to disable,\nright-click for more options."
8062 ))
8063 } else {
8064 SharedString::from("Right-click for more options.")
8065 };
8066 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8067 .icon_size(IconSize::XSmall)
8068 .size(ui::ButtonSize::None)
8069 .when(is_rejected, |this| {
8070 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8071 })
8072 .icon_color(color)
8073 .style(ButtonStyle::Transparent)
8074 .on_click(cx.listener({
8075 let breakpoint = breakpoint.clone();
8076
8077 move |editor, event: &ClickEvent, window, cx| {
8078 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8079 BreakpointEditAction::InvertState
8080 } else {
8081 BreakpointEditAction::Toggle
8082 };
8083
8084 window.focus(&editor.focus_handle(cx));
8085 editor.edit_breakpoint_at_anchor(
8086 position,
8087 breakpoint.as_ref().clone(),
8088 edit_action,
8089 cx,
8090 );
8091 }
8092 }))
8093 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8094 editor.set_breakpoint_context_menu(
8095 row,
8096 Some(position),
8097 event.down.position,
8098 window,
8099 cx,
8100 );
8101 }))
8102 .tooltip(move |window, cx| {
8103 Tooltip::with_meta_in(
8104 primary_action_text,
8105 Some(&ToggleBreakpoint),
8106 meta.clone(),
8107 &focus_handle,
8108 window,
8109 cx,
8110 )
8111 })
8112 }
8113
8114 fn build_tasks_context(
8115 project: &Entity<Project>,
8116 buffer: &Entity<Buffer>,
8117 buffer_row: u32,
8118 tasks: &Arc<RunnableTasks>,
8119 cx: &mut Context<Self>,
8120 ) -> Task<Option<task::TaskContext>> {
8121 let position = Point::new(buffer_row, tasks.column);
8122 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8123 let location = Location {
8124 buffer: buffer.clone(),
8125 range: range_start..range_start,
8126 };
8127 // Fill in the environmental variables from the tree-sitter captures
8128 let mut captured_task_variables = TaskVariables::default();
8129 for (capture_name, value) in tasks.extra_variables.clone() {
8130 captured_task_variables.insert(
8131 task::VariableName::Custom(capture_name.into()),
8132 value.clone(),
8133 );
8134 }
8135 project.update(cx, |project, cx| {
8136 project.task_store().update(cx, |task_store, cx| {
8137 task_store.task_context_for_location(captured_task_variables, location, cx)
8138 })
8139 })
8140 }
8141
8142 pub fn spawn_nearest_task(
8143 &mut self,
8144 action: &SpawnNearestTask,
8145 window: &mut Window,
8146 cx: &mut Context<Self>,
8147 ) {
8148 let Some((workspace, _)) = self.workspace.clone() else {
8149 return;
8150 };
8151 let Some(project) = self.project.clone() else {
8152 return;
8153 };
8154
8155 // Try to find a closest, enclosing node using tree-sitter that has a
8156 // task
8157 let Some((buffer, buffer_row, tasks)) = self
8158 .find_enclosing_node_task(cx)
8159 // Or find the task that's closest in row-distance.
8160 .or_else(|| self.find_closest_task(cx))
8161 else {
8162 return;
8163 };
8164
8165 let reveal_strategy = action.reveal;
8166 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8167 cx.spawn_in(window, async move |_, cx| {
8168 let context = task_context.await?;
8169 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8170
8171 let resolved = &mut resolved_task.resolved;
8172 resolved.reveal = reveal_strategy;
8173
8174 workspace
8175 .update_in(cx, |workspace, window, cx| {
8176 workspace.schedule_resolved_task(
8177 task_source_kind,
8178 resolved_task,
8179 false,
8180 window,
8181 cx,
8182 );
8183 })
8184 .ok()
8185 })
8186 .detach();
8187 }
8188
8189 fn find_closest_task(
8190 &mut self,
8191 cx: &mut Context<Self>,
8192 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8193 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8194
8195 let ((buffer_id, row), tasks) = self
8196 .tasks
8197 .iter()
8198 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8199
8200 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8201 let tasks = Arc::new(tasks.to_owned());
8202 Some((buffer, *row, tasks))
8203 }
8204
8205 fn find_enclosing_node_task(
8206 &mut self,
8207 cx: &mut Context<Self>,
8208 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8209 let snapshot = self.buffer.read(cx).snapshot(cx);
8210 let offset = self.selections.newest::<usize>(cx).head();
8211 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8212 let buffer_id = excerpt.buffer().remote_id();
8213
8214 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8215 let mut cursor = layer.node().walk();
8216
8217 while cursor.goto_first_child_for_byte(offset).is_some() {
8218 if cursor.node().end_byte() == offset {
8219 cursor.goto_next_sibling();
8220 }
8221 }
8222
8223 // Ascend to the smallest ancestor that contains the range and has a task.
8224 loop {
8225 let node = cursor.node();
8226 let node_range = node.byte_range();
8227 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8228
8229 // Check if this node contains our offset
8230 if node_range.start <= offset && node_range.end >= offset {
8231 // If it contains offset, check for task
8232 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8233 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8234 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8235 }
8236 }
8237
8238 if !cursor.goto_parent() {
8239 break;
8240 }
8241 }
8242 None
8243 }
8244
8245 fn render_run_indicator(
8246 &self,
8247 _style: &EditorStyle,
8248 is_active: bool,
8249 row: DisplayRow,
8250 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8251 cx: &mut Context<Self>,
8252 ) -> IconButton {
8253 let color = Color::Muted;
8254 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8255
8256 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
8257 .shape(ui::IconButtonShape::Square)
8258 .icon_size(IconSize::XSmall)
8259 .icon_color(color)
8260 .toggle_state(is_active)
8261 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8262 let quick_launch = e.down.button == MouseButton::Left;
8263 window.focus(&editor.focus_handle(cx));
8264 editor.toggle_code_actions(
8265 &ToggleCodeActions {
8266 deployed_from: Some(CodeActionSource::RunMenu(row)),
8267 quick_launch,
8268 },
8269 window,
8270 cx,
8271 );
8272 }))
8273 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8274 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
8275 }))
8276 }
8277
8278 pub fn context_menu_visible(&self) -> bool {
8279 !self.edit_prediction_preview_is_active()
8280 && self
8281 .context_menu
8282 .borrow()
8283 .as_ref()
8284 .map_or(false, |menu| menu.visible())
8285 }
8286
8287 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8288 self.context_menu
8289 .borrow()
8290 .as_ref()
8291 .map(|menu| menu.origin())
8292 }
8293
8294 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8295 self.context_menu_options = Some(options);
8296 }
8297
8298 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8299 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8300
8301 fn render_edit_prediction_popover(
8302 &mut self,
8303 text_bounds: &Bounds<Pixels>,
8304 content_origin: gpui::Point<Pixels>,
8305 right_margin: Pixels,
8306 editor_snapshot: &EditorSnapshot,
8307 visible_row_range: Range<DisplayRow>,
8308 scroll_top: f32,
8309 scroll_bottom: f32,
8310 line_layouts: &[LineWithInvisibles],
8311 line_height: Pixels,
8312 scroll_pixel_position: gpui::Point<Pixels>,
8313 newest_selection_head: Option<DisplayPoint>,
8314 editor_width: Pixels,
8315 style: &EditorStyle,
8316 window: &mut Window,
8317 cx: &mut App,
8318 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8319 if self.mode().is_minimap() {
8320 return None;
8321 }
8322 let active_inline_completion = self.active_inline_completion.as_ref()?;
8323
8324 if self.edit_prediction_visible_in_cursor_popover(true) {
8325 return None;
8326 }
8327
8328 match &active_inline_completion.completion {
8329 InlineCompletion::Move { target, .. } => {
8330 let target_display_point = target.to_display_point(editor_snapshot);
8331
8332 if self.edit_prediction_requires_modifier() {
8333 if !self.edit_prediction_preview_is_active() {
8334 return None;
8335 }
8336
8337 self.render_edit_prediction_modifier_jump_popover(
8338 text_bounds,
8339 content_origin,
8340 visible_row_range,
8341 line_layouts,
8342 line_height,
8343 scroll_pixel_position,
8344 newest_selection_head,
8345 target_display_point,
8346 window,
8347 cx,
8348 )
8349 } else {
8350 self.render_edit_prediction_eager_jump_popover(
8351 text_bounds,
8352 content_origin,
8353 editor_snapshot,
8354 visible_row_range,
8355 scroll_top,
8356 scroll_bottom,
8357 line_height,
8358 scroll_pixel_position,
8359 target_display_point,
8360 editor_width,
8361 window,
8362 cx,
8363 )
8364 }
8365 }
8366 InlineCompletion::Edit {
8367 display_mode: EditDisplayMode::Inline,
8368 ..
8369 } => None,
8370 InlineCompletion::Edit {
8371 display_mode: EditDisplayMode::TabAccept,
8372 edits,
8373 ..
8374 } => {
8375 let range = &edits.first()?.0;
8376 let target_display_point = range.end.to_display_point(editor_snapshot);
8377
8378 self.render_edit_prediction_end_of_line_popover(
8379 "Accept",
8380 editor_snapshot,
8381 visible_row_range,
8382 target_display_point,
8383 line_height,
8384 scroll_pixel_position,
8385 content_origin,
8386 editor_width,
8387 window,
8388 cx,
8389 )
8390 }
8391 InlineCompletion::Edit {
8392 edits,
8393 edit_preview,
8394 display_mode: EditDisplayMode::DiffPopover,
8395 snapshot,
8396 } => self.render_edit_prediction_diff_popover(
8397 text_bounds,
8398 content_origin,
8399 right_margin,
8400 editor_snapshot,
8401 visible_row_range,
8402 line_layouts,
8403 line_height,
8404 scroll_pixel_position,
8405 newest_selection_head,
8406 editor_width,
8407 style,
8408 edits,
8409 edit_preview,
8410 snapshot,
8411 window,
8412 cx,
8413 ),
8414 }
8415 }
8416
8417 fn render_edit_prediction_modifier_jump_popover(
8418 &mut self,
8419 text_bounds: &Bounds<Pixels>,
8420 content_origin: gpui::Point<Pixels>,
8421 visible_row_range: Range<DisplayRow>,
8422 line_layouts: &[LineWithInvisibles],
8423 line_height: Pixels,
8424 scroll_pixel_position: gpui::Point<Pixels>,
8425 newest_selection_head: Option<DisplayPoint>,
8426 target_display_point: DisplayPoint,
8427 window: &mut Window,
8428 cx: &mut App,
8429 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8430 let scrolled_content_origin =
8431 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8432
8433 const SCROLL_PADDING_Y: Pixels = px(12.);
8434
8435 if target_display_point.row() < visible_row_range.start {
8436 return self.render_edit_prediction_scroll_popover(
8437 |_| SCROLL_PADDING_Y,
8438 IconName::ArrowUp,
8439 visible_row_range,
8440 line_layouts,
8441 newest_selection_head,
8442 scrolled_content_origin,
8443 window,
8444 cx,
8445 );
8446 } else if target_display_point.row() >= visible_row_range.end {
8447 return self.render_edit_prediction_scroll_popover(
8448 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8449 IconName::ArrowDown,
8450 visible_row_range,
8451 line_layouts,
8452 newest_selection_head,
8453 scrolled_content_origin,
8454 window,
8455 cx,
8456 );
8457 }
8458
8459 const POLE_WIDTH: Pixels = px(2.);
8460
8461 let line_layout =
8462 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8463 let target_column = target_display_point.column() as usize;
8464
8465 let target_x = line_layout.x_for_index(target_column);
8466 let target_y =
8467 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8468
8469 let flag_on_right = target_x < text_bounds.size.width / 2.;
8470
8471 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8472 border_color.l += 0.001;
8473
8474 let mut element = v_flex()
8475 .items_end()
8476 .when(flag_on_right, |el| el.items_start())
8477 .child(if flag_on_right {
8478 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8479 .rounded_bl(px(0.))
8480 .rounded_tl(px(0.))
8481 .border_l_2()
8482 .border_color(border_color)
8483 } else {
8484 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8485 .rounded_br(px(0.))
8486 .rounded_tr(px(0.))
8487 .border_r_2()
8488 .border_color(border_color)
8489 })
8490 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8491 .into_any();
8492
8493 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8494
8495 let mut origin = scrolled_content_origin + point(target_x, target_y)
8496 - point(
8497 if flag_on_right {
8498 POLE_WIDTH
8499 } else {
8500 size.width - POLE_WIDTH
8501 },
8502 size.height - line_height,
8503 );
8504
8505 origin.x = origin.x.max(content_origin.x);
8506
8507 element.prepaint_at(origin, window, cx);
8508
8509 Some((element, origin))
8510 }
8511
8512 fn render_edit_prediction_scroll_popover(
8513 &mut self,
8514 to_y: impl Fn(Size<Pixels>) -> Pixels,
8515 scroll_icon: IconName,
8516 visible_row_range: Range<DisplayRow>,
8517 line_layouts: &[LineWithInvisibles],
8518 newest_selection_head: Option<DisplayPoint>,
8519 scrolled_content_origin: gpui::Point<Pixels>,
8520 window: &mut Window,
8521 cx: &mut App,
8522 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8523 let mut element = self
8524 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8525 .into_any();
8526
8527 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8528
8529 let cursor = newest_selection_head?;
8530 let cursor_row_layout =
8531 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8532 let cursor_column = cursor.column() as usize;
8533
8534 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8535
8536 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8537
8538 element.prepaint_at(origin, window, cx);
8539 Some((element, origin))
8540 }
8541
8542 fn render_edit_prediction_eager_jump_popover(
8543 &mut self,
8544 text_bounds: &Bounds<Pixels>,
8545 content_origin: gpui::Point<Pixels>,
8546 editor_snapshot: &EditorSnapshot,
8547 visible_row_range: Range<DisplayRow>,
8548 scroll_top: f32,
8549 scroll_bottom: f32,
8550 line_height: Pixels,
8551 scroll_pixel_position: gpui::Point<Pixels>,
8552 target_display_point: DisplayPoint,
8553 editor_width: Pixels,
8554 window: &mut Window,
8555 cx: &mut App,
8556 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8557 if target_display_point.row().as_f32() < scroll_top {
8558 let mut element = self
8559 .render_edit_prediction_line_popover(
8560 "Jump to Edit",
8561 Some(IconName::ArrowUp),
8562 window,
8563 cx,
8564 )?
8565 .into_any();
8566
8567 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8568 let offset = point(
8569 (text_bounds.size.width - size.width) / 2.,
8570 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8571 );
8572
8573 let origin = text_bounds.origin + offset;
8574 element.prepaint_at(origin, window, cx);
8575 Some((element, origin))
8576 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8577 let mut element = self
8578 .render_edit_prediction_line_popover(
8579 "Jump to Edit",
8580 Some(IconName::ArrowDown),
8581 window,
8582 cx,
8583 )?
8584 .into_any();
8585
8586 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8587 let offset = point(
8588 (text_bounds.size.width - size.width) / 2.,
8589 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8590 );
8591
8592 let origin = text_bounds.origin + offset;
8593 element.prepaint_at(origin, window, cx);
8594 Some((element, origin))
8595 } else {
8596 self.render_edit_prediction_end_of_line_popover(
8597 "Jump to Edit",
8598 editor_snapshot,
8599 visible_row_range,
8600 target_display_point,
8601 line_height,
8602 scroll_pixel_position,
8603 content_origin,
8604 editor_width,
8605 window,
8606 cx,
8607 )
8608 }
8609 }
8610
8611 fn render_edit_prediction_end_of_line_popover(
8612 self: &mut Editor,
8613 label: &'static str,
8614 editor_snapshot: &EditorSnapshot,
8615 visible_row_range: Range<DisplayRow>,
8616 target_display_point: DisplayPoint,
8617 line_height: Pixels,
8618 scroll_pixel_position: gpui::Point<Pixels>,
8619 content_origin: gpui::Point<Pixels>,
8620 editor_width: Pixels,
8621 window: &mut Window,
8622 cx: &mut App,
8623 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8624 let target_line_end = DisplayPoint::new(
8625 target_display_point.row(),
8626 editor_snapshot.line_len(target_display_point.row()),
8627 );
8628
8629 let mut element = self
8630 .render_edit_prediction_line_popover(label, None, window, cx)?
8631 .into_any();
8632
8633 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8634
8635 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8636
8637 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8638 let mut origin = start_point
8639 + line_origin
8640 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8641 origin.x = origin.x.max(content_origin.x);
8642
8643 let max_x = content_origin.x + editor_width - size.width;
8644
8645 if origin.x > max_x {
8646 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8647
8648 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8649 origin.y += offset;
8650 IconName::ArrowUp
8651 } else {
8652 origin.y -= offset;
8653 IconName::ArrowDown
8654 };
8655
8656 element = self
8657 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8658 .into_any();
8659
8660 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8661
8662 origin.x = content_origin.x + editor_width - size.width - px(2.);
8663 }
8664
8665 element.prepaint_at(origin, window, cx);
8666 Some((element, origin))
8667 }
8668
8669 fn render_edit_prediction_diff_popover(
8670 self: &Editor,
8671 text_bounds: &Bounds<Pixels>,
8672 content_origin: gpui::Point<Pixels>,
8673 right_margin: Pixels,
8674 editor_snapshot: &EditorSnapshot,
8675 visible_row_range: Range<DisplayRow>,
8676 line_layouts: &[LineWithInvisibles],
8677 line_height: Pixels,
8678 scroll_pixel_position: gpui::Point<Pixels>,
8679 newest_selection_head: Option<DisplayPoint>,
8680 editor_width: Pixels,
8681 style: &EditorStyle,
8682 edits: &Vec<(Range<Anchor>, String)>,
8683 edit_preview: &Option<language::EditPreview>,
8684 snapshot: &language::BufferSnapshot,
8685 window: &mut Window,
8686 cx: &mut App,
8687 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8688 let edit_start = edits
8689 .first()
8690 .unwrap()
8691 .0
8692 .start
8693 .to_display_point(editor_snapshot);
8694 let edit_end = edits
8695 .last()
8696 .unwrap()
8697 .0
8698 .end
8699 .to_display_point(editor_snapshot);
8700
8701 let is_visible = visible_row_range.contains(&edit_start.row())
8702 || visible_row_range.contains(&edit_end.row());
8703 if !is_visible {
8704 return None;
8705 }
8706
8707 let highlighted_edits =
8708 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8709
8710 let styled_text = highlighted_edits.to_styled_text(&style.text);
8711 let line_count = highlighted_edits.text.lines().count();
8712
8713 const BORDER_WIDTH: Pixels = px(1.);
8714
8715 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8716 let has_keybind = keybind.is_some();
8717
8718 let mut element = h_flex()
8719 .items_start()
8720 .child(
8721 h_flex()
8722 .bg(cx.theme().colors().editor_background)
8723 .border(BORDER_WIDTH)
8724 .shadow_xs()
8725 .border_color(cx.theme().colors().border)
8726 .rounded_l_lg()
8727 .when(line_count > 1, |el| el.rounded_br_lg())
8728 .pr_1()
8729 .child(styled_text),
8730 )
8731 .child(
8732 h_flex()
8733 .h(line_height + BORDER_WIDTH * 2.)
8734 .px_1p5()
8735 .gap_1()
8736 // Workaround: For some reason, there's a gap if we don't do this
8737 .ml(-BORDER_WIDTH)
8738 .shadow(vec![gpui::BoxShadow {
8739 color: gpui::black().opacity(0.05),
8740 offset: point(px(1.), px(1.)),
8741 blur_radius: px(2.),
8742 spread_radius: px(0.),
8743 }])
8744 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8745 .border(BORDER_WIDTH)
8746 .border_color(cx.theme().colors().border)
8747 .rounded_r_lg()
8748 .id("edit_prediction_diff_popover_keybind")
8749 .when(!has_keybind, |el| {
8750 let status_colors = cx.theme().status();
8751
8752 el.bg(status_colors.error_background)
8753 .border_color(status_colors.error.opacity(0.6))
8754 .child(Icon::new(IconName::Info).color(Color::Error))
8755 .cursor_default()
8756 .hoverable_tooltip(move |_window, cx| {
8757 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8758 })
8759 })
8760 .children(keybind),
8761 )
8762 .into_any();
8763
8764 let longest_row =
8765 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8766 let longest_line_width = if visible_row_range.contains(&longest_row) {
8767 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8768 } else {
8769 layout_line(
8770 longest_row,
8771 editor_snapshot,
8772 style,
8773 editor_width,
8774 |_| false,
8775 window,
8776 cx,
8777 )
8778 .width
8779 };
8780
8781 let viewport_bounds =
8782 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8783 right: -right_margin,
8784 ..Default::default()
8785 });
8786
8787 let x_after_longest =
8788 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8789 - scroll_pixel_position.x;
8790
8791 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8792
8793 // Fully visible if it can be displayed within the window (allow overlapping other
8794 // panes). However, this is only allowed if the popover starts within text_bounds.
8795 let can_position_to_the_right = x_after_longest < text_bounds.right()
8796 && x_after_longest + element_bounds.width < viewport_bounds.right();
8797
8798 let mut origin = if can_position_to_the_right {
8799 point(
8800 x_after_longest,
8801 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8802 - scroll_pixel_position.y,
8803 )
8804 } else {
8805 let cursor_row = newest_selection_head.map(|head| head.row());
8806 let above_edit = edit_start
8807 .row()
8808 .0
8809 .checked_sub(line_count as u32)
8810 .map(DisplayRow);
8811 let below_edit = Some(edit_end.row() + 1);
8812 let above_cursor =
8813 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8814 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8815
8816 // Place the edit popover adjacent to the edit if there is a location
8817 // available that is onscreen and does not obscure the cursor. Otherwise,
8818 // place it adjacent to the cursor.
8819 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8820 .into_iter()
8821 .flatten()
8822 .find(|&start_row| {
8823 let end_row = start_row + line_count as u32;
8824 visible_row_range.contains(&start_row)
8825 && visible_row_range.contains(&end_row)
8826 && cursor_row.map_or(true, |cursor_row| {
8827 !((start_row..end_row).contains(&cursor_row))
8828 })
8829 })?;
8830
8831 content_origin
8832 + point(
8833 -scroll_pixel_position.x,
8834 row_target.as_f32() * line_height - scroll_pixel_position.y,
8835 )
8836 };
8837
8838 origin.x -= BORDER_WIDTH;
8839
8840 window.defer_draw(element, origin, 1);
8841
8842 // Do not return an element, since it will already be drawn due to defer_draw.
8843 None
8844 }
8845
8846 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8847 px(30.)
8848 }
8849
8850 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8851 if self.read_only(cx) {
8852 cx.theme().players().read_only()
8853 } else {
8854 self.style.as_ref().unwrap().local_player
8855 }
8856 }
8857
8858 fn render_edit_prediction_accept_keybind(
8859 &self,
8860 window: &mut Window,
8861 cx: &App,
8862 ) -> Option<AnyElement> {
8863 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8864 let accept_keystroke = accept_binding.keystroke()?;
8865
8866 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8867
8868 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8869 Color::Accent
8870 } else {
8871 Color::Muted
8872 };
8873
8874 h_flex()
8875 .px_0p5()
8876 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8877 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8878 .text_size(TextSize::XSmall.rems(cx))
8879 .child(h_flex().children(ui::render_modifiers(
8880 &accept_keystroke.modifiers,
8881 PlatformStyle::platform(),
8882 Some(modifiers_color),
8883 Some(IconSize::XSmall.rems().into()),
8884 true,
8885 )))
8886 .when(is_platform_style_mac, |parent| {
8887 parent.child(accept_keystroke.key.clone())
8888 })
8889 .when(!is_platform_style_mac, |parent| {
8890 parent.child(
8891 Key::new(
8892 util::capitalize(&accept_keystroke.key),
8893 Some(Color::Default),
8894 )
8895 .size(Some(IconSize::XSmall.rems().into())),
8896 )
8897 })
8898 .into_any()
8899 .into()
8900 }
8901
8902 fn render_edit_prediction_line_popover(
8903 &self,
8904 label: impl Into<SharedString>,
8905 icon: Option<IconName>,
8906 window: &mut Window,
8907 cx: &App,
8908 ) -> Option<Stateful<Div>> {
8909 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8910
8911 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8912 let has_keybind = keybind.is_some();
8913
8914 let result = h_flex()
8915 .id("ep-line-popover")
8916 .py_0p5()
8917 .pl_1()
8918 .pr(padding_right)
8919 .gap_1()
8920 .rounded_md()
8921 .border_1()
8922 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8923 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8924 .shadow_xs()
8925 .when(!has_keybind, |el| {
8926 let status_colors = cx.theme().status();
8927
8928 el.bg(status_colors.error_background)
8929 .border_color(status_colors.error.opacity(0.6))
8930 .pl_2()
8931 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8932 .cursor_default()
8933 .hoverable_tooltip(move |_window, cx| {
8934 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8935 })
8936 })
8937 .children(keybind)
8938 .child(
8939 Label::new(label)
8940 .size(LabelSize::Small)
8941 .when(!has_keybind, |el| {
8942 el.color(cx.theme().status().error.into()).strikethrough()
8943 }),
8944 )
8945 .when(!has_keybind, |el| {
8946 el.child(
8947 h_flex().ml_1().child(
8948 Icon::new(IconName::Info)
8949 .size(IconSize::Small)
8950 .color(cx.theme().status().error.into()),
8951 ),
8952 )
8953 })
8954 .when_some(icon, |element, icon| {
8955 element.child(
8956 div()
8957 .mt(px(1.5))
8958 .child(Icon::new(icon).size(IconSize::Small)),
8959 )
8960 });
8961
8962 Some(result)
8963 }
8964
8965 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8966 let accent_color = cx.theme().colors().text_accent;
8967 let editor_bg_color = cx.theme().colors().editor_background;
8968 editor_bg_color.blend(accent_color.opacity(0.1))
8969 }
8970
8971 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8972 let accent_color = cx.theme().colors().text_accent;
8973 let editor_bg_color = cx.theme().colors().editor_background;
8974 editor_bg_color.blend(accent_color.opacity(0.6))
8975 }
8976
8977 fn render_edit_prediction_cursor_popover(
8978 &self,
8979 min_width: Pixels,
8980 max_width: Pixels,
8981 cursor_point: Point,
8982 style: &EditorStyle,
8983 accept_keystroke: Option<&gpui::Keystroke>,
8984 _window: &Window,
8985 cx: &mut Context<Editor>,
8986 ) -> Option<AnyElement> {
8987 let provider = self.edit_prediction_provider.as_ref()?;
8988
8989 if provider.provider.needs_terms_acceptance(cx) {
8990 return Some(
8991 h_flex()
8992 .min_w(min_width)
8993 .flex_1()
8994 .px_2()
8995 .py_1()
8996 .gap_3()
8997 .elevation_2(cx)
8998 .hover(|style| style.bg(cx.theme().colors().element_hover))
8999 .id("accept-terms")
9000 .cursor_pointer()
9001 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
9002 .on_click(cx.listener(|this, _event, window, cx| {
9003 cx.stop_propagation();
9004 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
9005 window.dispatch_action(
9006 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
9007 cx,
9008 );
9009 }))
9010 .child(
9011 h_flex()
9012 .flex_1()
9013 .gap_2()
9014 .child(Icon::new(IconName::ZedPredict))
9015 .child(Label::new("Accept Terms of Service"))
9016 .child(div().w_full())
9017 .child(
9018 Icon::new(IconName::ArrowUpRight)
9019 .color(Color::Muted)
9020 .size(IconSize::Small),
9021 )
9022 .into_any_element(),
9023 )
9024 .into_any(),
9025 );
9026 }
9027
9028 let is_refreshing = provider.provider.is_refreshing(cx);
9029
9030 fn pending_completion_container() -> Div {
9031 h_flex()
9032 .h_full()
9033 .flex_1()
9034 .gap_2()
9035 .child(Icon::new(IconName::ZedPredict))
9036 }
9037
9038 let completion = match &self.active_inline_completion {
9039 Some(prediction) => {
9040 if !self.has_visible_completions_menu() {
9041 const RADIUS: Pixels = px(6.);
9042 const BORDER_WIDTH: Pixels = px(1.);
9043
9044 return Some(
9045 h_flex()
9046 .elevation_2(cx)
9047 .border(BORDER_WIDTH)
9048 .border_color(cx.theme().colors().border)
9049 .when(accept_keystroke.is_none(), |el| {
9050 el.border_color(cx.theme().status().error)
9051 })
9052 .rounded(RADIUS)
9053 .rounded_tl(px(0.))
9054 .overflow_hidden()
9055 .child(div().px_1p5().child(match &prediction.completion {
9056 InlineCompletion::Move { target, snapshot } => {
9057 use text::ToPoint as _;
9058 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
9059 {
9060 Icon::new(IconName::ZedPredictDown)
9061 } else {
9062 Icon::new(IconName::ZedPredictUp)
9063 }
9064 }
9065 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
9066 }))
9067 .child(
9068 h_flex()
9069 .gap_1()
9070 .py_1()
9071 .px_2()
9072 .rounded_r(RADIUS - BORDER_WIDTH)
9073 .border_l_1()
9074 .border_color(cx.theme().colors().border)
9075 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9076 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9077 el.child(
9078 Label::new("Hold")
9079 .size(LabelSize::Small)
9080 .when(accept_keystroke.is_none(), |el| {
9081 el.strikethrough()
9082 })
9083 .line_height_style(LineHeightStyle::UiLabel),
9084 )
9085 })
9086 .id("edit_prediction_cursor_popover_keybind")
9087 .when(accept_keystroke.is_none(), |el| {
9088 let status_colors = cx.theme().status();
9089
9090 el.bg(status_colors.error_background)
9091 .border_color(status_colors.error.opacity(0.6))
9092 .child(Icon::new(IconName::Info).color(Color::Error))
9093 .cursor_default()
9094 .hoverable_tooltip(move |_window, cx| {
9095 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9096 .into()
9097 })
9098 })
9099 .when_some(
9100 accept_keystroke.as_ref(),
9101 |el, accept_keystroke| {
9102 el.child(h_flex().children(ui::render_modifiers(
9103 &accept_keystroke.modifiers,
9104 PlatformStyle::platform(),
9105 Some(Color::Default),
9106 Some(IconSize::XSmall.rems().into()),
9107 false,
9108 )))
9109 },
9110 ),
9111 )
9112 .into_any(),
9113 );
9114 }
9115
9116 self.render_edit_prediction_cursor_popover_preview(
9117 prediction,
9118 cursor_point,
9119 style,
9120 cx,
9121 )?
9122 }
9123
9124 None if is_refreshing => match &self.stale_inline_completion_in_menu {
9125 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9126 stale_completion,
9127 cursor_point,
9128 style,
9129 cx,
9130 )?,
9131
9132 None => {
9133 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
9134 }
9135 },
9136
9137 None => pending_completion_container().child(Label::new("No Prediction")),
9138 };
9139
9140 let completion = if is_refreshing {
9141 completion
9142 .with_animation(
9143 "loading-completion",
9144 Animation::new(Duration::from_secs(2))
9145 .repeat()
9146 .with_easing(pulsating_between(0.4, 0.8)),
9147 |label, delta| label.opacity(delta),
9148 )
9149 .into_any_element()
9150 } else {
9151 completion.into_any_element()
9152 };
9153
9154 let has_completion = self.active_inline_completion.is_some();
9155
9156 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9157 Some(
9158 h_flex()
9159 .min_w(min_width)
9160 .max_w(max_width)
9161 .flex_1()
9162 .elevation_2(cx)
9163 .border_color(cx.theme().colors().border)
9164 .child(
9165 div()
9166 .flex_1()
9167 .py_1()
9168 .px_2()
9169 .overflow_hidden()
9170 .child(completion),
9171 )
9172 .when_some(accept_keystroke, |el, accept_keystroke| {
9173 if !accept_keystroke.modifiers.modified() {
9174 return el;
9175 }
9176
9177 el.child(
9178 h_flex()
9179 .h_full()
9180 .border_l_1()
9181 .rounded_r_lg()
9182 .border_color(cx.theme().colors().border)
9183 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9184 .gap_1()
9185 .py_1()
9186 .px_2()
9187 .child(
9188 h_flex()
9189 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9190 .when(is_platform_style_mac, |parent| parent.gap_1())
9191 .child(h_flex().children(ui::render_modifiers(
9192 &accept_keystroke.modifiers,
9193 PlatformStyle::platform(),
9194 Some(if !has_completion {
9195 Color::Muted
9196 } else {
9197 Color::Default
9198 }),
9199 None,
9200 false,
9201 ))),
9202 )
9203 .child(Label::new("Preview").into_any_element())
9204 .opacity(if has_completion { 1.0 } else { 0.4 }),
9205 )
9206 })
9207 .into_any(),
9208 )
9209 }
9210
9211 fn render_edit_prediction_cursor_popover_preview(
9212 &self,
9213 completion: &InlineCompletionState,
9214 cursor_point: Point,
9215 style: &EditorStyle,
9216 cx: &mut Context<Editor>,
9217 ) -> Option<Div> {
9218 use text::ToPoint as _;
9219
9220 fn render_relative_row_jump(
9221 prefix: impl Into<String>,
9222 current_row: u32,
9223 target_row: u32,
9224 ) -> Div {
9225 let (row_diff, arrow) = if target_row < current_row {
9226 (current_row - target_row, IconName::ArrowUp)
9227 } else {
9228 (target_row - current_row, IconName::ArrowDown)
9229 };
9230
9231 h_flex()
9232 .child(
9233 Label::new(format!("{}{}", prefix.into(), row_diff))
9234 .color(Color::Muted)
9235 .size(LabelSize::Small),
9236 )
9237 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9238 }
9239
9240 match &completion.completion {
9241 InlineCompletion::Move {
9242 target, snapshot, ..
9243 } => Some(
9244 h_flex()
9245 .px_2()
9246 .gap_2()
9247 .flex_1()
9248 .child(
9249 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9250 Icon::new(IconName::ZedPredictDown)
9251 } else {
9252 Icon::new(IconName::ZedPredictUp)
9253 },
9254 )
9255 .child(Label::new("Jump to Edit")),
9256 ),
9257
9258 InlineCompletion::Edit {
9259 edits,
9260 edit_preview,
9261 snapshot,
9262 display_mode: _,
9263 } => {
9264 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9265
9266 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
9267 &snapshot,
9268 &edits,
9269 edit_preview.as_ref()?,
9270 true,
9271 cx,
9272 )
9273 .first_line_preview();
9274
9275 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9276 .with_default_highlights(&style.text, highlighted_edits.highlights);
9277
9278 let preview = h_flex()
9279 .gap_1()
9280 .min_w_16()
9281 .child(styled_text)
9282 .when(has_more_lines, |parent| parent.child("…"));
9283
9284 let left = if first_edit_row != cursor_point.row {
9285 render_relative_row_jump("", cursor_point.row, first_edit_row)
9286 .into_any_element()
9287 } else {
9288 Icon::new(IconName::ZedPredict).into_any_element()
9289 };
9290
9291 Some(
9292 h_flex()
9293 .h_full()
9294 .flex_1()
9295 .gap_2()
9296 .pr_1()
9297 .overflow_x_hidden()
9298 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9299 .child(left)
9300 .child(preview),
9301 )
9302 }
9303 }
9304 }
9305
9306 pub fn render_context_menu(
9307 &self,
9308 style: &EditorStyle,
9309 max_height_in_lines: u32,
9310 window: &mut Window,
9311 cx: &mut Context<Editor>,
9312 ) -> Option<AnyElement> {
9313 let menu = self.context_menu.borrow();
9314 let menu = menu.as_ref()?;
9315 if !menu.visible() {
9316 return None;
9317 };
9318 Some(menu.render(style, max_height_in_lines, window, cx))
9319 }
9320
9321 fn render_context_menu_aside(
9322 &mut self,
9323 max_size: Size<Pixels>,
9324 window: &mut Window,
9325 cx: &mut Context<Editor>,
9326 ) -> Option<AnyElement> {
9327 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9328 if menu.visible() {
9329 menu.render_aside(max_size, window, cx)
9330 } else {
9331 None
9332 }
9333 })
9334 }
9335
9336 fn hide_context_menu(
9337 &mut self,
9338 window: &mut Window,
9339 cx: &mut Context<Self>,
9340 ) -> Option<CodeContextMenu> {
9341 cx.notify();
9342 self.completion_tasks.clear();
9343 let context_menu = self.context_menu.borrow_mut().take();
9344 self.stale_inline_completion_in_menu.take();
9345 self.update_visible_inline_completion(window, cx);
9346 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9347 if let Some(completion_provider) = &self.completion_provider {
9348 completion_provider.selection_changed(None, window, cx);
9349 }
9350 }
9351 context_menu
9352 }
9353
9354 fn show_snippet_choices(
9355 &mut self,
9356 choices: &Vec<String>,
9357 selection: Range<Anchor>,
9358 cx: &mut Context<Self>,
9359 ) {
9360 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9361 (Some(a), Some(b)) if a == b => a,
9362 _ => {
9363 log::error!("expected anchor range to have matching buffer IDs");
9364 return;
9365 }
9366 };
9367 let multi_buffer = self.buffer().read(cx);
9368 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9369 return;
9370 };
9371
9372 let id = post_inc(&mut self.next_completion_id);
9373 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9374 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9375 CompletionsMenu::new_snippet_choices(
9376 id,
9377 true,
9378 choices,
9379 selection,
9380 buffer,
9381 snippet_sort_order,
9382 ),
9383 ));
9384 }
9385
9386 pub fn insert_snippet(
9387 &mut self,
9388 insertion_ranges: &[Range<usize>],
9389 snippet: Snippet,
9390 window: &mut Window,
9391 cx: &mut Context<Self>,
9392 ) -> Result<()> {
9393 struct Tabstop<T> {
9394 is_end_tabstop: bool,
9395 ranges: Vec<Range<T>>,
9396 choices: Option<Vec<String>>,
9397 }
9398
9399 let tabstops = self.buffer.update(cx, |buffer, cx| {
9400 let snippet_text: Arc<str> = snippet.text.clone().into();
9401 let edits = insertion_ranges
9402 .iter()
9403 .cloned()
9404 .map(|range| (range, snippet_text.clone()));
9405 let autoindent_mode = AutoindentMode::Block {
9406 original_indent_columns: Vec::new(),
9407 };
9408 buffer.edit(edits, Some(autoindent_mode), cx);
9409
9410 let snapshot = &*buffer.read(cx);
9411 let snippet = &snippet;
9412 snippet
9413 .tabstops
9414 .iter()
9415 .map(|tabstop| {
9416 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9417 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9418 });
9419 let mut tabstop_ranges = tabstop
9420 .ranges
9421 .iter()
9422 .flat_map(|tabstop_range| {
9423 let mut delta = 0_isize;
9424 insertion_ranges.iter().map(move |insertion_range| {
9425 let insertion_start = insertion_range.start as isize + delta;
9426 delta +=
9427 snippet.text.len() as isize - insertion_range.len() as isize;
9428
9429 let start = ((insertion_start + tabstop_range.start) as usize)
9430 .min(snapshot.len());
9431 let end = ((insertion_start + tabstop_range.end) as usize)
9432 .min(snapshot.len());
9433 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9434 })
9435 })
9436 .collect::<Vec<_>>();
9437 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9438
9439 Tabstop {
9440 is_end_tabstop,
9441 ranges: tabstop_ranges,
9442 choices: tabstop.choices.clone(),
9443 }
9444 })
9445 .collect::<Vec<_>>()
9446 });
9447 if let Some(tabstop) = tabstops.first() {
9448 self.change_selections(Default::default(), window, cx, |s| {
9449 // Reverse order so that the first range is the newest created selection.
9450 // Completions will use it and autoscroll will prioritize it.
9451 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9452 });
9453
9454 if let Some(choices) = &tabstop.choices {
9455 if let Some(selection) = tabstop.ranges.first() {
9456 self.show_snippet_choices(choices, selection.clone(), cx)
9457 }
9458 }
9459
9460 // If we're already at the last tabstop and it's at the end of the snippet,
9461 // we're done, we don't need to keep the state around.
9462 if !tabstop.is_end_tabstop {
9463 let choices = tabstops
9464 .iter()
9465 .map(|tabstop| tabstop.choices.clone())
9466 .collect();
9467
9468 let ranges = tabstops
9469 .into_iter()
9470 .map(|tabstop| tabstop.ranges)
9471 .collect::<Vec<_>>();
9472
9473 self.snippet_stack.push(SnippetState {
9474 active_index: 0,
9475 ranges,
9476 choices,
9477 });
9478 }
9479
9480 // Check whether the just-entered snippet ends with an auto-closable bracket.
9481 if self.autoclose_regions.is_empty() {
9482 let snapshot = self.buffer.read(cx).snapshot(cx);
9483 for selection in &mut self.selections.all::<Point>(cx) {
9484 let selection_head = selection.head();
9485 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9486 continue;
9487 };
9488
9489 let mut bracket_pair = None;
9490 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9491 let prev_chars = snapshot
9492 .reversed_chars_at(selection_head)
9493 .collect::<String>();
9494 for (pair, enabled) in scope.brackets() {
9495 if enabled
9496 && pair.close
9497 && prev_chars.starts_with(pair.start.as_str())
9498 && next_chars.starts_with(pair.end.as_str())
9499 {
9500 bracket_pair = Some(pair.clone());
9501 break;
9502 }
9503 }
9504 if let Some(pair) = bracket_pair {
9505 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9506 let autoclose_enabled =
9507 self.use_autoclose && snapshot_settings.use_autoclose;
9508 if autoclose_enabled {
9509 let start = snapshot.anchor_after(selection_head);
9510 let end = snapshot.anchor_after(selection_head);
9511 self.autoclose_regions.push(AutocloseRegion {
9512 selection_id: selection.id,
9513 range: start..end,
9514 pair,
9515 });
9516 }
9517 }
9518 }
9519 }
9520 }
9521 Ok(())
9522 }
9523
9524 pub fn move_to_next_snippet_tabstop(
9525 &mut self,
9526 window: &mut Window,
9527 cx: &mut Context<Self>,
9528 ) -> bool {
9529 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9530 }
9531
9532 pub fn move_to_prev_snippet_tabstop(
9533 &mut self,
9534 window: &mut Window,
9535 cx: &mut Context<Self>,
9536 ) -> bool {
9537 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9538 }
9539
9540 pub fn move_to_snippet_tabstop(
9541 &mut self,
9542 bias: Bias,
9543 window: &mut Window,
9544 cx: &mut Context<Self>,
9545 ) -> bool {
9546 if let Some(mut snippet) = self.snippet_stack.pop() {
9547 match bias {
9548 Bias::Left => {
9549 if snippet.active_index > 0 {
9550 snippet.active_index -= 1;
9551 } else {
9552 self.snippet_stack.push(snippet);
9553 return false;
9554 }
9555 }
9556 Bias::Right => {
9557 if snippet.active_index + 1 < snippet.ranges.len() {
9558 snippet.active_index += 1;
9559 } else {
9560 self.snippet_stack.push(snippet);
9561 return false;
9562 }
9563 }
9564 }
9565 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9566 self.change_selections(Default::default(), window, cx, |s| {
9567 // Reverse order so that the first range is the newest created selection.
9568 // Completions will use it and autoscroll will prioritize it.
9569 s.select_ranges(current_ranges.iter().rev().cloned())
9570 });
9571
9572 if let Some(choices) = &snippet.choices[snippet.active_index] {
9573 if let Some(selection) = current_ranges.first() {
9574 self.show_snippet_choices(&choices, selection.clone(), cx);
9575 }
9576 }
9577
9578 // If snippet state is not at the last tabstop, push it back on the stack
9579 if snippet.active_index + 1 < snippet.ranges.len() {
9580 self.snippet_stack.push(snippet);
9581 }
9582 return true;
9583 }
9584 }
9585
9586 false
9587 }
9588
9589 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9590 self.transact(window, cx, |this, window, cx| {
9591 this.select_all(&SelectAll, window, cx);
9592 this.insert("", window, cx);
9593 });
9594 }
9595
9596 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9597 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9598 self.transact(window, cx, |this, window, cx| {
9599 this.select_autoclose_pair(window, cx);
9600 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9601 if !this.linked_edit_ranges.is_empty() {
9602 let selections = this.selections.all::<MultiBufferPoint>(cx);
9603 let snapshot = this.buffer.read(cx).snapshot(cx);
9604
9605 for selection in selections.iter() {
9606 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9607 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9608 if selection_start.buffer_id != selection_end.buffer_id {
9609 continue;
9610 }
9611 if let Some(ranges) =
9612 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9613 {
9614 for (buffer, entries) in ranges {
9615 linked_ranges.entry(buffer).or_default().extend(entries);
9616 }
9617 }
9618 }
9619 }
9620
9621 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9622 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9623 for selection in &mut selections {
9624 if selection.is_empty() {
9625 let old_head = selection.head();
9626 let mut new_head =
9627 movement::left(&display_map, old_head.to_display_point(&display_map))
9628 .to_point(&display_map);
9629 if let Some((buffer, line_buffer_range)) = display_map
9630 .buffer_snapshot
9631 .buffer_line_for_row(MultiBufferRow(old_head.row))
9632 {
9633 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9634 let indent_len = match indent_size.kind {
9635 IndentKind::Space => {
9636 buffer.settings_at(line_buffer_range.start, cx).tab_size
9637 }
9638 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9639 };
9640 if old_head.column <= indent_size.len && old_head.column > 0 {
9641 let indent_len = indent_len.get();
9642 new_head = cmp::min(
9643 new_head,
9644 MultiBufferPoint::new(
9645 old_head.row,
9646 ((old_head.column - 1) / indent_len) * indent_len,
9647 ),
9648 );
9649 }
9650 }
9651
9652 selection.set_head(new_head, SelectionGoal::None);
9653 }
9654 }
9655
9656 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9657 this.insert("", window, cx);
9658 let empty_str: Arc<str> = Arc::from("");
9659 for (buffer, edits) in linked_ranges {
9660 let snapshot = buffer.read(cx).snapshot();
9661 use text::ToPoint as TP;
9662
9663 let edits = edits
9664 .into_iter()
9665 .map(|range| {
9666 let end_point = TP::to_point(&range.end, &snapshot);
9667 let mut start_point = TP::to_point(&range.start, &snapshot);
9668
9669 if end_point == start_point {
9670 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9671 .saturating_sub(1);
9672 start_point =
9673 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9674 };
9675
9676 (start_point..end_point, empty_str.clone())
9677 })
9678 .sorted_by_key(|(range, _)| range.start)
9679 .collect::<Vec<_>>();
9680 buffer.update(cx, |this, cx| {
9681 this.edit(edits, None, cx);
9682 })
9683 }
9684 this.refresh_inline_completion(true, false, window, cx);
9685 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9686 });
9687 }
9688
9689 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9690 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9691 self.transact(window, cx, |this, window, cx| {
9692 this.change_selections(Default::default(), window, cx, |s| {
9693 s.move_with(|map, selection| {
9694 if selection.is_empty() {
9695 let cursor = movement::right(map, selection.head());
9696 selection.end = cursor;
9697 selection.reversed = true;
9698 selection.goal = SelectionGoal::None;
9699 }
9700 })
9701 });
9702 this.insert("", window, cx);
9703 this.refresh_inline_completion(true, false, window, cx);
9704 });
9705 }
9706
9707 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9708 if self.mode.is_single_line() {
9709 cx.propagate();
9710 return;
9711 }
9712
9713 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9714 if self.move_to_prev_snippet_tabstop(window, cx) {
9715 return;
9716 }
9717 self.outdent(&Outdent, window, cx);
9718 }
9719
9720 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9721 if self.mode.is_single_line() {
9722 cx.propagate();
9723 return;
9724 }
9725
9726 if self.move_to_next_snippet_tabstop(window, cx) {
9727 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9728 return;
9729 }
9730 if self.read_only(cx) {
9731 return;
9732 }
9733 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9734 let mut selections = self.selections.all_adjusted(cx);
9735 let buffer = self.buffer.read(cx);
9736 let snapshot = buffer.snapshot(cx);
9737 let rows_iter = selections.iter().map(|s| s.head().row);
9738 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9739
9740 let has_some_cursor_in_whitespace = selections
9741 .iter()
9742 .filter(|selection| selection.is_empty())
9743 .any(|selection| {
9744 let cursor = selection.head();
9745 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9746 cursor.column < current_indent.len
9747 });
9748
9749 let mut edits = Vec::new();
9750 let mut prev_edited_row = 0;
9751 let mut row_delta = 0;
9752 for selection in &mut selections {
9753 if selection.start.row != prev_edited_row {
9754 row_delta = 0;
9755 }
9756 prev_edited_row = selection.end.row;
9757
9758 // If the selection is non-empty, then increase the indentation of the selected lines.
9759 if !selection.is_empty() {
9760 row_delta =
9761 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9762 continue;
9763 }
9764
9765 let cursor = selection.head();
9766 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9767 if let Some(suggested_indent) =
9768 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9769 {
9770 // Don't do anything if already at suggested indent
9771 // and there is any other cursor which is not
9772 if has_some_cursor_in_whitespace
9773 && cursor.column == current_indent.len
9774 && current_indent.len == suggested_indent.len
9775 {
9776 continue;
9777 }
9778
9779 // Adjust line and move cursor to suggested indent
9780 // if cursor is not at suggested indent
9781 if cursor.column < suggested_indent.len
9782 && cursor.column <= current_indent.len
9783 && current_indent.len <= suggested_indent.len
9784 {
9785 selection.start = Point::new(cursor.row, suggested_indent.len);
9786 selection.end = selection.start;
9787 if row_delta == 0 {
9788 edits.extend(Buffer::edit_for_indent_size_adjustment(
9789 cursor.row,
9790 current_indent,
9791 suggested_indent,
9792 ));
9793 row_delta = suggested_indent.len - current_indent.len;
9794 }
9795 continue;
9796 }
9797
9798 // If current indent is more than suggested indent
9799 // only move cursor to current indent and skip indent
9800 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9801 selection.start = Point::new(cursor.row, current_indent.len);
9802 selection.end = selection.start;
9803 continue;
9804 }
9805 }
9806
9807 // Otherwise, insert a hard or soft tab.
9808 let settings = buffer.language_settings_at(cursor, cx);
9809 let tab_size = if settings.hard_tabs {
9810 IndentSize::tab()
9811 } else {
9812 let tab_size = settings.tab_size.get();
9813 let indent_remainder = snapshot
9814 .text_for_range(Point::new(cursor.row, 0)..cursor)
9815 .flat_map(str::chars)
9816 .fold(row_delta % tab_size, |counter: u32, c| {
9817 if c == '\t' {
9818 0
9819 } else {
9820 (counter + 1) % tab_size
9821 }
9822 });
9823
9824 let chars_to_next_tab_stop = tab_size - indent_remainder;
9825 IndentSize::spaces(chars_to_next_tab_stop)
9826 };
9827 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9828 selection.end = selection.start;
9829 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9830 row_delta += tab_size.len;
9831 }
9832
9833 self.transact(window, cx, |this, window, cx| {
9834 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9835 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9836 this.refresh_inline_completion(true, false, window, cx);
9837 });
9838 }
9839
9840 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9841 if self.read_only(cx) {
9842 return;
9843 }
9844 if self.mode.is_single_line() {
9845 cx.propagate();
9846 return;
9847 }
9848
9849 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9850 let mut selections = self.selections.all::<Point>(cx);
9851 let mut prev_edited_row = 0;
9852 let mut row_delta = 0;
9853 let mut edits = Vec::new();
9854 let buffer = self.buffer.read(cx);
9855 let snapshot = buffer.snapshot(cx);
9856 for selection in &mut selections {
9857 if selection.start.row != prev_edited_row {
9858 row_delta = 0;
9859 }
9860 prev_edited_row = selection.end.row;
9861
9862 row_delta =
9863 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9864 }
9865
9866 self.transact(window, cx, |this, window, cx| {
9867 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9868 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9869 });
9870 }
9871
9872 fn indent_selection(
9873 buffer: &MultiBuffer,
9874 snapshot: &MultiBufferSnapshot,
9875 selection: &mut Selection<Point>,
9876 edits: &mut Vec<(Range<Point>, String)>,
9877 delta_for_start_row: u32,
9878 cx: &App,
9879 ) -> u32 {
9880 let settings = buffer.language_settings_at(selection.start, cx);
9881 let tab_size = settings.tab_size.get();
9882 let indent_kind = if settings.hard_tabs {
9883 IndentKind::Tab
9884 } else {
9885 IndentKind::Space
9886 };
9887 let mut start_row = selection.start.row;
9888 let mut end_row = selection.end.row + 1;
9889
9890 // If a selection ends at the beginning of a line, don't indent
9891 // that last line.
9892 if selection.end.column == 0 && selection.end.row > selection.start.row {
9893 end_row -= 1;
9894 }
9895
9896 // Avoid re-indenting a row that has already been indented by a
9897 // previous selection, but still update this selection's column
9898 // to reflect that indentation.
9899 if delta_for_start_row > 0 {
9900 start_row += 1;
9901 selection.start.column += delta_for_start_row;
9902 if selection.end.row == selection.start.row {
9903 selection.end.column += delta_for_start_row;
9904 }
9905 }
9906
9907 let mut delta_for_end_row = 0;
9908 let has_multiple_rows = start_row + 1 != end_row;
9909 for row in start_row..end_row {
9910 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9911 let indent_delta = match (current_indent.kind, indent_kind) {
9912 (IndentKind::Space, IndentKind::Space) => {
9913 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9914 IndentSize::spaces(columns_to_next_tab_stop)
9915 }
9916 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9917 (_, IndentKind::Tab) => IndentSize::tab(),
9918 };
9919
9920 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9921 0
9922 } else {
9923 selection.start.column
9924 };
9925 let row_start = Point::new(row, start);
9926 edits.push((
9927 row_start..row_start,
9928 indent_delta.chars().collect::<String>(),
9929 ));
9930
9931 // Update this selection's endpoints to reflect the indentation.
9932 if row == selection.start.row {
9933 selection.start.column += indent_delta.len;
9934 }
9935 if row == selection.end.row {
9936 selection.end.column += indent_delta.len;
9937 delta_for_end_row = indent_delta.len;
9938 }
9939 }
9940
9941 if selection.start.row == selection.end.row {
9942 delta_for_start_row + delta_for_end_row
9943 } else {
9944 delta_for_end_row
9945 }
9946 }
9947
9948 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9949 if self.read_only(cx) {
9950 return;
9951 }
9952 if self.mode.is_single_line() {
9953 cx.propagate();
9954 return;
9955 }
9956
9957 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9958 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9959 let selections = self.selections.all::<Point>(cx);
9960 let mut deletion_ranges = Vec::new();
9961 let mut last_outdent = None;
9962 {
9963 let buffer = self.buffer.read(cx);
9964 let snapshot = buffer.snapshot(cx);
9965 for selection in &selections {
9966 let settings = buffer.language_settings_at(selection.start, cx);
9967 let tab_size = settings.tab_size.get();
9968 let mut rows = selection.spanned_rows(false, &display_map);
9969
9970 // Avoid re-outdenting a row that has already been outdented by a
9971 // previous selection.
9972 if let Some(last_row) = last_outdent {
9973 if last_row == rows.start {
9974 rows.start = rows.start.next_row();
9975 }
9976 }
9977 let has_multiple_rows = rows.len() > 1;
9978 for row in rows.iter_rows() {
9979 let indent_size = snapshot.indent_size_for_line(row);
9980 if indent_size.len > 0 {
9981 let deletion_len = match indent_size.kind {
9982 IndentKind::Space => {
9983 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9984 if columns_to_prev_tab_stop == 0 {
9985 tab_size
9986 } else {
9987 columns_to_prev_tab_stop
9988 }
9989 }
9990 IndentKind::Tab => 1,
9991 };
9992 let start = if has_multiple_rows
9993 || deletion_len > selection.start.column
9994 || indent_size.len < selection.start.column
9995 {
9996 0
9997 } else {
9998 selection.start.column - deletion_len
9999 };
10000 deletion_ranges.push(
10001 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10002 );
10003 last_outdent = Some(row);
10004 }
10005 }
10006 }
10007 }
10008
10009 self.transact(window, cx, |this, window, cx| {
10010 this.buffer.update(cx, |buffer, cx| {
10011 let empty_str: Arc<str> = Arc::default();
10012 buffer.edit(
10013 deletion_ranges
10014 .into_iter()
10015 .map(|range| (range, empty_str.clone())),
10016 None,
10017 cx,
10018 );
10019 });
10020 let selections = this.selections.all::<usize>(cx);
10021 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10022 });
10023 }
10024
10025 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10026 if self.read_only(cx) {
10027 return;
10028 }
10029 if self.mode.is_single_line() {
10030 cx.propagate();
10031 return;
10032 }
10033
10034 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10035 let selections = self
10036 .selections
10037 .all::<usize>(cx)
10038 .into_iter()
10039 .map(|s| s.range());
10040
10041 self.transact(window, cx, |this, window, cx| {
10042 this.buffer.update(cx, |buffer, cx| {
10043 buffer.autoindent_ranges(selections, cx);
10044 });
10045 let selections = this.selections.all::<usize>(cx);
10046 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10047 });
10048 }
10049
10050 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10051 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10052 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10053 let selections = self.selections.all::<Point>(cx);
10054
10055 let mut new_cursors = Vec::new();
10056 let mut edit_ranges = Vec::new();
10057 let mut selections = selections.iter().peekable();
10058 while let Some(selection) = selections.next() {
10059 let mut rows = selection.spanned_rows(false, &display_map);
10060 let goal_display_column = selection.head().to_display_point(&display_map).column();
10061
10062 // Accumulate contiguous regions of rows that we want to delete.
10063 while let Some(next_selection) = selections.peek() {
10064 let next_rows = next_selection.spanned_rows(false, &display_map);
10065 if next_rows.start <= rows.end {
10066 rows.end = next_rows.end;
10067 selections.next().unwrap();
10068 } else {
10069 break;
10070 }
10071 }
10072
10073 let buffer = &display_map.buffer_snapshot;
10074 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10075 let edit_end;
10076 let cursor_buffer_row;
10077 if buffer.max_point().row >= rows.end.0 {
10078 // If there's a line after the range, delete the \n from the end of the row range
10079 // and position the cursor on the next line.
10080 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10081 cursor_buffer_row = rows.end;
10082 } else {
10083 // If there isn't a line after the range, delete the \n from the line before the
10084 // start of the row range and position the cursor there.
10085 edit_start = edit_start.saturating_sub(1);
10086 edit_end = buffer.len();
10087 cursor_buffer_row = rows.start.previous_row();
10088 }
10089
10090 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10091 *cursor.column_mut() =
10092 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10093
10094 new_cursors.push((
10095 selection.id,
10096 buffer.anchor_after(cursor.to_point(&display_map)),
10097 ));
10098 edit_ranges.push(edit_start..edit_end);
10099 }
10100
10101 self.transact(window, cx, |this, window, cx| {
10102 let buffer = this.buffer.update(cx, |buffer, cx| {
10103 let empty_str: Arc<str> = Arc::default();
10104 buffer.edit(
10105 edit_ranges
10106 .into_iter()
10107 .map(|range| (range, empty_str.clone())),
10108 None,
10109 cx,
10110 );
10111 buffer.snapshot(cx)
10112 });
10113 let new_selections = new_cursors
10114 .into_iter()
10115 .map(|(id, cursor)| {
10116 let cursor = cursor.to_point(&buffer);
10117 Selection {
10118 id,
10119 start: cursor,
10120 end: cursor,
10121 reversed: false,
10122 goal: SelectionGoal::None,
10123 }
10124 })
10125 .collect();
10126
10127 this.change_selections(Default::default(), window, cx, |s| {
10128 s.select(new_selections);
10129 });
10130 });
10131 }
10132
10133 pub fn join_lines_impl(
10134 &mut self,
10135 insert_whitespace: bool,
10136 window: &mut Window,
10137 cx: &mut Context<Self>,
10138 ) {
10139 if self.read_only(cx) {
10140 return;
10141 }
10142 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10143 for selection in self.selections.all::<Point>(cx) {
10144 let start = MultiBufferRow(selection.start.row);
10145 // Treat single line selections as if they include the next line. Otherwise this action
10146 // would do nothing for single line selections individual cursors.
10147 let end = if selection.start.row == selection.end.row {
10148 MultiBufferRow(selection.start.row + 1)
10149 } else {
10150 MultiBufferRow(selection.end.row)
10151 };
10152
10153 if let Some(last_row_range) = row_ranges.last_mut() {
10154 if start <= last_row_range.end {
10155 last_row_range.end = end;
10156 continue;
10157 }
10158 }
10159 row_ranges.push(start..end);
10160 }
10161
10162 let snapshot = self.buffer.read(cx).snapshot(cx);
10163 let mut cursor_positions = Vec::new();
10164 for row_range in &row_ranges {
10165 let anchor = snapshot.anchor_before(Point::new(
10166 row_range.end.previous_row().0,
10167 snapshot.line_len(row_range.end.previous_row()),
10168 ));
10169 cursor_positions.push(anchor..anchor);
10170 }
10171
10172 self.transact(window, cx, |this, window, cx| {
10173 for row_range in row_ranges.into_iter().rev() {
10174 for row in row_range.iter_rows().rev() {
10175 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10176 let next_line_row = row.next_row();
10177 let indent = snapshot.indent_size_for_line(next_line_row);
10178 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10179
10180 let replace =
10181 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10182 " "
10183 } else {
10184 ""
10185 };
10186
10187 this.buffer.update(cx, |buffer, cx| {
10188 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10189 });
10190 }
10191 }
10192
10193 this.change_selections(Default::default(), window, cx, |s| {
10194 s.select_anchor_ranges(cursor_positions)
10195 });
10196 });
10197 }
10198
10199 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10200 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10201 self.join_lines_impl(true, window, cx);
10202 }
10203
10204 pub fn sort_lines_case_sensitive(
10205 &mut self,
10206 _: &SortLinesCaseSensitive,
10207 window: &mut Window,
10208 cx: &mut Context<Self>,
10209 ) {
10210 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10211 }
10212
10213 pub fn sort_lines_by_length(
10214 &mut self,
10215 _: &SortLinesByLength,
10216 window: &mut Window,
10217 cx: &mut Context<Self>,
10218 ) {
10219 self.manipulate_immutable_lines(window, cx, |lines| {
10220 lines.sort_by_key(|&line| line.chars().count())
10221 })
10222 }
10223
10224 pub fn sort_lines_case_insensitive(
10225 &mut self,
10226 _: &SortLinesCaseInsensitive,
10227 window: &mut Window,
10228 cx: &mut Context<Self>,
10229 ) {
10230 self.manipulate_immutable_lines(window, cx, |lines| {
10231 lines.sort_by_key(|line| line.to_lowercase())
10232 })
10233 }
10234
10235 pub fn unique_lines_case_insensitive(
10236 &mut self,
10237 _: &UniqueLinesCaseInsensitive,
10238 window: &mut Window,
10239 cx: &mut Context<Self>,
10240 ) {
10241 self.manipulate_immutable_lines(window, cx, |lines| {
10242 let mut seen = HashSet::default();
10243 lines.retain(|line| seen.insert(line.to_lowercase()));
10244 })
10245 }
10246
10247 pub fn unique_lines_case_sensitive(
10248 &mut self,
10249 _: &UniqueLinesCaseSensitive,
10250 window: &mut Window,
10251 cx: &mut Context<Self>,
10252 ) {
10253 self.manipulate_immutable_lines(window, cx, |lines| {
10254 let mut seen = HashSet::default();
10255 lines.retain(|line| seen.insert(*line));
10256 })
10257 }
10258
10259 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10260 let Some(project) = self.project.clone() else {
10261 return;
10262 };
10263 self.reload(project, window, cx)
10264 .detach_and_notify_err(window, cx);
10265 }
10266
10267 pub fn restore_file(
10268 &mut self,
10269 _: &::git::RestoreFile,
10270 window: &mut Window,
10271 cx: &mut Context<Self>,
10272 ) {
10273 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10274 let mut buffer_ids = HashSet::default();
10275 let snapshot = self.buffer().read(cx).snapshot(cx);
10276 for selection in self.selections.all::<usize>(cx) {
10277 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10278 }
10279
10280 let buffer = self.buffer().read(cx);
10281 let ranges = buffer_ids
10282 .into_iter()
10283 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10284 .collect::<Vec<_>>();
10285
10286 self.restore_hunks_in_ranges(ranges, window, cx);
10287 }
10288
10289 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10290 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10291 let selections = self
10292 .selections
10293 .all(cx)
10294 .into_iter()
10295 .map(|s| s.range())
10296 .collect();
10297 self.restore_hunks_in_ranges(selections, window, cx);
10298 }
10299
10300 pub fn restore_hunks_in_ranges(
10301 &mut self,
10302 ranges: Vec<Range<Point>>,
10303 window: &mut Window,
10304 cx: &mut Context<Editor>,
10305 ) {
10306 let mut revert_changes = HashMap::default();
10307 let chunk_by = self
10308 .snapshot(window, cx)
10309 .hunks_for_ranges(ranges)
10310 .into_iter()
10311 .chunk_by(|hunk| hunk.buffer_id);
10312 for (buffer_id, hunks) in &chunk_by {
10313 let hunks = hunks.collect::<Vec<_>>();
10314 for hunk in &hunks {
10315 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10316 }
10317 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10318 }
10319 drop(chunk_by);
10320 if !revert_changes.is_empty() {
10321 self.transact(window, cx, |editor, window, cx| {
10322 editor.restore(revert_changes, window, cx);
10323 });
10324 }
10325 }
10326
10327 pub fn open_active_item_in_terminal(
10328 &mut self,
10329 _: &OpenInTerminal,
10330 window: &mut Window,
10331 cx: &mut Context<Self>,
10332 ) {
10333 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10334 let project_path = buffer.read(cx).project_path(cx)?;
10335 let project = self.project.as_ref()?.read(cx);
10336 let entry = project.entry_for_path(&project_path, cx)?;
10337 let parent = match &entry.canonical_path {
10338 Some(canonical_path) => canonical_path.to_path_buf(),
10339 None => project.absolute_path(&project_path, cx)?,
10340 }
10341 .parent()?
10342 .to_path_buf();
10343 Some(parent)
10344 }) {
10345 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10346 }
10347 }
10348
10349 fn set_breakpoint_context_menu(
10350 &mut self,
10351 display_row: DisplayRow,
10352 position: Option<Anchor>,
10353 clicked_point: gpui::Point<Pixels>,
10354 window: &mut Window,
10355 cx: &mut Context<Self>,
10356 ) {
10357 let source = self
10358 .buffer
10359 .read(cx)
10360 .snapshot(cx)
10361 .anchor_before(Point::new(display_row.0, 0u32));
10362
10363 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10364
10365 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10366 self,
10367 source,
10368 clicked_point,
10369 context_menu,
10370 window,
10371 cx,
10372 );
10373 }
10374
10375 fn add_edit_breakpoint_block(
10376 &mut self,
10377 anchor: Anchor,
10378 breakpoint: &Breakpoint,
10379 edit_action: BreakpointPromptEditAction,
10380 window: &mut Window,
10381 cx: &mut Context<Self>,
10382 ) {
10383 let weak_editor = cx.weak_entity();
10384 let bp_prompt = cx.new(|cx| {
10385 BreakpointPromptEditor::new(
10386 weak_editor,
10387 anchor,
10388 breakpoint.clone(),
10389 edit_action,
10390 window,
10391 cx,
10392 )
10393 });
10394
10395 let height = bp_prompt.update(cx, |this, cx| {
10396 this.prompt
10397 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10398 });
10399 let cloned_prompt = bp_prompt.clone();
10400 let blocks = vec![BlockProperties {
10401 style: BlockStyle::Sticky,
10402 placement: BlockPlacement::Above(anchor),
10403 height: Some(height),
10404 render: Arc::new(move |cx| {
10405 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10406 cloned_prompt.clone().into_any_element()
10407 }),
10408 priority: 0,
10409 render_in_minimap: true,
10410 }];
10411
10412 let focus_handle = bp_prompt.focus_handle(cx);
10413 window.focus(&focus_handle);
10414
10415 let block_ids = self.insert_blocks(blocks, None, cx);
10416 bp_prompt.update(cx, |prompt, _| {
10417 prompt.add_block_ids(block_ids);
10418 });
10419 }
10420
10421 pub(crate) fn breakpoint_at_row(
10422 &self,
10423 row: u32,
10424 window: &mut Window,
10425 cx: &mut Context<Self>,
10426 ) -> Option<(Anchor, Breakpoint)> {
10427 let snapshot = self.snapshot(window, cx);
10428 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10429
10430 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10431 }
10432
10433 pub(crate) fn breakpoint_at_anchor(
10434 &self,
10435 breakpoint_position: Anchor,
10436 snapshot: &EditorSnapshot,
10437 cx: &mut Context<Self>,
10438 ) -> Option<(Anchor, Breakpoint)> {
10439 let project = self.project.clone()?;
10440
10441 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10442 snapshot
10443 .buffer_snapshot
10444 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10445 })?;
10446
10447 let enclosing_excerpt = breakpoint_position.excerpt_id;
10448 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10449 let buffer_snapshot = buffer.read(cx).snapshot();
10450
10451 let row = buffer_snapshot
10452 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10453 .row;
10454
10455 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10456 let anchor_end = snapshot
10457 .buffer_snapshot
10458 .anchor_after(Point::new(row, line_len));
10459
10460 let bp = self
10461 .breakpoint_store
10462 .as_ref()?
10463 .read_with(cx, |breakpoint_store, cx| {
10464 breakpoint_store
10465 .breakpoints(
10466 &buffer,
10467 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10468 &buffer_snapshot,
10469 cx,
10470 )
10471 .next()
10472 .and_then(|(bp, _)| {
10473 let breakpoint_row = buffer_snapshot
10474 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10475 .row;
10476
10477 if breakpoint_row == row {
10478 snapshot
10479 .buffer_snapshot
10480 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10481 .map(|position| (position, bp.bp.clone()))
10482 } else {
10483 None
10484 }
10485 })
10486 });
10487 bp
10488 }
10489
10490 pub fn edit_log_breakpoint(
10491 &mut self,
10492 _: &EditLogBreakpoint,
10493 window: &mut Window,
10494 cx: &mut Context<Self>,
10495 ) {
10496 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10497 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10498 message: None,
10499 state: BreakpointState::Enabled,
10500 condition: None,
10501 hit_condition: None,
10502 });
10503
10504 self.add_edit_breakpoint_block(
10505 anchor,
10506 &breakpoint,
10507 BreakpointPromptEditAction::Log,
10508 window,
10509 cx,
10510 );
10511 }
10512 }
10513
10514 fn breakpoints_at_cursors(
10515 &self,
10516 window: &mut Window,
10517 cx: &mut Context<Self>,
10518 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10519 let snapshot = self.snapshot(window, cx);
10520 let cursors = self
10521 .selections
10522 .disjoint_anchors()
10523 .into_iter()
10524 .map(|selection| {
10525 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10526
10527 let breakpoint_position = self
10528 .breakpoint_at_row(cursor_position.row, window, cx)
10529 .map(|bp| bp.0)
10530 .unwrap_or_else(|| {
10531 snapshot
10532 .display_snapshot
10533 .buffer_snapshot
10534 .anchor_after(Point::new(cursor_position.row, 0))
10535 });
10536
10537 let breakpoint = self
10538 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10539 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10540
10541 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10542 })
10543 // 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.
10544 .collect::<HashMap<Anchor, _>>();
10545
10546 cursors.into_iter().collect()
10547 }
10548
10549 pub fn enable_breakpoint(
10550 &mut self,
10551 _: &crate::actions::EnableBreakpoint,
10552 window: &mut Window,
10553 cx: &mut Context<Self>,
10554 ) {
10555 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10556 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10557 continue;
10558 };
10559 self.edit_breakpoint_at_anchor(
10560 anchor,
10561 breakpoint,
10562 BreakpointEditAction::InvertState,
10563 cx,
10564 );
10565 }
10566 }
10567
10568 pub fn disable_breakpoint(
10569 &mut self,
10570 _: &crate::actions::DisableBreakpoint,
10571 window: &mut Window,
10572 cx: &mut Context<Self>,
10573 ) {
10574 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10575 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10576 continue;
10577 };
10578 self.edit_breakpoint_at_anchor(
10579 anchor,
10580 breakpoint,
10581 BreakpointEditAction::InvertState,
10582 cx,
10583 );
10584 }
10585 }
10586
10587 pub fn toggle_breakpoint(
10588 &mut self,
10589 _: &crate::actions::ToggleBreakpoint,
10590 window: &mut Window,
10591 cx: &mut Context<Self>,
10592 ) {
10593 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10594 if let Some(breakpoint) = breakpoint {
10595 self.edit_breakpoint_at_anchor(
10596 anchor,
10597 breakpoint,
10598 BreakpointEditAction::Toggle,
10599 cx,
10600 );
10601 } else {
10602 self.edit_breakpoint_at_anchor(
10603 anchor,
10604 Breakpoint::new_standard(),
10605 BreakpointEditAction::Toggle,
10606 cx,
10607 );
10608 }
10609 }
10610 }
10611
10612 pub fn edit_breakpoint_at_anchor(
10613 &mut self,
10614 breakpoint_position: Anchor,
10615 breakpoint: Breakpoint,
10616 edit_action: BreakpointEditAction,
10617 cx: &mut Context<Self>,
10618 ) {
10619 let Some(breakpoint_store) = &self.breakpoint_store else {
10620 return;
10621 };
10622
10623 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10624 if breakpoint_position == Anchor::min() {
10625 self.buffer()
10626 .read(cx)
10627 .excerpt_buffer_ids()
10628 .into_iter()
10629 .next()
10630 } else {
10631 None
10632 }
10633 }) else {
10634 return;
10635 };
10636
10637 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10638 return;
10639 };
10640
10641 breakpoint_store.update(cx, |breakpoint_store, cx| {
10642 breakpoint_store.toggle_breakpoint(
10643 buffer,
10644 BreakpointWithPosition {
10645 position: breakpoint_position.text_anchor,
10646 bp: breakpoint,
10647 },
10648 edit_action,
10649 cx,
10650 );
10651 });
10652
10653 cx.notify();
10654 }
10655
10656 #[cfg(any(test, feature = "test-support"))]
10657 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10658 self.breakpoint_store.clone()
10659 }
10660
10661 pub fn prepare_restore_change(
10662 &self,
10663 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10664 hunk: &MultiBufferDiffHunk,
10665 cx: &mut App,
10666 ) -> Option<()> {
10667 if hunk.is_created_file() {
10668 return None;
10669 }
10670 let buffer = self.buffer.read(cx);
10671 let diff = buffer.diff_for(hunk.buffer_id)?;
10672 let buffer = buffer.buffer(hunk.buffer_id)?;
10673 let buffer = buffer.read(cx);
10674 let original_text = diff
10675 .read(cx)
10676 .base_text()
10677 .as_rope()
10678 .slice(hunk.diff_base_byte_range.clone());
10679 let buffer_snapshot = buffer.snapshot();
10680 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10681 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10682 probe
10683 .0
10684 .start
10685 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10686 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10687 }) {
10688 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10689 Some(())
10690 } else {
10691 None
10692 }
10693 }
10694
10695 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10696 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10697 }
10698
10699 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10700 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10701 }
10702
10703 fn manipulate_lines<M>(
10704 &mut self,
10705 window: &mut Window,
10706 cx: &mut Context<Self>,
10707 mut manipulate: M,
10708 ) where
10709 M: FnMut(&str) -> LineManipulationResult,
10710 {
10711 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10712
10713 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10714 let buffer = self.buffer.read(cx).snapshot(cx);
10715
10716 let mut edits = Vec::new();
10717
10718 let selections = self.selections.all::<Point>(cx);
10719 let mut selections = selections.iter().peekable();
10720 let mut contiguous_row_selections = Vec::new();
10721 let mut new_selections = Vec::new();
10722 let mut added_lines = 0;
10723 let mut removed_lines = 0;
10724
10725 while let Some(selection) = selections.next() {
10726 let (start_row, end_row) = consume_contiguous_rows(
10727 &mut contiguous_row_selections,
10728 selection,
10729 &display_map,
10730 &mut selections,
10731 );
10732
10733 let start_point = Point::new(start_row.0, 0);
10734 let end_point = Point::new(
10735 end_row.previous_row().0,
10736 buffer.line_len(end_row.previous_row()),
10737 );
10738 let text = buffer
10739 .text_for_range(start_point..end_point)
10740 .collect::<String>();
10741
10742 let LineManipulationResult {
10743 new_text,
10744 line_count_before,
10745 line_count_after,
10746 } = manipulate(&text);
10747
10748 edits.push((start_point..end_point, new_text));
10749
10750 // Selections must change based on added and removed line count
10751 let start_row =
10752 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10753 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10754 new_selections.push(Selection {
10755 id: selection.id,
10756 start: start_row,
10757 end: end_row,
10758 goal: SelectionGoal::None,
10759 reversed: selection.reversed,
10760 });
10761
10762 if line_count_after > line_count_before {
10763 added_lines += line_count_after - line_count_before;
10764 } else if line_count_before > line_count_after {
10765 removed_lines += line_count_before - line_count_after;
10766 }
10767 }
10768
10769 self.transact(window, cx, |this, window, cx| {
10770 let buffer = this.buffer.update(cx, |buffer, cx| {
10771 buffer.edit(edits, None, cx);
10772 buffer.snapshot(cx)
10773 });
10774
10775 // Recalculate offsets on newly edited buffer
10776 let new_selections = new_selections
10777 .iter()
10778 .map(|s| {
10779 let start_point = Point::new(s.start.0, 0);
10780 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10781 Selection {
10782 id: s.id,
10783 start: buffer.point_to_offset(start_point),
10784 end: buffer.point_to_offset(end_point),
10785 goal: s.goal,
10786 reversed: s.reversed,
10787 }
10788 })
10789 .collect();
10790
10791 this.change_selections(Default::default(), window, cx, |s| {
10792 s.select(new_selections);
10793 });
10794
10795 this.request_autoscroll(Autoscroll::fit(), cx);
10796 });
10797 }
10798
10799 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10800 self.manipulate_text(window, cx, |text| {
10801 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10802 if has_upper_case_characters {
10803 text.to_lowercase()
10804 } else {
10805 text.to_uppercase()
10806 }
10807 })
10808 }
10809
10810 fn manipulate_immutable_lines<Fn>(
10811 &mut self,
10812 window: &mut Window,
10813 cx: &mut Context<Self>,
10814 mut callback: Fn,
10815 ) where
10816 Fn: FnMut(&mut Vec<&str>),
10817 {
10818 self.manipulate_lines(window, cx, |text| {
10819 let mut lines: Vec<&str> = text.split('\n').collect();
10820 let line_count_before = lines.len();
10821
10822 callback(&mut lines);
10823
10824 LineManipulationResult {
10825 new_text: lines.join("\n"),
10826 line_count_before,
10827 line_count_after: lines.len(),
10828 }
10829 });
10830 }
10831
10832 fn manipulate_mutable_lines<Fn>(
10833 &mut self,
10834 window: &mut Window,
10835 cx: &mut Context<Self>,
10836 mut callback: Fn,
10837 ) where
10838 Fn: FnMut(&mut Vec<Cow<'_, str>>),
10839 {
10840 self.manipulate_lines(window, cx, |text| {
10841 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
10842 let line_count_before = lines.len();
10843
10844 callback(&mut lines);
10845
10846 LineManipulationResult {
10847 new_text: lines.join("\n"),
10848 line_count_before,
10849 line_count_after: lines.len(),
10850 }
10851 });
10852 }
10853
10854 pub fn convert_indentation_to_spaces(
10855 &mut self,
10856 _: &ConvertIndentationToSpaces,
10857 window: &mut Window,
10858 cx: &mut Context<Self>,
10859 ) {
10860 let settings = self.buffer.read(cx).language_settings(cx);
10861 let tab_size = settings.tab_size.get() as usize;
10862
10863 self.manipulate_mutable_lines(window, cx, |lines| {
10864 // Allocates a reasonably sized scratch buffer once for the whole loop
10865 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10866 // Avoids recomputing spaces that could be inserted many times
10867 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10868 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10869 .collect();
10870
10871 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10872 let mut chars = line.as_ref().chars();
10873 let mut col = 0;
10874 let mut changed = false;
10875
10876 while let Some(ch) = chars.next() {
10877 match ch {
10878 ' ' => {
10879 reindented_line.push(' ');
10880 col += 1;
10881 }
10882 '\t' => {
10883 // \t are converted to spaces depending on the current column
10884 let spaces_len = tab_size - (col % tab_size);
10885 reindented_line.extend(&space_cache[spaces_len - 1]);
10886 col += spaces_len;
10887 changed = true;
10888 }
10889 _ => {
10890 // If we dont append before break, the character is consumed
10891 reindented_line.push(ch);
10892 break;
10893 }
10894 }
10895 }
10896
10897 if !changed {
10898 reindented_line.clear();
10899 continue;
10900 }
10901 // Append the rest of the line and replace old reference with new one
10902 reindented_line.extend(chars);
10903 *line = Cow::Owned(reindented_line.clone());
10904 reindented_line.clear();
10905 }
10906 });
10907 }
10908
10909 pub fn convert_indentation_to_tabs(
10910 &mut self,
10911 _: &ConvertIndentationToTabs,
10912 window: &mut Window,
10913 cx: &mut Context<Self>,
10914 ) {
10915 let settings = self.buffer.read(cx).language_settings(cx);
10916 let tab_size = settings.tab_size.get() as usize;
10917
10918 self.manipulate_mutable_lines(window, cx, |lines| {
10919 // Allocates a reasonably sized buffer once for the whole loop
10920 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10921 // Avoids recomputing spaces that could be inserted many times
10922 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10923 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10924 .collect();
10925
10926 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10927 let mut chars = line.chars();
10928 let mut spaces_count = 0;
10929 let mut first_non_indent_char = None;
10930 let mut changed = false;
10931
10932 while let Some(ch) = chars.next() {
10933 match ch {
10934 ' ' => {
10935 // Keep track of spaces. Append \t when we reach tab_size
10936 spaces_count += 1;
10937 changed = true;
10938 if spaces_count == tab_size {
10939 reindented_line.push('\t');
10940 spaces_count = 0;
10941 }
10942 }
10943 '\t' => {
10944 reindented_line.push('\t');
10945 spaces_count = 0;
10946 }
10947 _ => {
10948 // Dont append it yet, we might have remaining spaces
10949 first_non_indent_char = Some(ch);
10950 break;
10951 }
10952 }
10953 }
10954
10955 if !changed {
10956 reindented_line.clear();
10957 continue;
10958 }
10959 // Remaining spaces that didn't make a full tab stop
10960 if spaces_count > 0 {
10961 reindented_line.extend(&space_cache[spaces_count - 1]);
10962 }
10963 // If we consume an extra character that was not indentation, add it back
10964 if let Some(extra_char) = first_non_indent_char {
10965 reindented_line.push(extra_char);
10966 }
10967 // Append the rest of the line and replace old reference with new one
10968 reindented_line.extend(chars);
10969 *line = Cow::Owned(reindented_line.clone());
10970 reindented_line.clear();
10971 }
10972 });
10973 }
10974
10975 pub fn convert_to_upper_case(
10976 &mut self,
10977 _: &ConvertToUpperCase,
10978 window: &mut Window,
10979 cx: &mut Context<Self>,
10980 ) {
10981 self.manipulate_text(window, cx, |text| text.to_uppercase())
10982 }
10983
10984 pub fn convert_to_lower_case(
10985 &mut self,
10986 _: &ConvertToLowerCase,
10987 window: &mut Window,
10988 cx: &mut Context<Self>,
10989 ) {
10990 self.manipulate_text(window, cx, |text| text.to_lowercase())
10991 }
10992
10993 pub fn convert_to_title_case(
10994 &mut self,
10995 _: &ConvertToTitleCase,
10996 window: &mut Window,
10997 cx: &mut Context<Self>,
10998 ) {
10999 self.manipulate_text(window, cx, |text| {
11000 text.split('\n')
11001 .map(|line| line.to_case(Case::Title))
11002 .join("\n")
11003 })
11004 }
11005
11006 pub fn convert_to_snake_case(
11007 &mut self,
11008 _: &ConvertToSnakeCase,
11009 window: &mut Window,
11010 cx: &mut Context<Self>,
11011 ) {
11012 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11013 }
11014
11015 pub fn convert_to_kebab_case(
11016 &mut self,
11017 _: &ConvertToKebabCase,
11018 window: &mut Window,
11019 cx: &mut Context<Self>,
11020 ) {
11021 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11022 }
11023
11024 pub fn convert_to_upper_camel_case(
11025 &mut self,
11026 _: &ConvertToUpperCamelCase,
11027 window: &mut Window,
11028 cx: &mut Context<Self>,
11029 ) {
11030 self.manipulate_text(window, cx, |text| {
11031 text.split('\n')
11032 .map(|line| line.to_case(Case::UpperCamel))
11033 .join("\n")
11034 })
11035 }
11036
11037 pub fn convert_to_lower_camel_case(
11038 &mut self,
11039 _: &ConvertToLowerCamelCase,
11040 window: &mut Window,
11041 cx: &mut Context<Self>,
11042 ) {
11043 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11044 }
11045
11046 pub fn convert_to_opposite_case(
11047 &mut self,
11048 _: &ConvertToOppositeCase,
11049 window: &mut Window,
11050 cx: &mut Context<Self>,
11051 ) {
11052 self.manipulate_text(window, cx, |text| {
11053 text.chars()
11054 .fold(String::with_capacity(text.len()), |mut t, c| {
11055 if c.is_uppercase() {
11056 t.extend(c.to_lowercase());
11057 } else {
11058 t.extend(c.to_uppercase());
11059 }
11060 t
11061 })
11062 })
11063 }
11064
11065 pub fn convert_to_rot13(
11066 &mut self,
11067 _: &ConvertToRot13,
11068 window: &mut Window,
11069 cx: &mut Context<Self>,
11070 ) {
11071 self.manipulate_text(window, cx, |text| {
11072 text.chars()
11073 .map(|c| match c {
11074 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11075 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11076 _ => c,
11077 })
11078 .collect()
11079 })
11080 }
11081
11082 pub fn convert_to_rot47(
11083 &mut self,
11084 _: &ConvertToRot47,
11085 window: &mut Window,
11086 cx: &mut Context<Self>,
11087 ) {
11088 self.manipulate_text(window, cx, |text| {
11089 text.chars()
11090 .map(|c| {
11091 let code_point = c as u32;
11092 if code_point >= 33 && code_point <= 126 {
11093 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11094 }
11095 c
11096 })
11097 .collect()
11098 })
11099 }
11100
11101 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11102 where
11103 Fn: FnMut(&str) -> String,
11104 {
11105 let buffer = self.buffer.read(cx).snapshot(cx);
11106
11107 let mut new_selections = Vec::new();
11108 let mut edits = Vec::new();
11109 let mut selection_adjustment = 0i32;
11110
11111 for selection in self.selections.all::<usize>(cx) {
11112 let selection_is_empty = selection.is_empty();
11113
11114 let (start, end) = if selection_is_empty {
11115 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11116 (word_range.start, word_range.end)
11117 } else {
11118 (selection.start, selection.end)
11119 };
11120
11121 let text = buffer.text_for_range(start..end).collect::<String>();
11122 let old_length = text.len() as i32;
11123 let text = callback(&text);
11124
11125 new_selections.push(Selection {
11126 start: (start as i32 - selection_adjustment) as usize,
11127 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11128 goal: SelectionGoal::None,
11129 ..selection
11130 });
11131
11132 selection_adjustment += old_length - text.len() as i32;
11133
11134 edits.push((start..end, text));
11135 }
11136
11137 self.transact(window, cx, |this, window, cx| {
11138 this.buffer.update(cx, |buffer, cx| {
11139 buffer.edit(edits, None, cx);
11140 });
11141
11142 this.change_selections(Default::default(), window, cx, |s| {
11143 s.select(new_selections);
11144 });
11145
11146 this.request_autoscroll(Autoscroll::fit(), cx);
11147 });
11148 }
11149
11150 pub fn move_selection_on_drop(
11151 &mut self,
11152 selection: &Selection<Anchor>,
11153 target: DisplayPoint,
11154 is_cut: bool,
11155 window: &mut Window,
11156 cx: &mut Context<Self>,
11157 ) {
11158 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11159 let buffer = &display_map.buffer_snapshot;
11160 let mut edits = Vec::new();
11161 let insert_point = display_map
11162 .clip_point(target, Bias::Left)
11163 .to_point(&display_map);
11164 let text = buffer
11165 .text_for_range(selection.start..selection.end)
11166 .collect::<String>();
11167 if is_cut {
11168 edits.push(((selection.start..selection.end), String::new()));
11169 }
11170 let insert_anchor = buffer.anchor_before(insert_point);
11171 edits.push(((insert_anchor..insert_anchor), text));
11172 let last_edit_start = insert_anchor.bias_left(buffer);
11173 let last_edit_end = insert_anchor.bias_right(buffer);
11174 self.transact(window, cx, |this, window, cx| {
11175 this.buffer.update(cx, |buffer, cx| {
11176 buffer.edit(edits, None, cx);
11177 });
11178 this.change_selections(Default::default(), window, cx, |s| {
11179 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11180 });
11181 });
11182 }
11183
11184 pub fn clear_selection_drag_state(&mut self) {
11185 self.selection_drag_state = SelectionDragState::None;
11186 }
11187
11188 pub fn duplicate(
11189 &mut self,
11190 upwards: bool,
11191 whole_lines: bool,
11192 window: &mut Window,
11193 cx: &mut Context<Self>,
11194 ) {
11195 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11196
11197 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11198 let buffer = &display_map.buffer_snapshot;
11199 let selections = self.selections.all::<Point>(cx);
11200
11201 let mut edits = Vec::new();
11202 let mut selections_iter = selections.iter().peekable();
11203 while let Some(selection) = selections_iter.next() {
11204 let mut rows = selection.spanned_rows(false, &display_map);
11205 // duplicate line-wise
11206 if whole_lines || selection.start == selection.end {
11207 // Avoid duplicating the same lines twice.
11208 while let Some(next_selection) = selections_iter.peek() {
11209 let next_rows = next_selection.spanned_rows(false, &display_map);
11210 if next_rows.start < rows.end {
11211 rows.end = next_rows.end;
11212 selections_iter.next().unwrap();
11213 } else {
11214 break;
11215 }
11216 }
11217
11218 // Copy the text from the selected row region and splice it either at the start
11219 // or end of the region.
11220 let start = Point::new(rows.start.0, 0);
11221 let end = Point::new(
11222 rows.end.previous_row().0,
11223 buffer.line_len(rows.end.previous_row()),
11224 );
11225 let text = buffer
11226 .text_for_range(start..end)
11227 .chain(Some("\n"))
11228 .collect::<String>();
11229 let insert_location = if upwards {
11230 Point::new(rows.end.0, 0)
11231 } else {
11232 start
11233 };
11234 edits.push((insert_location..insert_location, text));
11235 } else {
11236 // duplicate character-wise
11237 let start = selection.start;
11238 let end = selection.end;
11239 let text = buffer.text_for_range(start..end).collect::<String>();
11240 edits.push((selection.end..selection.end, text));
11241 }
11242 }
11243
11244 self.transact(window, cx, |this, _, cx| {
11245 this.buffer.update(cx, |buffer, cx| {
11246 buffer.edit(edits, None, cx);
11247 });
11248
11249 this.request_autoscroll(Autoscroll::fit(), cx);
11250 });
11251 }
11252
11253 pub fn duplicate_line_up(
11254 &mut self,
11255 _: &DuplicateLineUp,
11256 window: &mut Window,
11257 cx: &mut Context<Self>,
11258 ) {
11259 self.duplicate(true, true, window, cx);
11260 }
11261
11262 pub fn duplicate_line_down(
11263 &mut self,
11264 _: &DuplicateLineDown,
11265 window: &mut Window,
11266 cx: &mut Context<Self>,
11267 ) {
11268 self.duplicate(false, true, window, cx);
11269 }
11270
11271 pub fn duplicate_selection(
11272 &mut self,
11273 _: &DuplicateSelection,
11274 window: &mut Window,
11275 cx: &mut Context<Self>,
11276 ) {
11277 self.duplicate(false, false, window, cx);
11278 }
11279
11280 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11281 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11282 if self.mode.is_single_line() {
11283 cx.propagate();
11284 return;
11285 }
11286
11287 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11288 let buffer = self.buffer.read(cx).snapshot(cx);
11289
11290 let mut edits = Vec::new();
11291 let mut unfold_ranges = Vec::new();
11292 let mut refold_creases = Vec::new();
11293
11294 let selections = self.selections.all::<Point>(cx);
11295 let mut selections = selections.iter().peekable();
11296 let mut contiguous_row_selections = Vec::new();
11297 let mut new_selections = Vec::new();
11298
11299 while let Some(selection) = selections.next() {
11300 // Find all the selections that span a contiguous row range
11301 let (start_row, end_row) = consume_contiguous_rows(
11302 &mut contiguous_row_selections,
11303 selection,
11304 &display_map,
11305 &mut selections,
11306 );
11307
11308 // Move the text spanned by the row range to be before the line preceding the row range
11309 if start_row.0 > 0 {
11310 let range_to_move = Point::new(
11311 start_row.previous_row().0,
11312 buffer.line_len(start_row.previous_row()),
11313 )
11314 ..Point::new(
11315 end_row.previous_row().0,
11316 buffer.line_len(end_row.previous_row()),
11317 );
11318 let insertion_point = display_map
11319 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11320 .0;
11321
11322 // Don't move lines across excerpts
11323 if buffer
11324 .excerpt_containing(insertion_point..range_to_move.end)
11325 .is_some()
11326 {
11327 let text = buffer
11328 .text_for_range(range_to_move.clone())
11329 .flat_map(|s| s.chars())
11330 .skip(1)
11331 .chain(['\n'])
11332 .collect::<String>();
11333
11334 edits.push((
11335 buffer.anchor_after(range_to_move.start)
11336 ..buffer.anchor_before(range_to_move.end),
11337 String::new(),
11338 ));
11339 let insertion_anchor = buffer.anchor_after(insertion_point);
11340 edits.push((insertion_anchor..insertion_anchor, text));
11341
11342 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11343
11344 // Move selections up
11345 new_selections.extend(contiguous_row_selections.drain(..).map(
11346 |mut selection| {
11347 selection.start.row -= row_delta;
11348 selection.end.row -= row_delta;
11349 selection
11350 },
11351 ));
11352
11353 // Move folds up
11354 unfold_ranges.push(range_to_move.clone());
11355 for fold in display_map.folds_in_range(
11356 buffer.anchor_before(range_to_move.start)
11357 ..buffer.anchor_after(range_to_move.end),
11358 ) {
11359 let mut start = fold.range.start.to_point(&buffer);
11360 let mut end = fold.range.end.to_point(&buffer);
11361 start.row -= row_delta;
11362 end.row -= row_delta;
11363 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11364 }
11365 }
11366 }
11367
11368 // If we didn't move line(s), preserve the existing selections
11369 new_selections.append(&mut contiguous_row_selections);
11370 }
11371
11372 self.transact(window, cx, |this, window, cx| {
11373 this.unfold_ranges(&unfold_ranges, true, true, cx);
11374 this.buffer.update(cx, |buffer, cx| {
11375 for (range, text) in edits {
11376 buffer.edit([(range, text)], None, cx);
11377 }
11378 });
11379 this.fold_creases(refold_creases, true, window, cx);
11380 this.change_selections(Default::default(), window, cx, |s| {
11381 s.select(new_selections);
11382 })
11383 });
11384 }
11385
11386 pub fn move_line_down(
11387 &mut self,
11388 _: &MoveLineDown,
11389 window: &mut Window,
11390 cx: &mut Context<Self>,
11391 ) {
11392 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11393 if self.mode.is_single_line() {
11394 cx.propagate();
11395 return;
11396 }
11397
11398 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11399 let buffer = self.buffer.read(cx).snapshot(cx);
11400
11401 let mut edits = Vec::new();
11402 let mut unfold_ranges = Vec::new();
11403 let mut refold_creases = Vec::new();
11404
11405 let selections = self.selections.all::<Point>(cx);
11406 let mut selections = selections.iter().peekable();
11407 let mut contiguous_row_selections = Vec::new();
11408 let mut new_selections = Vec::new();
11409
11410 while let Some(selection) = selections.next() {
11411 // Find all the selections that span a contiguous row range
11412 let (start_row, end_row) = consume_contiguous_rows(
11413 &mut contiguous_row_selections,
11414 selection,
11415 &display_map,
11416 &mut selections,
11417 );
11418
11419 // Move the text spanned by the row range to be after the last line of the row range
11420 if end_row.0 <= buffer.max_point().row {
11421 let range_to_move =
11422 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11423 let insertion_point = display_map
11424 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11425 .0;
11426
11427 // Don't move lines across excerpt boundaries
11428 if buffer
11429 .excerpt_containing(range_to_move.start..insertion_point)
11430 .is_some()
11431 {
11432 let mut text = String::from("\n");
11433 text.extend(buffer.text_for_range(range_to_move.clone()));
11434 text.pop(); // Drop trailing newline
11435 edits.push((
11436 buffer.anchor_after(range_to_move.start)
11437 ..buffer.anchor_before(range_to_move.end),
11438 String::new(),
11439 ));
11440 let insertion_anchor = buffer.anchor_after(insertion_point);
11441 edits.push((insertion_anchor..insertion_anchor, text));
11442
11443 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11444
11445 // Move selections down
11446 new_selections.extend(contiguous_row_selections.drain(..).map(
11447 |mut selection| {
11448 selection.start.row += row_delta;
11449 selection.end.row += row_delta;
11450 selection
11451 },
11452 ));
11453
11454 // Move folds down
11455 unfold_ranges.push(range_to_move.clone());
11456 for fold in display_map.folds_in_range(
11457 buffer.anchor_before(range_to_move.start)
11458 ..buffer.anchor_after(range_to_move.end),
11459 ) {
11460 let mut start = fold.range.start.to_point(&buffer);
11461 let mut end = fold.range.end.to_point(&buffer);
11462 start.row += row_delta;
11463 end.row += row_delta;
11464 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11465 }
11466 }
11467 }
11468
11469 // If we didn't move line(s), preserve the existing selections
11470 new_selections.append(&mut contiguous_row_selections);
11471 }
11472
11473 self.transact(window, cx, |this, window, cx| {
11474 this.unfold_ranges(&unfold_ranges, true, true, cx);
11475 this.buffer.update(cx, |buffer, cx| {
11476 for (range, text) in edits {
11477 buffer.edit([(range, text)], None, cx);
11478 }
11479 });
11480 this.fold_creases(refold_creases, true, window, cx);
11481 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11482 });
11483 }
11484
11485 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11486 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11487 let text_layout_details = &self.text_layout_details(window);
11488 self.transact(window, cx, |this, window, cx| {
11489 let edits = this.change_selections(Default::default(), window, cx, |s| {
11490 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11491 s.move_with(|display_map, selection| {
11492 if !selection.is_empty() {
11493 return;
11494 }
11495
11496 let mut head = selection.head();
11497 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11498 if head.column() == display_map.line_len(head.row()) {
11499 transpose_offset = display_map
11500 .buffer_snapshot
11501 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11502 }
11503
11504 if transpose_offset == 0 {
11505 return;
11506 }
11507
11508 *head.column_mut() += 1;
11509 head = display_map.clip_point(head, Bias::Right);
11510 let goal = SelectionGoal::HorizontalPosition(
11511 display_map
11512 .x_for_display_point(head, text_layout_details)
11513 .into(),
11514 );
11515 selection.collapse_to(head, goal);
11516
11517 let transpose_start = display_map
11518 .buffer_snapshot
11519 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11520 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11521 let transpose_end = display_map
11522 .buffer_snapshot
11523 .clip_offset(transpose_offset + 1, Bias::Right);
11524 if let Some(ch) =
11525 display_map.buffer_snapshot.chars_at(transpose_start).next()
11526 {
11527 edits.push((transpose_start..transpose_offset, String::new()));
11528 edits.push((transpose_end..transpose_end, ch.to_string()));
11529 }
11530 }
11531 });
11532 edits
11533 });
11534 this.buffer
11535 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11536 let selections = this.selections.all::<usize>(cx);
11537 this.change_selections(Default::default(), window, cx, |s| {
11538 s.select(selections);
11539 });
11540 });
11541 }
11542
11543 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11544 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11545 if self.mode.is_single_line() {
11546 cx.propagate();
11547 return;
11548 }
11549
11550 self.rewrap_impl(RewrapOptions::default(), cx)
11551 }
11552
11553 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11554 let buffer = self.buffer.read(cx).snapshot(cx);
11555 let selections = self.selections.all::<Point>(cx);
11556
11557 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11558 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11559 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11560 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11561 .peekable();
11562
11563 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11564 row
11565 } else {
11566 return Vec::new();
11567 };
11568
11569 let language_settings = buffer.language_settings_at(selection.head(), cx);
11570 let language_scope = buffer.language_scope_at(selection.head());
11571
11572 let indent_and_prefix_for_row =
11573 |row: u32| -> (IndentSize, Option<String>, Option<String>) {
11574 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11575 let (comment_prefix, rewrap_prefix) =
11576 if let Some(language_scope) = &language_scope {
11577 let indent_end = Point::new(row, indent.len);
11578 let comment_prefix = language_scope
11579 .line_comment_prefixes()
11580 .iter()
11581 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11582 .map(|prefix| prefix.to_string());
11583 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11584 let line_text_after_indent = buffer
11585 .text_for_range(indent_end..line_end)
11586 .collect::<String>();
11587 let rewrap_prefix = language_scope
11588 .rewrap_prefixes()
11589 .iter()
11590 .find_map(|prefix_regex| {
11591 prefix_regex.find(&line_text_after_indent).map(|mat| {
11592 if mat.start() == 0 {
11593 Some(mat.as_str().to_string())
11594 } else {
11595 None
11596 }
11597 })
11598 })
11599 .flatten();
11600 (comment_prefix, rewrap_prefix)
11601 } else {
11602 (None, None)
11603 };
11604 (indent, comment_prefix, rewrap_prefix)
11605 };
11606
11607 let mut ranges = Vec::new();
11608 let from_empty_selection = selection.is_empty();
11609
11610 let mut current_range_start = first_row;
11611 let mut prev_row = first_row;
11612 let (
11613 mut current_range_indent,
11614 mut current_range_comment_prefix,
11615 mut current_range_rewrap_prefix,
11616 ) = indent_and_prefix_for_row(first_row);
11617
11618 for row in non_blank_rows_iter.skip(1) {
11619 let has_paragraph_break = row > prev_row + 1;
11620
11621 let (row_indent, row_comment_prefix, row_rewrap_prefix) =
11622 indent_and_prefix_for_row(row);
11623
11624 let has_indent_change = row_indent != current_range_indent;
11625 let has_comment_change = row_comment_prefix != current_range_comment_prefix;
11626
11627 let has_boundary_change = has_comment_change
11628 || row_rewrap_prefix.is_some()
11629 || (has_indent_change && current_range_comment_prefix.is_some());
11630
11631 if has_paragraph_break || has_boundary_change {
11632 ranges.push((
11633 language_settings.clone(),
11634 Point::new(current_range_start, 0)
11635 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11636 current_range_indent,
11637 current_range_comment_prefix.clone(),
11638 current_range_rewrap_prefix.clone(),
11639 from_empty_selection,
11640 ));
11641 current_range_start = row;
11642 current_range_indent = row_indent;
11643 current_range_comment_prefix = row_comment_prefix;
11644 current_range_rewrap_prefix = row_rewrap_prefix;
11645 }
11646 prev_row = row;
11647 }
11648
11649 ranges.push((
11650 language_settings.clone(),
11651 Point::new(current_range_start, 0)
11652 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11653 current_range_indent,
11654 current_range_comment_prefix,
11655 current_range_rewrap_prefix,
11656 from_empty_selection,
11657 ));
11658
11659 ranges
11660 });
11661
11662 let mut edits = Vec::new();
11663 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11664
11665 for (
11666 language_settings,
11667 wrap_range,
11668 indent_size,
11669 comment_prefix,
11670 rewrap_prefix,
11671 from_empty_selection,
11672 ) in wrap_ranges
11673 {
11674 let mut start_row = wrap_range.start.row;
11675 let mut end_row = wrap_range.end.row;
11676
11677 // Skip selections that overlap with a range that has already been rewrapped.
11678 let selection_range = start_row..end_row;
11679 if rewrapped_row_ranges
11680 .iter()
11681 .any(|range| range.overlaps(&selection_range))
11682 {
11683 continue;
11684 }
11685
11686 let tab_size = language_settings.tab_size;
11687
11688 let indent_prefix = indent_size.chars().collect::<String>();
11689 let mut line_prefix = indent_prefix.clone();
11690 let mut inside_comment = false;
11691 if let Some(prefix) = &comment_prefix {
11692 line_prefix.push_str(prefix);
11693 inside_comment = true;
11694 }
11695 if let Some(prefix) = &rewrap_prefix {
11696 line_prefix.push_str(prefix);
11697 }
11698
11699 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11700 RewrapBehavior::InComments => inside_comment,
11701 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11702 RewrapBehavior::Anywhere => true,
11703 };
11704
11705 let should_rewrap = options.override_language_settings
11706 || allow_rewrap_based_on_language
11707 || self.hard_wrap.is_some();
11708 if !should_rewrap {
11709 continue;
11710 }
11711
11712 if from_empty_selection {
11713 'expand_upwards: while start_row > 0 {
11714 let prev_row = start_row - 1;
11715 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11716 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11717 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11718 {
11719 start_row = prev_row;
11720 } else {
11721 break 'expand_upwards;
11722 }
11723 }
11724
11725 'expand_downwards: while end_row < buffer.max_point().row {
11726 let next_row = end_row + 1;
11727 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11728 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11729 && !buffer.is_line_blank(MultiBufferRow(next_row))
11730 {
11731 end_row = next_row;
11732 } else {
11733 break 'expand_downwards;
11734 }
11735 }
11736 }
11737
11738 let start = Point::new(start_row, 0);
11739 let start_offset = start.to_offset(&buffer);
11740 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11741 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11742 let Some(lines_without_prefixes) = selection_text
11743 .lines()
11744 .enumerate()
11745 .map(|(ix, line)| {
11746 let line_trimmed = line.trim_start();
11747 if rewrap_prefix.is_some() && ix > 0 {
11748 Ok(line_trimmed)
11749 } else {
11750 line_trimmed
11751 .strip_prefix(&line_prefix.trim_start())
11752 .with_context(|| {
11753 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11754 })
11755 }
11756 })
11757 .collect::<Result<Vec<_>, _>>()
11758 .log_err()
11759 else {
11760 continue;
11761 };
11762
11763 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11764 buffer
11765 .language_settings_at(Point::new(start_row, 0), cx)
11766 .preferred_line_length as usize
11767 });
11768
11769 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
11770 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
11771 } else {
11772 line_prefix.clone()
11773 };
11774
11775 let wrapped_text = wrap_with_prefix(
11776 line_prefix,
11777 subsequent_lines_prefix,
11778 lines_without_prefixes.join("\n"),
11779 wrap_column,
11780 tab_size,
11781 options.preserve_existing_whitespace,
11782 );
11783
11784 // TODO: should always use char-based diff while still supporting cursor behavior that
11785 // matches vim.
11786 let mut diff_options = DiffOptions::default();
11787 if options.override_language_settings {
11788 diff_options.max_word_diff_len = 0;
11789 diff_options.max_word_diff_line_count = 0;
11790 } else {
11791 diff_options.max_word_diff_len = usize::MAX;
11792 diff_options.max_word_diff_line_count = usize::MAX;
11793 }
11794
11795 for (old_range, new_text) in
11796 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11797 {
11798 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11799 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11800 edits.push((edit_start..edit_end, new_text));
11801 }
11802
11803 rewrapped_row_ranges.push(start_row..=end_row);
11804 }
11805
11806 self.buffer
11807 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11808 }
11809
11810 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11811 let mut text = String::new();
11812 let buffer = self.buffer.read(cx).snapshot(cx);
11813 let mut selections = self.selections.all::<Point>(cx);
11814 let mut clipboard_selections = Vec::with_capacity(selections.len());
11815 {
11816 let max_point = buffer.max_point();
11817 let mut is_first = true;
11818 for selection in &mut selections {
11819 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11820 if is_entire_line {
11821 selection.start = Point::new(selection.start.row, 0);
11822 if !selection.is_empty() && selection.end.column == 0 {
11823 selection.end = cmp::min(max_point, selection.end);
11824 } else {
11825 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11826 }
11827 selection.goal = SelectionGoal::None;
11828 }
11829 if is_first {
11830 is_first = false;
11831 } else {
11832 text += "\n";
11833 }
11834 let mut len = 0;
11835 for chunk in buffer.text_for_range(selection.start..selection.end) {
11836 text.push_str(chunk);
11837 len += chunk.len();
11838 }
11839 clipboard_selections.push(ClipboardSelection {
11840 len,
11841 is_entire_line,
11842 first_line_indent: buffer
11843 .indent_size_for_line(MultiBufferRow(selection.start.row))
11844 .len,
11845 });
11846 }
11847 }
11848
11849 self.transact(window, cx, |this, window, cx| {
11850 this.change_selections(Default::default(), window, cx, |s| {
11851 s.select(selections);
11852 });
11853 this.insert("", window, cx);
11854 });
11855 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11856 }
11857
11858 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11859 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11860 let item = self.cut_common(window, cx);
11861 cx.write_to_clipboard(item);
11862 }
11863
11864 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11865 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11866 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11867 s.move_with(|snapshot, sel| {
11868 if sel.is_empty() {
11869 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11870 }
11871 });
11872 });
11873 let item = self.cut_common(window, cx);
11874 cx.set_global(KillRing(item))
11875 }
11876
11877 pub fn kill_ring_yank(
11878 &mut self,
11879 _: &KillRingYank,
11880 window: &mut Window,
11881 cx: &mut Context<Self>,
11882 ) {
11883 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11884 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11885 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11886 (kill_ring.text().to_string(), kill_ring.metadata_json())
11887 } else {
11888 return;
11889 }
11890 } else {
11891 return;
11892 };
11893 self.do_paste(&text, metadata, false, window, cx);
11894 }
11895
11896 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11897 self.do_copy(true, cx);
11898 }
11899
11900 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11901 self.do_copy(false, cx);
11902 }
11903
11904 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11905 let selections = self.selections.all::<Point>(cx);
11906 let buffer = self.buffer.read(cx).read(cx);
11907 let mut text = String::new();
11908
11909 let mut clipboard_selections = Vec::with_capacity(selections.len());
11910 {
11911 let max_point = buffer.max_point();
11912 let mut is_first = true;
11913 for selection in &selections {
11914 let mut start = selection.start;
11915 let mut end = selection.end;
11916 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11917 if is_entire_line {
11918 start = Point::new(start.row, 0);
11919 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11920 }
11921
11922 let mut trimmed_selections = Vec::new();
11923 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11924 let row = MultiBufferRow(start.row);
11925 let first_indent = buffer.indent_size_for_line(row);
11926 if first_indent.len == 0 || start.column > first_indent.len {
11927 trimmed_selections.push(start..end);
11928 } else {
11929 trimmed_selections.push(
11930 Point::new(row.0, first_indent.len)
11931 ..Point::new(row.0, buffer.line_len(row)),
11932 );
11933 for row in start.row + 1..=end.row {
11934 let mut line_len = buffer.line_len(MultiBufferRow(row));
11935 if row == end.row {
11936 line_len = end.column;
11937 }
11938 if line_len == 0 {
11939 trimmed_selections
11940 .push(Point::new(row, 0)..Point::new(row, line_len));
11941 continue;
11942 }
11943 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11944 if row_indent_size.len >= first_indent.len {
11945 trimmed_selections.push(
11946 Point::new(row, first_indent.len)..Point::new(row, line_len),
11947 );
11948 } else {
11949 trimmed_selections.clear();
11950 trimmed_selections.push(start..end);
11951 break;
11952 }
11953 }
11954 }
11955 } else {
11956 trimmed_selections.push(start..end);
11957 }
11958
11959 for trimmed_range in trimmed_selections {
11960 if is_first {
11961 is_first = false;
11962 } else {
11963 text += "\n";
11964 }
11965 let mut len = 0;
11966 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11967 text.push_str(chunk);
11968 len += chunk.len();
11969 }
11970 clipboard_selections.push(ClipboardSelection {
11971 len,
11972 is_entire_line,
11973 first_line_indent: buffer
11974 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11975 .len,
11976 });
11977 }
11978 }
11979 }
11980
11981 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11982 text,
11983 clipboard_selections,
11984 ));
11985 }
11986
11987 pub fn do_paste(
11988 &mut self,
11989 text: &String,
11990 clipboard_selections: Option<Vec<ClipboardSelection>>,
11991 handle_entire_lines: bool,
11992 window: &mut Window,
11993 cx: &mut Context<Self>,
11994 ) {
11995 if self.read_only(cx) {
11996 return;
11997 }
11998
11999 let clipboard_text = Cow::Borrowed(text);
12000
12001 self.transact(window, cx, |this, window, cx| {
12002 if let Some(mut clipboard_selections) = clipboard_selections {
12003 let old_selections = this.selections.all::<usize>(cx);
12004 let all_selections_were_entire_line =
12005 clipboard_selections.iter().all(|s| s.is_entire_line);
12006 let first_selection_indent_column =
12007 clipboard_selections.first().map(|s| s.first_line_indent);
12008 if clipboard_selections.len() != old_selections.len() {
12009 clipboard_selections.drain(..);
12010 }
12011 let cursor_offset = this.selections.last::<usize>(cx).head();
12012 let mut auto_indent_on_paste = true;
12013
12014 this.buffer.update(cx, |buffer, cx| {
12015 let snapshot = buffer.read(cx);
12016 auto_indent_on_paste = snapshot
12017 .language_settings_at(cursor_offset, cx)
12018 .auto_indent_on_paste;
12019
12020 let mut start_offset = 0;
12021 let mut edits = Vec::new();
12022 let mut original_indent_columns = Vec::new();
12023 for (ix, selection) in old_selections.iter().enumerate() {
12024 let to_insert;
12025 let entire_line;
12026 let original_indent_column;
12027 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12028 let end_offset = start_offset + clipboard_selection.len;
12029 to_insert = &clipboard_text[start_offset..end_offset];
12030 entire_line = clipboard_selection.is_entire_line;
12031 start_offset = end_offset + 1;
12032 original_indent_column = Some(clipboard_selection.first_line_indent);
12033 } else {
12034 to_insert = clipboard_text.as_str();
12035 entire_line = all_selections_were_entire_line;
12036 original_indent_column = first_selection_indent_column
12037 }
12038
12039 // If the corresponding selection was empty when this slice of the
12040 // clipboard text was written, then the entire line containing the
12041 // selection was copied. If this selection is also currently empty,
12042 // then paste the line before the current line of the buffer.
12043 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12044 let column = selection.start.to_point(&snapshot).column as usize;
12045 let line_start = selection.start - column;
12046 line_start..line_start
12047 } else {
12048 selection.range()
12049 };
12050
12051 edits.push((range, to_insert));
12052 original_indent_columns.push(original_indent_column);
12053 }
12054 drop(snapshot);
12055
12056 buffer.edit(
12057 edits,
12058 if auto_indent_on_paste {
12059 Some(AutoindentMode::Block {
12060 original_indent_columns,
12061 })
12062 } else {
12063 None
12064 },
12065 cx,
12066 );
12067 });
12068
12069 let selections = this.selections.all::<usize>(cx);
12070 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12071 } else {
12072 this.insert(&clipboard_text, window, cx);
12073 }
12074 });
12075 }
12076
12077 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12078 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12079 if let Some(item) = cx.read_from_clipboard() {
12080 let entries = item.entries();
12081
12082 match entries.first() {
12083 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12084 // of all the pasted entries.
12085 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12086 .do_paste(
12087 clipboard_string.text(),
12088 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12089 true,
12090 window,
12091 cx,
12092 ),
12093 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12094 }
12095 }
12096 }
12097
12098 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12099 if self.read_only(cx) {
12100 return;
12101 }
12102
12103 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12104
12105 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12106 if let Some((selections, _)) =
12107 self.selection_history.transaction(transaction_id).cloned()
12108 {
12109 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12110 s.select_anchors(selections.to_vec());
12111 });
12112 } else {
12113 log::error!(
12114 "No entry in selection_history found for undo. \
12115 This may correspond to a bug where undo does not update the selection. \
12116 If this is occurring, please add details to \
12117 https://github.com/zed-industries/zed/issues/22692"
12118 );
12119 }
12120 self.request_autoscroll(Autoscroll::fit(), cx);
12121 self.unmark_text(window, cx);
12122 self.refresh_inline_completion(true, false, window, cx);
12123 cx.emit(EditorEvent::Edited { transaction_id });
12124 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12125 }
12126 }
12127
12128 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12129 if self.read_only(cx) {
12130 return;
12131 }
12132
12133 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12134
12135 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12136 if let Some((_, Some(selections))) =
12137 self.selection_history.transaction(transaction_id).cloned()
12138 {
12139 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12140 s.select_anchors(selections.to_vec());
12141 });
12142 } else {
12143 log::error!(
12144 "No entry in selection_history found for redo. \
12145 This may correspond to a bug where undo does not update the selection. \
12146 If this is occurring, please add details to \
12147 https://github.com/zed-industries/zed/issues/22692"
12148 );
12149 }
12150 self.request_autoscroll(Autoscroll::fit(), cx);
12151 self.unmark_text(window, cx);
12152 self.refresh_inline_completion(true, false, window, cx);
12153 cx.emit(EditorEvent::Edited { transaction_id });
12154 }
12155 }
12156
12157 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12158 self.buffer
12159 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12160 }
12161
12162 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12163 self.buffer
12164 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12165 }
12166
12167 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12168 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12169 self.change_selections(Default::default(), window, cx, |s| {
12170 s.move_with(|map, selection| {
12171 let cursor = if selection.is_empty() {
12172 movement::left(map, selection.start)
12173 } else {
12174 selection.start
12175 };
12176 selection.collapse_to(cursor, SelectionGoal::None);
12177 });
12178 })
12179 }
12180
12181 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12182 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12183 self.change_selections(Default::default(), window, cx, |s| {
12184 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12185 })
12186 }
12187
12188 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12189 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12190 self.change_selections(Default::default(), window, cx, |s| {
12191 s.move_with(|map, selection| {
12192 let cursor = if selection.is_empty() {
12193 movement::right(map, selection.end)
12194 } else {
12195 selection.end
12196 };
12197 selection.collapse_to(cursor, SelectionGoal::None)
12198 });
12199 })
12200 }
12201
12202 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12203 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12204 self.change_selections(Default::default(), window, cx, |s| {
12205 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12206 })
12207 }
12208
12209 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12210 if self.take_rename(true, window, cx).is_some() {
12211 return;
12212 }
12213
12214 if self.mode.is_single_line() {
12215 cx.propagate();
12216 return;
12217 }
12218
12219 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12220
12221 let text_layout_details = &self.text_layout_details(window);
12222 let selection_count = self.selections.count();
12223 let first_selection = self.selections.first_anchor();
12224
12225 self.change_selections(Default::default(), window, cx, |s| {
12226 s.move_with(|map, selection| {
12227 if !selection.is_empty() {
12228 selection.goal = SelectionGoal::None;
12229 }
12230 let (cursor, goal) = movement::up(
12231 map,
12232 selection.start,
12233 selection.goal,
12234 false,
12235 text_layout_details,
12236 );
12237 selection.collapse_to(cursor, goal);
12238 });
12239 });
12240
12241 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12242 {
12243 cx.propagate();
12244 }
12245 }
12246
12247 pub fn move_up_by_lines(
12248 &mut self,
12249 action: &MoveUpByLines,
12250 window: &mut Window,
12251 cx: &mut Context<Self>,
12252 ) {
12253 if self.take_rename(true, window, cx).is_some() {
12254 return;
12255 }
12256
12257 if self.mode.is_single_line() {
12258 cx.propagate();
12259 return;
12260 }
12261
12262 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12263
12264 let text_layout_details = &self.text_layout_details(window);
12265
12266 self.change_selections(Default::default(), window, cx, |s| {
12267 s.move_with(|map, selection| {
12268 if !selection.is_empty() {
12269 selection.goal = SelectionGoal::None;
12270 }
12271 let (cursor, goal) = movement::up_by_rows(
12272 map,
12273 selection.start,
12274 action.lines,
12275 selection.goal,
12276 false,
12277 text_layout_details,
12278 );
12279 selection.collapse_to(cursor, goal);
12280 });
12281 })
12282 }
12283
12284 pub fn move_down_by_lines(
12285 &mut self,
12286 action: &MoveDownByLines,
12287 window: &mut Window,
12288 cx: &mut Context<Self>,
12289 ) {
12290 if self.take_rename(true, window, cx).is_some() {
12291 return;
12292 }
12293
12294 if self.mode.is_single_line() {
12295 cx.propagate();
12296 return;
12297 }
12298
12299 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12300
12301 let text_layout_details = &self.text_layout_details(window);
12302
12303 self.change_selections(Default::default(), window, cx, |s| {
12304 s.move_with(|map, selection| {
12305 if !selection.is_empty() {
12306 selection.goal = SelectionGoal::None;
12307 }
12308 let (cursor, goal) = movement::down_by_rows(
12309 map,
12310 selection.start,
12311 action.lines,
12312 selection.goal,
12313 false,
12314 text_layout_details,
12315 );
12316 selection.collapse_to(cursor, goal);
12317 });
12318 })
12319 }
12320
12321 pub fn select_down_by_lines(
12322 &mut self,
12323 action: &SelectDownByLines,
12324 window: &mut Window,
12325 cx: &mut Context<Self>,
12326 ) {
12327 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12328 let text_layout_details = &self.text_layout_details(window);
12329 self.change_selections(Default::default(), window, cx, |s| {
12330 s.move_heads_with(|map, head, goal| {
12331 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12332 })
12333 })
12334 }
12335
12336 pub fn select_up_by_lines(
12337 &mut self,
12338 action: &SelectUpByLines,
12339 window: &mut Window,
12340 cx: &mut Context<Self>,
12341 ) {
12342 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12343 let text_layout_details = &self.text_layout_details(window);
12344 self.change_selections(Default::default(), window, cx, |s| {
12345 s.move_heads_with(|map, head, goal| {
12346 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12347 })
12348 })
12349 }
12350
12351 pub fn select_page_up(
12352 &mut self,
12353 _: &SelectPageUp,
12354 window: &mut Window,
12355 cx: &mut Context<Self>,
12356 ) {
12357 let Some(row_count) = self.visible_row_count() else {
12358 return;
12359 };
12360
12361 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12362
12363 let text_layout_details = &self.text_layout_details(window);
12364
12365 self.change_selections(Default::default(), window, cx, |s| {
12366 s.move_heads_with(|map, head, goal| {
12367 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12368 })
12369 })
12370 }
12371
12372 pub fn move_page_up(
12373 &mut self,
12374 action: &MovePageUp,
12375 window: &mut Window,
12376 cx: &mut Context<Self>,
12377 ) {
12378 if self.take_rename(true, window, cx).is_some() {
12379 return;
12380 }
12381
12382 if self
12383 .context_menu
12384 .borrow_mut()
12385 .as_mut()
12386 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12387 .unwrap_or(false)
12388 {
12389 return;
12390 }
12391
12392 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12393 cx.propagate();
12394 return;
12395 }
12396
12397 let Some(row_count) = self.visible_row_count() else {
12398 return;
12399 };
12400
12401 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12402
12403 let effects = if action.center_cursor {
12404 SelectionEffects::scroll(Autoscroll::center())
12405 } else {
12406 SelectionEffects::default()
12407 };
12408
12409 let text_layout_details = &self.text_layout_details(window);
12410
12411 self.change_selections(effects, window, cx, |s| {
12412 s.move_with(|map, selection| {
12413 if !selection.is_empty() {
12414 selection.goal = SelectionGoal::None;
12415 }
12416 let (cursor, goal) = movement::up_by_rows(
12417 map,
12418 selection.end,
12419 row_count,
12420 selection.goal,
12421 false,
12422 text_layout_details,
12423 );
12424 selection.collapse_to(cursor, goal);
12425 });
12426 });
12427 }
12428
12429 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12430 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12431 let text_layout_details = &self.text_layout_details(window);
12432 self.change_selections(Default::default(), window, cx, |s| {
12433 s.move_heads_with(|map, head, goal| {
12434 movement::up(map, head, goal, false, text_layout_details)
12435 })
12436 })
12437 }
12438
12439 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12440 self.take_rename(true, window, cx);
12441
12442 if self.mode.is_single_line() {
12443 cx.propagate();
12444 return;
12445 }
12446
12447 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12448
12449 let text_layout_details = &self.text_layout_details(window);
12450 let selection_count = self.selections.count();
12451 let first_selection = self.selections.first_anchor();
12452
12453 self.change_selections(Default::default(), window, cx, |s| {
12454 s.move_with(|map, selection| {
12455 if !selection.is_empty() {
12456 selection.goal = SelectionGoal::None;
12457 }
12458 let (cursor, goal) = movement::down(
12459 map,
12460 selection.end,
12461 selection.goal,
12462 false,
12463 text_layout_details,
12464 );
12465 selection.collapse_to(cursor, goal);
12466 });
12467 });
12468
12469 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12470 {
12471 cx.propagate();
12472 }
12473 }
12474
12475 pub fn select_page_down(
12476 &mut self,
12477 _: &SelectPageDown,
12478 window: &mut Window,
12479 cx: &mut Context<Self>,
12480 ) {
12481 let Some(row_count) = self.visible_row_count() else {
12482 return;
12483 };
12484
12485 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12486
12487 let text_layout_details = &self.text_layout_details(window);
12488
12489 self.change_selections(Default::default(), window, cx, |s| {
12490 s.move_heads_with(|map, head, goal| {
12491 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12492 })
12493 })
12494 }
12495
12496 pub fn move_page_down(
12497 &mut self,
12498 action: &MovePageDown,
12499 window: &mut Window,
12500 cx: &mut Context<Self>,
12501 ) {
12502 if self.take_rename(true, window, cx).is_some() {
12503 return;
12504 }
12505
12506 if self
12507 .context_menu
12508 .borrow_mut()
12509 .as_mut()
12510 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12511 .unwrap_or(false)
12512 {
12513 return;
12514 }
12515
12516 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12517 cx.propagate();
12518 return;
12519 }
12520
12521 let Some(row_count) = self.visible_row_count() else {
12522 return;
12523 };
12524
12525 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12526
12527 let effects = if action.center_cursor {
12528 SelectionEffects::scroll(Autoscroll::center())
12529 } else {
12530 SelectionEffects::default()
12531 };
12532
12533 let text_layout_details = &self.text_layout_details(window);
12534 self.change_selections(effects, window, cx, |s| {
12535 s.move_with(|map, selection| {
12536 if !selection.is_empty() {
12537 selection.goal = SelectionGoal::None;
12538 }
12539 let (cursor, goal) = movement::down_by_rows(
12540 map,
12541 selection.end,
12542 row_count,
12543 selection.goal,
12544 false,
12545 text_layout_details,
12546 );
12547 selection.collapse_to(cursor, goal);
12548 });
12549 });
12550 }
12551
12552 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12553 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12554 let text_layout_details = &self.text_layout_details(window);
12555 self.change_selections(Default::default(), window, cx, |s| {
12556 s.move_heads_with(|map, head, goal| {
12557 movement::down(map, head, goal, false, text_layout_details)
12558 })
12559 });
12560 }
12561
12562 pub fn context_menu_first(
12563 &mut self,
12564 _: &ContextMenuFirst,
12565 window: &mut Window,
12566 cx: &mut Context<Self>,
12567 ) {
12568 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12569 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12570 }
12571 }
12572
12573 pub fn context_menu_prev(
12574 &mut self,
12575 _: &ContextMenuPrevious,
12576 window: &mut Window,
12577 cx: &mut Context<Self>,
12578 ) {
12579 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12580 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12581 }
12582 }
12583
12584 pub fn context_menu_next(
12585 &mut self,
12586 _: &ContextMenuNext,
12587 window: &mut Window,
12588 cx: &mut Context<Self>,
12589 ) {
12590 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12591 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12592 }
12593 }
12594
12595 pub fn context_menu_last(
12596 &mut self,
12597 _: &ContextMenuLast,
12598 window: &mut Window,
12599 cx: &mut Context<Self>,
12600 ) {
12601 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12602 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12603 }
12604 }
12605
12606 pub fn signature_help_prev(
12607 &mut self,
12608 _: &SignatureHelpPrevious,
12609 _: &mut Window,
12610 cx: &mut Context<Self>,
12611 ) {
12612 if let Some(popover) = self.signature_help_state.popover_mut() {
12613 if popover.current_signature == 0 {
12614 popover.current_signature = popover.signatures.len() - 1;
12615 } else {
12616 popover.current_signature -= 1;
12617 }
12618 cx.notify();
12619 }
12620 }
12621
12622 pub fn signature_help_next(
12623 &mut self,
12624 _: &SignatureHelpNext,
12625 _: &mut Window,
12626 cx: &mut Context<Self>,
12627 ) {
12628 if let Some(popover) = self.signature_help_state.popover_mut() {
12629 if popover.current_signature + 1 == popover.signatures.len() {
12630 popover.current_signature = 0;
12631 } else {
12632 popover.current_signature += 1;
12633 }
12634 cx.notify();
12635 }
12636 }
12637
12638 pub fn move_to_previous_word_start(
12639 &mut self,
12640 _: &MoveToPreviousWordStart,
12641 window: &mut Window,
12642 cx: &mut Context<Self>,
12643 ) {
12644 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12645 self.change_selections(Default::default(), window, cx, |s| {
12646 s.move_cursors_with(|map, head, _| {
12647 (
12648 movement::previous_word_start(map, head),
12649 SelectionGoal::None,
12650 )
12651 });
12652 })
12653 }
12654
12655 pub fn move_to_previous_subword_start(
12656 &mut self,
12657 _: &MoveToPreviousSubwordStart,
12658 window: &mut Window,
12659 cx: &mut Context<Self>,
12660 ) {
12661 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12662 self.change_selections(Default::default(), window, cx, |s| {
12663 s.move_cursors_with(|map, head, _| {
12664 (
12665 movement::previous_subword_start(map, head),
12666 SelectionGoal::None,
12667 )
12668 });
12669 })
12670 }
12671
12672 pub fn select_to_previous_word_start(
12673 &mut self,
12674 _: &SelectToPreviousWordStart,
12675 window: &mut Window,
12676 cx: &mut Context<Self>,
12677 ) {
12678 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12679 self.change_selections(Default::default(), window, cx, |s| {
12680 s.move_heads_with(|map, head, _| {
12681 (
12682 movement::previous_word_start(map, head),
12683 SelectionGoal::None,
12684 )
12685 });
12686 })
12687 }
12688
12689 pub fn select_to_previous_subword_start(
12690 &mut self,
12691 _: &SelectToPreviousSubwordStart,
12692 window: &mut Window,
12693 cx: &mut Context<Self>,
12694 ) {
12695 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12696 self.change_selections(Default::default(), window, cx, |s| {
12697 s.move_heads_with(|map, head, _| {
12698 (
12699 movement::previous_subword_start(map, head),
12700 SelectionGoal::None,
12701 )
12702 });
12703 })
12704 }
12705
12706 pub fn delete_to_previous_word_start(
12707 &mut self,
12708 action: &DeleteToPreviousWordStart,
12709 window: &mut Window,
12710 cx: &mut Context<Self>,
12711 ) {
12712 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12713 self.transact(window, cx, |this, window, cx| {
12714 this.select_autoclose_pair(window, cx);
12715 this.change_selections(Default::default(), window, cx, |s| {
12716 s.move_with(|map, selection| {
12717 if selection.is_empty() {
12718 let cursor = if action.ignore_newlines {
12719 movement::previous_word_start(map, selection.head())
12720 } else {
12721 movement::previous_word_start_or_newline(map, selection.head())
12722 };
12723 selection.set_head(cursor, SelectionGoal::None);
12724 }
12725 });
12726 });
12727 this.insert("", window, cx);
12728 });
12729 }
12730
12731 pub fn delete_to_previous_subword_start(
12732 &mut self,
12733 _: &DeleteToPreviousSubwordStart,
12734 window: &mut Window,
12735 cx: &mut Context<Self>,
12736 ) {
12737 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12738 self.transact(window, cx, |this, window, cx| {
12739 this.select_autoclose_pair(window, cx);
12740 this.change_selections(Default::default(), window, cx, |s| {
12741 s.move_with(|map, selection| {
12742 if selection.is_empty() {
12743 let cursor = movement::previous_subword_start(map, selection.head());
12744 selection.set_head(cursor, SelectionGoal::None);
12745 }
12746 });
12747 });
12748 this.insert("", window, cx);
12749 });
12750 }
12751
12752 pub fn move_to_next_word_end(
12753 &mut self,
12754 _: &MoveToNextWordEnd,
12755 window: &mut Window,
12756 cx: &mut Context<Self>,
12757 ) {
12758 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12759 self.change_selections(Default::default(), window, cx, |s| {
12760 s.move_cursors_with(|map, head, _| {
12761 (movement::next_word_end(map, head), SelectionGoal::None)
12762 });
12763 })
12764 }
12765
12766 pub fn move_to_next_subword_end(
12767 &mut self,
12768 _: &MoveToNextSubwordEnd,
12769 window: &mut Window,
12770 cx: &mut Context<Self>,
12771 ) {
12772 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12773 self.change_selections(Default::default(), window, cx, |s| {
12774 s.move_cursors_with(|map, head, _| {
12775 (movement::next_subword_end(map, head), SelectionGoal::None)
12776 });
12777 })
12778 }
12779
12780 pub fn select_to_next_word_end(
12781 &mut self,
12782 _: &SelectToNextWordEnd,
12783 window: &mut Window,
12784 cx: &mut Context<Self>,
12785 ) {
12786 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12787 self.change_selections(Default::default(), window, cx, |s| {
12788 s.move_heads_with(|map, head, _| {
12789 (movement::next_word_end(map, head), SelectionGoal::None)
12790 });
12791 })
12792 }
12793
12794 pub fn select_to_next_subword_end(
12795 &mut self,
12796 _: &SelectToNextSubwordEnd,
12797 window: &mut Window,
12798 cx: &mut Context<Self>,
12799 ) {
12800 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12801 self.change_selections(Default::default(), window, cx, |s| {
12802 s.move_heads_with(|map, head, _| {
12803 (movement::next_subword_end(map, head), SelectionGoal::None)
12804 });
12805 })
12806 }
12807
12808 pub fn delete_to_next_word_end(
12809 &mut self,
12810 action: &DeleteToNextWordEnd,
12811 window: &mut Window,
12812 cx: &mut Context<Self>,
12813 ) {
12814 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12815 self.transact(window, cx, |this, window, cx| {
12816 this.change_selections(Default::default(), window, cx, |s| {
12817 s.move_with(|map, selection| {
12818 if selection.is_empty() {
12819 let cursor = if action.ignore_newlines {
12820 movement::next_word_end(map, selection.head())
12821 } else {
12822 movement::next_word_end_or_newline(map, selection.head())
12823 };
12824 selection.set_head(cursor, SelectionGoal::None);
12825 }
12826 });
12827 });
12828 this.insert("", window, cx);
12829 });
12830 }
12831
12832 pub fn delete_to_next_subword_end(
12833 &mut self,
12834 _: &DeleteToNextSubwordEnd,
12835 window: &mut Window,
12836 cx: &mut Context<Self>,
12837 ) {
12838 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12839 self.transact(window, cx, |this, window, cx| {
12840 this.change_selections(Default::default(), window, cx, |s| {
12841 s.move_with(|map, selection| {
12842 if selection.is_empty() {
12843 let cursor = movement::next_subword_end(map, selection.head());
12844 selection.set_head(cursor, SelectionGoal::None);
12845 }
12846 });
12847 });
12848 this.insert("", window, cx);
12849 });
12850 }
12851
12852 pub fn move_to_beginning_of_line(
12853 &mut self,
12854 action: &MoveToBeginningOfLine,
12855 window: &mut Window,
12856 cx: &mut Context<Self>,
12857 ) {
12858 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12859 self.change_selections(Default::default(), window, cx, |s| {
12860 s.move_cursors_with(|map, head, _| {
12861 (
12862 movement::indented_line_beginning(
12863 map,
12864 head,
12865 action.stop_at_soft_wraps,
12866 action.stop_at_indent,
12867 ),
12868 SelectionGoal::None,
12869 )
12870 });
12871 })
12872 }
12873
12874 pub fn select_to_beginning_of_line(
12875 &mut self,
12876 action: &SelectToBeginningOfLine,
12877 window: &mut Window,
12878 cx: &mut Context<Self>,
12879 ) {
12880 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12881 self.change_selections(Default::default(), window, cx, |s| {
12882 s.move_heads_with(|map, head, _| {
12883 (
12884 movement::indented_line_beginning(
12885 map,
12886 head,
12887 action.stop_at_soft_wraps,
12888 action.stop_at_indent,
12889 ),
12890 SelectionGoal::None,
12891 )
12892 });
12893 });
12894 }
12895
12896 pub fn delete_to_beginning_of_line(
12897 &mut self,
12898 action: &DeleteToBeginningOfLine,
12899 window: &mut Window,
12900 cx: &mut Context<Self>,
12901 ) {
12902 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12903 self.transact(window, cx, |this, window, cx| {
12904 this.change_selections(Default::default(), window, cx, |s| {
12905 s.move_with(|_, selection| {
12906 selection.reversed = true;
12907 });
12908 });
12909
12910 this.select_to_beginning_of_line(
12911 &SelectToBeginningOfLine {
12912 stop_at_soft_wraps: false,
12913 stop_at_indent: action.stop_at_indent,
12914 },
12915 window,
12916 cx,
12917 );
12918 this.backspace(&Backspace, window, cx);
12919 });
12920 }
12921
12922 pub fn move_to_end_of_line(
12923 &mut self,
12924 action: &MoveToEndOfLine,
12925 window: &mut Window,
12926 cx: &mut Context<Self>,
12927 ) {
12928 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12929 self.change_selections(Default::default(), window, cx, |s| {
12930 s.move_cursors_with(|map, head, _| {
12931 (
12932 movement::line_end(map, head, action.stop_at_soft_wraps),
12933 SelectionGoal::None,
12934 )
12935 });
12936 })
12937 }
12938
12939 pub fn select_to_end_of_line(
12940 &mut self,
12941 action: &SelectToEndOfLine,
12942 window: &mut Window,
12943 cx: &mut Context<Self>,
12944 ) {
12945 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12946 self.change_selections(Default::default(), window, cx, |s| {
12947 s.move_heads_with(|map, head, _| {
12948 (
12949 movement::line_end(map, head, action.stop_at_soft_wraps),
12950 SelectionGoal::None,
12951 )
12952 });
12953 })
12954 }
12955
12956 pub fn delete_to_end_of_line(
12957 &mut self,
12958 _: &DeleteToEndOfLine,
12959 window: &mut Window,
12960 cx: &mut Context<Self>,
12961 ) {
12962 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12963 self.transact(window, cx, |this, window, cx| {
12964 this.select_to_end_of_line(
12965 &SelectToEndOfLine {
12966 stop_at_soft_wraps: false,
12967 },
12968 window,
12969 cx,
12970 );
12971 this.delete(&Delete, window, cx);
12972 });
12973 }
12974
12975 pub fn cut_to_end_of_line(
12976 &mut self,
12977 _: &CutToEndOfLine,
12978 window: &mut Window,
12979 cx: &mut Context<Self>,
12980 ) {
12981 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12982 self.transact(window, cx, |this, window, cx| {
12983 this.select_to_end_of_line(
12984 &SelectToEndOfLine {
12985 stop_at_soft_wraps: false,
12986 },
12987 window,
12988 cx,
12989 );
12990 this.cut(&Cut, window, cx);
12991 });
12992 }
12993
12994 pub fn move_to_start_of_paragraph(
12995 &mut self,
12996 _: &MoveToStartOfParagraph,
12997 window: &mut Window,
12998 cx: &mut Context<Self>,
12999 ) {
13000 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13001 cx.propagate();
13002 return;
13003 }
13004 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13005 self.change_selections(Default::default(), window, cx, |s| {
13006 s.move_with(|map, selection| {
13007 selection.collapse_to(
13008 movement::start_of_paragraph(map, selection.head(), 1),
13009 SelectionGoal::None,
13010 )
13011 });
13012 })
13013 }
13014
13015 pub fn move_to_end_of_paragraph(
13016 &mut self,
13017 _: &MoveToEndOfParagraph,
13018 window: &mut Window,
13019 cx: &mut Context<Self>,
13020 ) {
13021 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13022 cx.propagate();
13023 return;
13024 }
13025 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13026 self.change_selections(Default::default(), window, cx, |s| {
13027 s.move_with(|map, selection| {
13028 selection.collapse_to(
13029 movement::end_of_paragraph(map, selection.head(), 1),
13030 SelectionGoal::None,
13031 )
13032 });
13033 })
13034 }
13035
13036 pub fn select_to_start_of_paragraph(
13037 &mut self,
13038 _: &SelectToStartOfParagraph,
13039 window: &mut Window,
13040 cx: &mut Context<Self>,
13041 ) {
13042 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13043 cx.propagate();
13044 return;
13045 }
13046 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13047 self.change_selections(Default::default(), window, cx, |s| {
13048 s.move_heads_with(|map, head, _| {
13049 (
13050 movement::start_of_paragraph(map, head, 1),
13051 SelectionGoal::None,
13052 )
13053 });
13054 })
13055 }
13056
13057 pub fn select_to_end_of_paragraph(
13058 &mut self,
13059 _: &SelectToEndOfParagraph,
13060 window: &mut Window,
13061 cx: &mut Context<Self>,
13062 ) {
13063 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13064 cx.propagate();
13065 return;
13066 }
13067 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13068 self.change_selections(Default::default(), window, cx, |s| {
13069 s.move_heads_with(|map, head, _| {
13070 (
13071 movement::end_of_paragraph(map, head, 1),
13072 SelectionGoal::None,
13073 )
13074 });
13075 })
13076 }
13077
13078 pub fn move_to_start_of_excerpt(
13079 &mut self,
13080 _: &MoveToStartOfExcerpt,
13081 window: &mut Window,
13082 cx: &mut Context<Self>,
13083 ) {
13084 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13085 cx.propagate();
13086 return;
13087 }
13088 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13089 self.change_selections(Default::default(), window, cx, |s| {
13090 s.move_with(|map, selection| {
13091 selection.collapse_to(
13092 movement::start_of_excerpt(
13093 map,
13094 selection.head(),
13095 workspace::searchable::Direction::Prev,
13096 ),
13097 SelectionGoal::None,
13098 )
13099 });
13100 })
13101 }
13102
13103 pub fn move_to_start_of_next_excerpt(
13104 &mut self,
13105 _: &MoveToStartOfNextExcerpt,
13106 window: &mut Window,
13107 cx: &mut Context<Self>,
13108 ) {
13109 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13110 cx.propagate();
13111 return;
13112 }
13113
13114 self.change_selections(Default::default(), window, cx, |s| {
13115 s.move_with(|map, selection| {
13116 selection.collapse_to(
13117 movement::start_of_excerpt(
13118 map,
13119 selection.head(),
13120 workspace::searchable::Direction::Next,
13121 ),
13122 SelectionGoal::None,
13123 )
13124 });
13125 })
13126 }
13127
13128 pub fn move_to_end_of_excerpt(
13129 &mut self,
13130 _: &MoveToEndOfExcerpt,
13131 window: &mut Window,
13132 cx: &mut Context<Self>,
13133 ) {
13134 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13135 cx.propagate();
13136 return;
13137 }
13138 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13139 self.change_selections(Default::default(), window, cx, |s| {
13140 s.move_with(|map, selection| {
13141 selection.collapse_to(
13142 movement::end_of_excerpt(
13143 map,
13144 selection.head(),
13145 workspace::searchable::Direction::Next,
13146 ),
13147 SelectionGoal::None,
13148 )
13149 });
13150 })
13151 }
13152
13153 pub fn move_to_end_of_previous_excerpt(
13154 &mut self,
13155 _: &MoveToEndOfPreviousExcerpt,
13156 window: &mut Window,
13157 cx: &mut Context<Self>,
13158 ) {
13159 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13160 cx.propagate();
13161 return;
13162 }
13163 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13164 self.change_selections(Default::default(), window, cx, |s| {
13165 s.move_with(|map, selection| {
13166 selection.collapse_to(
13167 movement::end_of_excerpt(
13168 map,
13169 selection.head(),
13170 workspace::searchable::Direction::Prev,
13171 ),
13172 SelectionGoal::None,
13173 )
13174 });
13175 })
13176 }
13177
13178 pub fn select_to_start_of_excerpt(
13179 &mut self,
13180 _: &SelectToStartOfExcerpt,
13181 window: &mut Window,
13182 cx: &mut Context<Self>,
13183 ) {
13184 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13185 cx.propagate();
13186 return;
13187 }
13188 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13189 self.change_selections(Default::default(), window, cx, |s| {
13190 s.move_heads_with(|map, head, _| {
13191 (
13192 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13193 SelectionGoal::None,
13194 )
13195 });
13196 })
13197 }
13198
13199 pub fn select_to_start_of_next_excerpt(
13200 &mut self,
13201 _: &SelectToStartOfNextExcerpt,
13202 window: &mut Window,
13203 cx: &mut Context<Self>,
13204 ) {
13205 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13206 cx.propagate();
13207 return;
13208 }
13209 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13210 self.change_selections(Default::default(), window, cx, |s| {
13211 s.move_heads_with(|map, head, _| {
13212 (
13213 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13214 SelectionGoal::None,
13215 )
13216 });
13217 })
13218 }
13219
13220 pub fn select_to_end_of_excerpt(
13221 &mut self,
13222 _: &SelectToEndOfExcerpt,
13223 window: &mut Window,
13224 cx: &mut Context<Self>,
13225 ) {
13226 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13227 cx.propagate();
13228 return;
13229 }
13230 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13231 self.change_selections(Default::default(), window, cx, |s| {
13232 s.move_heads_with(|map, head, _| {
13233 (
13234 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13235 SelectionGoal::None,
13236 )
13237 });
13238 })
13239 }
13240
13241 pub fn select_to_end_of_previous_excerpt(
13242 &mut self,
13243 _: &SelectToEndOfPreviousExcerpt,
13244 window: &mut Window,
13245 cx: &mut Context<Self>,
13246 ) {
13247 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13248 cx.propagate();
13249 return;
13250 }
13251 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13252 self.change_selections(Default::default(), window, cx, |s| {
13253 s.move_heads_with(|map, head, _| {
13254 (
13255 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13256 SelectionGoal::None,
13257 )
13258 });
13259 })
13260 }
13261
13262 pub fn move_to_beginning(
13263 &mut self,
13264 _: &MoveToBeginning,
13265 window: &mut Window,
13266 cx: &mut Context<Self>,
13267 ) {
13268 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13269 cx.propagate();
13270 return;
13271 }
13272 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13273 self.change_selections(Default::default(), window, cx, |s| {
13274 s.select_ranges(vec![0..0]);
13275 });
13276 }
13277
13278 pub fn select_to_beginning(
13279 &mut self,
13280 _: &SelectToBeginning,
13281 window: &mut Window,
13282 cx: &mut Context<Self>,
13283 ) {
13284 let mut selection = self.selections.last::<Point>(cx);
13285 selection.set_head(Point::zero(), SelectionGoal::None);
13286 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13287 self.change_selections(Default::default(), window, cx, |s| {
13288 s.select(vec![selection]);
13289 });
13290 }
13291
13292 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13293 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13294 cx.propagate();
13295 return;
13296 }
13297 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13298 let cursor = self.buffer.read(cx).read(cx).len();
13299 self.change_selections(Default::default(), window, cx, |s| {
13300 s.select_ranges(vec![cursor..cursor])
13301 });
13302 }
13303
13304 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13305 self.nav_history = nav_history;
13306 }
13307
13308 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13309 self.nav_history.as_ref()
13310 }
13311
13312 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13313 self.push_to_nav_history(
13314 self.selections.newest_anchor().head(),
13315 None,
13316 false,
13317 true,
13318 cx,
13319 );
13320 }
13321
13322 fn push_to_nav_history(
13323 &mut self,
13324 cursor_anchor: Anchor,
13325 new_position: Option<Point>,
13326 is_deactivate: bool,
13327 always: bool,
13328 cx: &mut Context<Self>,
13329 ) {
13330 if let Some(nav_history) = self.nav_history.as_mut() {
13331 let buffer = self.buffer.read(cx).read(cx);
13332 let cursor_position = cursor_anchor.to_point(&buffer);
13333 let scroll_state = self.scroll_manager.anchor();
13334 let scroll_top_row = scroll_state.top_row(&buffer);
13335 drop(buffer);
13336
13337 if let Some(new_position) = new_position {
13338 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13339 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13340 return;
13341 }
13342 }
13343
13344 nav_history.push(
13345 Some(NavigationData {
13346 cursor_anchor,
13347 cursor_position,
13348 scroll_anchor: scroll_state,
13349 scroll_top_row,
13350 }),
13351 cx,
13352 );
13353 cx.emit(EditorEvent::PushedToNavHistory {
13354 anchor: cursor_anchor,
13355 is_deactivate,
13356 })
13357 }
13358 }
13359
13360 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13361 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13362 let buffer = self.buffer.read(cx).snapshot(cx);
13363 let mut selection = self.selections.first::<usize>(cx);
13364 selection.set_head(buffer.len(), SelectionGoal::None);
13365 self.change_selections(Default::default(), window, cx, |s| {
13366 s.select(vec![selection]);
13367 });
13368 }
13369
13370 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13371 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13372 let end = self.buffer.read(cx).read(cx).len();
13373 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13374 s.select_ranges(vec![0..end]);
13375 });
13376 }
13377
13378 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13379 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13380 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13381 let mut selections = self.selections.all::<Point>(cx);
13382 let max_point = display_map.buffer_snapshot.max_point();
13383 for selection in &mut selections {
13384 let rows = selection.spanned_rows(true, &display_map);
13385 selection.start = Point::new(rows.start.0, 0);
13386 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13387 selection.reversed = false;
13388 }
13389 self.change_selections(Default::default(), window, cx, |s| {
13390 s.select(selections);
13391 });
13392 }
13393
13394 pub fn split_selection_into_lines(
13395 &mut self,
13396 _: &SplitSelectionIntoLines,
13397 window: &mut Window,
13398 cx: &mut Context<Self>,
13399 ) {
13400 let selections = self
13401 .selections
13402 .all::<Point>(cx)
13403 .into_iter()
13404 .map(|selection| selection.start..selection.end)
13405 .collect::<Vec<_>>();
13406 self.unfold_ranges(&selections, true, true, cx);
13407
13408 let mut new_selection_ranges = Vec::new();
13409 {
13410 let buffer = self.buffer.read(cx).read(cx);
13411 for selection in selections {
13412 for row in selection.start.row..selection.end.row {
13413 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13414 new_selection_ranges.push(cursor..cursor);
13415 }
13416
13417 let is_multiline_selection = selection.start.row != selection.end.row;
13418 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13419 // so this action feels more ergonomic when paired with other selection operations
13420 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13421 if !should_skip_last {
13422 new_selection_ranges.push(selection.end..selection.end);
13423 }
13424 }
13425 }
13426 self.change_selections(Default::default(), window, cx, |s| {
13427 s.select_ranges(new_selection_ranges);
13428 });
13429 }
13430
13431 pub fn add_selection_above(
13432 &mut self,
13433 _: &AddSelectionAbove,
13434 window: &mut Window,
13435 cx: &mut Context<Self>,
13436 ) {
13437 self.add_selection(true, window, cx);
13438 }
13439
13440 pub fn add_selection_below(
13441 &mut self,
13442 _: &AddSelectionBelow,
13443 window: &mut Window,
13444 cx: &mut Context<Self>,
13445 ) {
13446 self.add_selection(false, window, cx);
13447 }
13448
13449 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13450 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13451
13452 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13453 let all_selections = self.selections.all::<Point>(cx);
13454 let text_layout_details = self.text_layout_details(window);
13455
13456 let (mut columnar_selections, new_selections_to_columnarize) = {
13457 if let Some(state) = self.add_selections_state.as_ref() {
13458 let columnar_selection_ids: HashSet<_> = state
13459 .groups
13460 .iter()
13461 .flat_map(|group| group.stack.iter())
13462 .copied()
13463 .collect();
13464
13465 all_selections
13466 .into_iter()
13467 .partition(|s| columnar_selection_ids.contains(&s.id))
13468 } else {
13469 (Vec::new(), all_selections)
13470 }
13471 };
13472
13473 let mut state = self
13474 .add_selections_state
13475 .take()
13476 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13477
13478 for selection in new_selections_to_columnarize {
13479 let range = selection.display_range(&display_map).sorted();
13480 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13481 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13482 let positions = start_x.min(end_x)..start_x.max(end_x);
13483 let mut stack = Vec::new();
13484 for row in range.start.row().0..=range.end.row().0 {
13485 if let Some(selection) = self.selections.build_columnar_selection(
13486 &display_map,
13487 DisplayRow(row),
13488 &positions,
13489 selection.reversed,
13490 &text_layout_details,
13491 ) {
13492 stack.push(selection.id);
13493 columnar_selections.push(selection);
13494 }
13495 }
13496 if !stack.is_empty() {
13497 if above {
13498 stack.reverse();
13499 }
13500 state.groups.push(AddSelectionsGroup { above, stack });
13501 }
13502 }
13503
13504 let mut final_selections = Vec::new();
13505 let end_row = if above {
13506 DisplayRow(0)
13507 } else {
13508 display_map.max_point().row()
13509 };
13510
13511 let mut last_added_item_per_group = HashMap::default();
13512 for group in state.groups.iter_mut() {
13513 if let Some(last_id) = group.stack.last() {
13514 last_added_item_per_group.insert(*last_id, group);
13515 }
13516 }
13517
13518 for selection in columnar_selections {
13519 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13520 if above == group.above {
13521 let range = selection.display_range(&display_map).sorted();
13522 debug_assert_eq!(range.start.row(), range.end.row());
13523 let mut row = range.start.row();
13524 let positions =
13525 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13526 px(start)..px(end)
13527 } else {
13528 let start_x =
13529 display_map.x_for_display_point(range.start, &text_layout_details);
13530 let end_x =
13531 display_map.x_for_display_point(range.end, &text_layout_details);
13532 start_x.min(end_x)..start_x.max(end_x)
13533 };
13534
13535 let mut maybe_new_selection = None;
13536 while row != end_row {
13537 if above {
13538 row.0 -= 1;
13539 } else {
13540 row.0 += 1;
13541 }
13542 if let Some(new_selection) = self.selections.build_columnar_selection(
13543 &display_map,
13544 row,
13545 &positions,
13546 selection.reversed,
13547 &text_layout_details,
13548 ) {
13549 maybe_new_selection = Some(new_selection);
13550 break;
13551 }
13552 }
13553
13554 if let Some(new_selection) = maybe_new_selection {
13555 group.stack.push(new_selection.id);
13556 if above {
13557 final_selections.push(new_selection);
13558 final_selections.push(selection);
13559 } else {
13560 final_selections.push(selection);
13561 final_selections.push(new_selection);
13562 }
13563 } else {
13564 final_selections.push(selection);
13565 }
13566 } else {
13567 group.stack.pop();
13568 }
13569 } else {
13570 final_selections.push(selection);
13571 }
13572 }
13573
13574 self.change_selections(Default::default(), window, cx, |s| {
13575 s.select(final_selections);
13576 });
13577
13578 let final_selection_ids: HashSet<_> = self
13579 .selections
13580 .all::<Point>(cx)
13581 .iter()
13582 .map(|s| s.id)
13583 .collect();
13584 state.groups.retain_mut(|group| {
13585 // selections might get merged above so we remove invalid items from stacks
13586 group.stack.retain(|id| final_selection_ids.contains(id));
13587
13588 // single selection in stack can be treated as initial state
13589 group.stack.len() > 1
13590 });
13591
13592 if !state.groups.is_empty() {
13593 self.add_selections_state = Some(state);
13594 }
13595 }
13596
13597 fn select_match_ranges(
13598 &mut self,
13599 range: Range<usize>,
13600 reversed: bool,
13601 replace_newest: bool,
13602 auto_scroll: Option<Autoscroll>,
13603 window: &mut Window,
13604 cx: &mut Context<Editor>,
13605 ) {
13606 self.unfold_ranges(
13607 std::slice::from_ref(&range),
13608 false,
13609 auto_scroll.is_some(),
13610 cx,
13611 );
13612 let effects = if let Some(scroll) = auto_scroll {
13613 SelectionEffects::scroll(scroll)
13614 } else {
13615 SelectionEffects::no_scroll()
13616 };
13617 self.change_selections(effects, window, cx, |s| {
13618 if replace_newest {
13619 s.delete(s.newest_anchor().id);
13620 }
13621 if reversed {
13622 s.insert_range(range.end..range.start);
13623 } else {
13624 s.insert_range(range);
13625 }
13626 });
13627 }
13628
13629 pub fn select_next_match_internal(
13630 &mut self,
13631 display_map: &DisplaySnapshot,
13632 replace_newest: bool,
13633 autoscroll: Option<Autoscroll>,
13634 window: &mut Window,
13635 cx: &mut Context<Self>,
13636 ) -> Result<()> {
13637 let buffer = &display_map.buffer_snapshot;
13638 let mut selections = self.selections.all::<usize>(cx);
13639 if let Some(mut select_next_state) = self.select_next_state.take() {
13640 let query = &select_next_state.query;
13641 if !select_next_state.done {
13642 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13643 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13644 let mut next_selected_range = None;
13645
13646 let bytes_after_last_selection =
13647 buffer.bytes_in_range(last_selection.end..buffer.len());
13648 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13649 let query_matches = query
13650 .stream_find_iter(bytes_after_last_selection)
13651 .map(|result| (last_selection.end, result))
13652 .chain(
13653 query
13654 .stream_find_iter(bytes_before_first_selection)
13655 .map(|result| (0, result)),
13656 );
13657
13658 for (start_offset, query_match) in query_matches {
13659 let query_match = query_match.unwrap(); // can only fail due to I/O
13660 let offset_range =
13661 start_offset + query_match.start()..start_offset + query_match.end();
13662
13663 if !select_next_state.wordwise
13664 || (!buffer.is_inside_word(offset_range.start, false)
13665 && !buffer.is_inside_word(offset_range.end, false))
13666 {
13667 // TODO: This is n^2, because we might check all the selections
13668 if !selections
13669 .iter()
13670 .any(|selection| selection.range().overlaps(&offset_range))
13671 {
13672 next_selected_range = Some(offset_range);
13673 break;
13674 }
13675 }
13676 }
13677
13678 if let Some(next_selected_range) = next_selected_range {
13679 self.select_match_ranges(
13680 next_selected_range,
13681 last_selection.reversed,
13682 replace_newest,
13683 autoscroll,
13684 window,
13685 cx,
13686 );
13687 } else {
13688 select_next_state.done = true;
13689 }
13690 }
13691
13692 self.select_next_state = Some(select_next_state);
13693 } else {
13694 let mut only_carets = true;
13695 let mut same_text_selected = true;
13696 let mut selected_text = None;
13697
13698 let mut selections_iter = selections.iter().peekable();
13699 while let Some(selection) = selections_iter.next() {
13700 if selection.start != selection.end {
13701 only_carets = false;
13702 }
13703
13704 if same_text_selected {
13705 if selected_text.is_none() {
13706 selected_text =
13707 Some(buffer.text_for_range(selection.range()).collect::<String>());
13708 }
13709
13710 if let Some(next_selection) = selections_iter.peek() {
13711 if next_selection.range().len() == selection.range().len() {
13712 let next_selected_text = buffer
13713 .text_for_range(next_selection.range())
13714 .collect::<String>();
13715 if Some(next_selected_text) != selected_text {
13716 same_text_selected = false;
13717 selected_text = None;
13718 }
13719 } else {
13720 same_text_selected = false;
13721 selected_text = None;
13722 }
13723 }
13724 }
13725 }
13726
13727 if only_carets {
13728 for selection in &mut selections {
13729 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13730 selection.start = word_range.start;
13731 selection.end = word_range.end;
13732 selection.goal = SelectionGoal::None;
13733 selection.reversed = false;
13734 self.select_match_ranges(
13735 selection.start..selection.end,
13736 selection.reversed,
13737 replace_newest,
13738 autoscroll,
13739 window,
13740 cx,
13741 );
13742 }
13743
13744 if selections.len() == 1 {
13745 let selection = selections
13746 .last()
13747 .expect("ensured that there's only one selection");
13748 let query = buffer
13749 .text_for_range(selection.start..selection.end)
13750 .collect::<String>();
13751 let is_empty = query.is_empty();
13752 let select_state = SelectNextState {
13753 query: AhoCorasick::new(&[query])?,
13754 wordwise: true,
13755 done: is_empty,
13756 };
13757 self.select_next_state = Some(select_state);
13758 } else {
13759 self.select_next_state = None;
13760 }
13761 } else if let Some(selected_text) = selected_text {
13762 self.select_next_state = Some(SelectNextState {
13763 query: AhoCorasick::new(&[selected_text])?,
13764 wordwise: false,
13765 done: false,
13766 });
13767 self.select_next_match_internal(
13768 display_map,
13769 replace_newest,
13770 autoscroll,
13771 window,
13772 cx,
13773 )?;
13774 }
13775 }
13776 Ok(())
13777 }
13778
13779 pub fn select_all_matches(
13780 &mut self,
13781 _action: &SelectAllMatches,
13782 window: &mut Window,
13783 cx: &mut Context<Self>,
13784 ) -> Result<()> {
13785 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13786
13787 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13788
13789 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13790 let Some(select_next_state) = self.select_next_state.as_mut() else {
13791 return Ok(());
13792 };
13793 if select_next_state.done {
13794 return Ok(());
13795 }
13796
13797 let mut new_selections = Vec::new();
13798
13799 let reversed = self.selections.oldest::<usize>(cx).reversed;
13800 let buffer = &display_map.buffer_snapshot;
13801 let query_matches = select_next_state
13802 .query
13803 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13804
13805 for query_match in query_matches.into_iter() {
13806 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13807 let offset_range = if reversed {
13808 query_match.end()..query_match.start()
13809 } else {
13810 query_match.start()..query_match.end()
13811 };
13812
13813 if !select_next_state.wordwise
13814 || (!buffer.is_inside_word(offset_range.start, false)
13815 && !buffer.is_inside_word(offset_range.end, false))
13816 {
13817 new_selections.push(offset_range.start..offset_range.end);
13818 }
13819 }
13820
13821 select_next_state.done = true;
13822
13823 if new_selections.is_empty() {
13824 log::error!("bug: new_selections is empty in select_all_matches");
13825 return Ok(());
13826 }
13827
13828 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13829 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
13830 selections.select_ranges(new_selections)
13831 });
13832
13833 Ok(())
13834 }
13835
13836 pub fn select_next(
13837 &mut self,
13838 action: &SelectNext,
13839 window: &mut Window,
13840 cx: &mut Context<Self>,
13841 ) -> Result<()> {
13842 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13843 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13844 self.select_next_match_internal(
13845 &display_map,
13846 action.replace_newest,
13847 Some(Autoscroll::newest()),
13848 window,
13849 cx,
13850 )?;
13851 Ok(())
13852 }
13853
13854 pub fn select_previous(
13855 &mut self,
13856 action: &SelectPrevious,
13857 window: &mut Window,
13858 cx: &mut Context<Self>,
13859 ) -> Result<()> {
13860 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13861 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13862 let buffer = &display_map.buffer_snapshot;
13863 let mut selections = self.selections.all::<usize>(cx);
13864 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13865 let query = &select_prev_state.query;
13866 if !select_prev_state.done {
13867 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13868 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13869 let mut next_selected_range = None;
13870 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13871 let bytes_before_last_selection =
13872 buffer.reversed_bytes_in_range(0..last_selection.start);
13873 let bytes_after_first_selection =
13874 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13875 let query_matches = query
13876 .stream_find_iter(bytes_before_last_selection)
13877 .map(|result| (last_selection.start, result))
13878 .chain(
13879 query
13880 .stream_find_iter(bytes_after_first_selection)
13881 .map(|result| (buffer.len(), result)),
13882 );
13883 for (end_offset, query_match) in query_matches {
13884 let query_match = query_match.unwrap(); // can only fail due to I/O
13885 let offset_range =
13886 end_offset - query_match.end()..end_offset - query_match.start();
13887
13888 if !select_prev_state.wordwise
13889 || (!buffer.is_inside_word(offset_range.start, false)
13890 && !buffer.is_inside_word(offset_range.end, false))
13891 {
13892 next_selected_range = Some(offset_range);
13893 break;
13894 }
13895 }
13896
13897 if let Some(next_selected_range) = next_selected_range {
13898 self.select_match_ranges(
13899 next_selected_range,
13900 last_selection.reversed,
13901 action.replace_newest,
13902 Some(Autoscroll::newest()),
13903 window,
13904 cx,
13905 );
13906 } else {
13907 select_prev_state.done = true;
13908 }
13909 }
13910
13911 self.select_prev_state = Some(select_prev_state);
13912 } else {
13913 let mut only_carets = true;
13914 let mut same_text_selected = true;
13915 let mut selected_text = None;
13916
13917 let mut selections_iter = selections.iter().peekable();
13918 while let Some(selection) = selections_iter.next() {
13919 if selection.start != selection.end {
13920 only_carets = false;
13921 }
13922
13923 if same_text_selected {
13924 if selected_text.is_none() {
13925 selected_text =
13926 Some(buffer.text_for_range(selection.range()).collect::<String>());
13927 }
13928
13929 if let Some(next_selection) = selections_iter.peek() {
13930 if next_selection.range().len() == selection.range().len() {
13931 let next_selected_text = buffer
13932 .text_for_range(next_selection.range())
13933 .collect::<String>();
13934 if Some(next_selected_text) != selected_text {
13935 same_text_selected = false;
13936 selected_text = None;
13937 }
13938 } else {
13939 same_text_selected = false;
13940 selected_text = None;
13941 }
13942 }
13943 }
13944 }
13945
13946 if only_carets {
13947 for selection in &mut selections {
13948 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13949 selection.start = word_range.start;
13950 selection.end = word_range.end;
13951 selection.goal = SelectionGoal::None;
13952 selection.reversed = false;
13953 self.select_match_ranges(
13954 selection.start..selection.end,
13955 selection.reversed,
13956 action.replace_newest,
13957 Some(Autoscroll::newest()),
13958 window,
13959 cx,
13960 );
13961 }
13962 if selections.len() == 1 {
13963 let selection = selections
13964 .last()
13965 .expect("ensured that there's only one selection");
13966 let query = buffer
13967 .text_for_range(selection.start..selection.end)
13968 .collect::<String>();
13969 let is_empty = query.is_empty();
13970 let select_state = SelectNextState {
13971 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13972 wordwise: true,
13973 done: is_empty,
13974 };
13975 self.select_prev_state = Some(select_state);
13976 } else {
13977 self.select_prev_state = None;
13978 }
13979 } else if let Some(selected_text) = selected_text {
13980 self.select_prev_state = Some(SelectNextState {
13981 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13982 wordwise: false,
13983 done: false,
13984 });
13985 self.select_previous(action, window, cx)?;
13986 }
13987 }
13988 Ok(())
13989 }
13990
13991 pub fn find_next_match(
13992 &mut self,
13993 _: &FindNextMatch,
13994 window: &mut Window,
13995 cx: &mut Context<Self>,
13996 ) -> Result<()> {
13997 let selections = self.selections.disjoint_anchors();
13998 match selections.first() {
13999 Some(first) if selections.len() >= 2 => {
14000 self.change_selections(Default::default(), window, cx, |s| {
14001 s.select_ranges([first.range()]);
14002 });
14003 }
14004 _ => self.select_next(
14005 &SelectNext {
14006 replace_newest: true,
14007 },
14008 window,
14009 cx,
14010 )?,
14011 }
14012 Ok(())
14013 }
14014
14015 pub fn find_previous_match(
14016 &mut self,
14017 _: &FindPreviousMatch,
14018 window: &mut Window,
14019 cx: &mut Context<Self>,
14020 ) -> Result<()> {
14021 let selections = self.selections.disjoint_anchors();
14022 match selections.last() {
14023 Some(last) if selections.len() >= 2 => {
14024 self.change_selections(Default::default(), window, cx, |s| {
14025 s.select_ranges([last.range()]);
14026 });
14027 }
14028 _ => self.select_previous(
14029 &SelectPrevious {
14030 replace_newest: true,
14031 },
14032 window,
14033 cx,
14034 )?,
14035 }
14036 Ok(())
14037 }
14038
14039 pub fn toggle_comments(
14040 &mut self,
14041 action: &ToggleComments,
14042 window: &mut Window,
14043 cx: &mut Context<Self>,
14044 ) {
14045 if self.read_only(cx) {
14046 return;
14047 }
14048 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14049 let text_layout_details = &self.text_layout_details(window);
14050 self.transact(window, cx, |this, window, cx| {
14051 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14052 let mut edits = Vec::new();
14053 let mut selection_edit_ranges = Vec::new();
14054 let mut last_toggled_row = None;
14055 let snapshot = this.buffer.read(cx).read(cx);
14056 let empty_str: Arc<str> = Arc::default();
14057 let mut suffixes_inserted = Vec::new();
14058 let ignore_indent = action.ignore_indent;
14059
14060 fn comment_prefix_range(
14061 snapshot: &MultiBufferSnapshot,
14062 row: MultiBufferRow,
14063 comment_prefix: &str,
14064 comment_prefix_whitespace: &str,
14065 ignore_indent: bool,
14066 ) -> Range<Point> {
14067 let indent_size = if ignore_indent {
14068 0
14069 } else {
14070 snapshot.indent_size_for_line(row).len
14071 };
14072
14073 let start = Point::new(row.0, indent_size);
14074
14075 let mut line_bytes = snapshot
14076 .bytes_in_range(start..snapshot.max_point())
14077 .flatten()
14078 .copied();
14079
14080 // If this line currently begins with the line comment prefix, then record
14081 // the range containing the prefix.
14082 if line_bytes
14083 .by_ref()
14084 .take(comment_prefix.len())
14085 .eq(comment_prefix.bytes())
14086 {
14087 // Include any whitespace that matches the comment prefix.
14088 let matching_whitespace_len = line_bytes
14089 .zip(comment_prefix_whitespace.bytes())
14090 .take_while(|(a, b)| a == b)
14091 .count() as u32;
14092 let end = Point::new(
14093 start.row,
14094 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14095 );
14096 start..end
14097 } else {
14098 start..start
14099 }
14100 }
14101
14102 fn comment_suffix_range(
14103 snapshot: &MultiBufferSnapshot,
14104 row: MultiBufferRow,
14105 comment_suffix: &str,
14106 comment_suffix_has_leading_space: bool,
14107 ) -> Range<Point> {
14108 let end = Point::new(row.0, snapshot.line_len(row));
14109 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14110
14111 let mut line_end_bytes = snapshot
14112 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14113 .flatten()
14114 .copied();
14115
14116 let leading_space_len = if suffix_start_column > 0
14117 && line_end_bytes.next() == Some(b' ')
14118 && comment_suffix_has_leading_space
14119 {
14120 1
14121 } else {
14122 0
14123 };
14124
14125 // If this line currently begins with the line comment prefix, then record
14126 // the range containing the prefix.
14127 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14128 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14129 start..end
14130 } else {
14131 end..end
14132 }
14133 }
14134
14135 // TODO: Handle selections that cross excerpts
14136 for selection in &mut selections {
14137 let start_column = snapshot
14138 .indent_size_for_line(MultiBufferRow(selection.start.row))
14139 .len;
14140 let language = if let Some(language) =
14141 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14142 {
14143 language
14144 } else {
14145 continue;
14146 };
14147
14148 selection_edit_ranges.clear();
14149
14150 // If multiple selections contain a given row, avoid processing that
14151 // row more than once.
14152 let mut start_row = MultiBufferRow(selection.start.row);
14153 if last_toggled_row == Some(start_row) {
14154 start_row = start_row.next_row();
14155 }
14156 let end_row =
14157 if selection.end.row > selection.start.row && selection.end.column == 0 {
14158 MultiBufferRow(selection.end.row - 1)
14159 } else {
14160 MultiBufferRow(selection.end.row)
14161 };
14162 last_toggled_row = Some(end_row);
14163
14164 if start_row > end_row {
14165 continue;
14166 }
14167
14168 // If the language has line comments, toggle those.
14169 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14170
14171 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14172 if ignore_indent {
14173 full_comment_prefixes = full_comment_prefixes
14174 .into_iter()
14175 .map(|s| Arc::from(s.trim_end()))
14176 .collect();
14177 }
14178
14179 if !full_comment_prefixes.is_empty() {
14180 let first_prefix = full_comment_prefixes
14181 .first()
14182 .expect("prefixes is non-empty");
14183 let prefix_trimmed_lengths = full_comment_prefixes
14184 .iter()
14185 .map(|p| p.trim_end_matches(' ').len())
14186 .collect::<SmallVec<[usize; 4]>>();
14187
14188 let mut all_selection_lines_are_comments = true;
14189
14190 for row in start_row.0..=end_row.0 {
14191 let row = MultiBufferRow(row);
14192 if start_row < end_row && snapshot.is_line_blank(row) {
14193 continue;
14194 }
14195
14196 let prefix_range = full_comment_prefixes
14197 .iter()
14198 .zip(prefix_trimmed_lengths.iter().copied())
14199 .map(|(prefix, trimmed_prefix_len)| {
14200 comment_prefix_range(
14201 snapshot.deref(),
14202 row,
14203 &prefix[..trimmed_prefix_len],
14204 &prefix[trimmed_prefix_len..],
14205 ignore_indent,
14206 )
14207 })
14208 .max_by_key(|range| range.end.column - range.start.column)
14209 .expect("prefixes is non-empty");
14210
14211 if prefix_range.is_empty() {
14212 all_selection_lines_are_comments = false;
14213 }
14214
14215 selection_edit_ranges.push(prefix_range);
14216 }
14217
14218 if all_selection_lines_are_comments {
14219 edits.extend(
14220 selection_edit_ranges
14221 .iter()
14222 .cloned()
14223 .map(|range| (range, empty_str.clone())),
14224 );
14225 } else {
14226 let min_column = selection_edit_ranges
14227 .iter()
14228 .map(|range| range.start.column)
14229 .min()
14230 .unwrap_or(0);
14231 edits.extend(selection_edit_ranges.iter().map(|range| {
14232 let position = Point::new(range.start.row, min_column);
14233 (position..position, first_prefix.clone())
14234 }));
14235 }
14236 } else if let Some((full_comment_prefix, comment_suffix)) =
14237 language.block_comment_delimiters()
14238 {
14239 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14240 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14241 let prefix_range = comment_prefix_range(
14242 snapshot.deref(),
14243 start_row,
14244 comment_prefix,
14245 comment_prefix_whitespace,
14246 ignore_indent,
14247 );
14248 let suffix_range = comment_suffix_range(
14249 snapshot.deref(),
14250 end_row,
14251 comment_suffix.trim_start_matches(' '),
14252 comment_suffix.starts_with(' '),
14253 );
14254
14255 if prefix_range.is_empty() || suffix_range.is_empty() {
14256 edits.push((
14257 prefix_range.start..prefix_range.start,
14258 full_comment_prefix.clone(),
14259 ));
14260 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14261 suffixes_inserted.push((end_row, comment_suffix.len()));
14262 } else {
14263 edits.push((prefix_range, empty_str.clone()));
14264 edits.push((suffix_range, empty_str.clone()));
14265 }
14266 } else {
14267 continue;
14268 }
14269 }
14270
14271 drop(snapshot);
14272 this.buffer.update(cx, |buffer, cx| {
14273 buffer.edit(edits, None, cx);
14274 });
14275
14276 // Adjust selections so that they end before any comment suffixes that
14277 // were inserted.
14278 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14279 let mut selections = this.selections.all::<Point>(cx);
14280 let snapshot = this.buffer.read(cx).read(cx);
14281 for selection in &mut selections {
14282 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14283 match row.cmp(&MultiBufferRow(selection.end.row)) {
14284 Ordering::Less => {
14285 suffixes_inserted.next();
14286 continue;
14287 }
14288 Ordering::Greater => break,
14289 Ordering::Equal => {
14290 if selection.end.column == snapshot.line_len(row) {
14291 if selection.is_empty() {
14292 selection.start.column -= suffix_len as u32;
14293 }
14294 selection.end.column -= suffix_len as u32;
14295 }
14296 break;
14297 }
14298 }
14299 }
14300 }
14301
14302 drop(snapshot);
14303 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14304
14305 let selections = this.selections.all::<Point>(cx);
14306 let selections_on_single_row = selections.windows(2).all(|selections| {
14307 selections[0].start.row == selections[1].start.row
14308 && selections[0].end.row == selections[1].end.row
14309 && selections[0].start.row == selections[0].end.row
14310 });
14311 let selections_selecting = selections
14312 .iter()
14313 .any(|selection| selection.start != selection.end);
14314 let advance_downwards = action.advance_downwards
14315 && selections_on_single_row
14316 && !selections_selecting
14317 && !matches!(this.mode, EditorMode::SingleLine { .. });
14318
14319 if advance_downwards {
14320 let snapshot = this.buffer.read(cx).snapshot(cx);
14321
14322 this.change_selections(Default::default(), window, cx, |s| {
14323 s.move_cursors_with(|display_snapshot, display_point, _| {
14324 let mut point = display_point.to_point(display_snapshot);
14325 point.row += 1;
14326 point = snapshot.clip_point(point, Bias::Left);
14327 let display_point = point.to_display_point(display_snapshot);
14328 let goal = SelectionGoal::HorizontalPosition(
14329 display_snapshot
14330 .x_for_display_point(display_point, text_layout_details)
14331 .into(),
14332 );
14333 (display_point, goal)
14334 })
14335 });
14336 }
14337 });
14338 }
14339
14340 pub fn select_enclosing_symbol(
14341 &mut self,
14342 _: &SelectEnclosingSymbol,
14343 window: &mut Window,
14344 cx: &mut Context<Self>,
14345 ) {
14346 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14347
14348 let buffer = self.buffer.read(cx).snapshot(cx);
14349 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14350
14351 fn update_selection(
14352 selection: &Selection<usize>,
14353 buffer_snap: &MultiBufferSnapshot,
14354 ) -> Option<Selection<usize>> {
14355 let cursor = selection.head();
14356 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14357 for symbol in symbols.iter().rev() {
14358 let start = symbol.range.start.to_offset(buffer_snap);
14359 let end = symbol.range.end.to_offset(buffer_snap);
14360 let new_range = start..end;
14361 if start < selection.start || end > selection.end {
14362 return Some(Selection {
14363 id: selection.id,
14364 start: new_range.start,
14365 end: new_range.end,
14366 goal: SelectionGoal::None,
14367 reversed: selection.reversed,
14368 });
14369 }
14370 }
14371 None
14372 }
14373
14374 let mut selected_larger_symbol = false;
14375 let new_selections = old_selections
14376 .iter()
14377 .map(|selection| match update_selection(selection, &buffer) {
14378 Some(new_selection) => {
14379 if new_selection.range() != selection.range() {
14380 selected_larger_symbol = true;
14381 }
14382 new_selection
14383 }
14384 None => selection.clone(),
14385 })
14386 .collect::<Vec<_>>();
14387
14388 if selected_larger_symbol {
14389 self.change_selections(Default::default(), window, cx, |s| {
14390 s.select(new_selections);
14391 });
14392 }
14393 }
14394
14395 pub fn select_larger_syntax_node(
14396 &mut self,
14397 _: &SelectLargerSyntaxNode,
14398 window: &mut Window,
14399 cx: &mut Context<Self>,
14400 ) {
14401 let Some(visible_row_count) = self.visible_row_count() else {
14402 return;
14403 };
14404 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14405 if old_selections.is_empty() {
14406 return;
14407 }
14408
14409 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14410
14411 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14412 let buffer = self.buffer.read(cx).snapshot(cx);
14413
14414 let mut selected_larger_node = false;
14415 let mut new_selections = old_selections
14416 .iter()
14417 .map(|selection| {
14418 let old_range = selection.start..selection.end;
14419
14420 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14421 // manually select word at selection
14422 if ["string_content", "inline"].contains(&node.kind()) {
14423 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14424 // ignore if word is already selected
14425 if !word_range.is_empty() && old_range != word_range {
14426 let (last_word_range, _) =
14427 buffer.surrounding_word(old_range.end, false);
14428 // only select word if start and end point belongs to same word
14429 if word_range == last_word_range {
14430 selected_larger_node = true;
14431 return Selection {
14432 id: selection.id,
14433 start: word_range.start,
14434 end: word_range.end,
14435 goal: SelectionGoal::None,
14436 reversed: selection.reversed,
14437 };
14438 }
14439 }
14440 }
14441 }
14442
14443 let mut new_range = old_range.clone();
14444 while let Some((_node, containing_range)) =
14445 buffer.syntax_ancestor(new_range.clone())
14446 {
14447 new_range = match containing_range {
14448 MultiOrSingleBufferOffsetRange::Single(_) => break,
14449 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14450 };
14451 if !display_map.intersects_fold(new_range.start)
14452 && !display_map.intersects_fold(new_range.end)
14453 {
14454 break;
14455 }
14456 }
14457
14458 selected_larger_node |= new_range != old_range;
14459 Selection {
14460 id: selection.id,
14461 start: new_range.start,
14462 end: new_range.end,
14463 goal: SelectionGoal::None,
14464 reversed: selection.reversed,
14465 }
14466 })
14467 .collect::<Vec<_>>();
14468
14469 if !selected_larger_node {
14470 return; // don't put this call in the history
14471 }
14472
14473 // scroll based on transformation done to the last selection created by the user
14474 let (last_old, last_new) = old_selections
14475 .last()
14476 .zip(new_selections.last().cloned())
14477 .expect("old_selections isn't empty");
14478
14479 // revert selection
14480 let is_selection_reversed = {
14481 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14482 new_selections.last_mut().expect("checked above").reversed =
14483 should_newest_selection_be_reversed;
14484 should_newest_selection_be_reversed
14485 };
14486
14487 if selected_larger_node {
14488 self.select_syntax_node_history.disable_clearing = true;
14489 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14490 s.select(new_selections.clone());
14491 });
14492 self.select_syntax_node_history.disable_clearing = false;
14493 }
14494
14495 let start_row = last_new.start.to_display_point(&display_map).row().0;
14496 let end_row = last_new.end.to_display_point(&display_map).row().0;
14497 let selection_height = end_row - start_row + 1;
14498 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14499
14500 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14501 let scroll_behavior = if fits_on_the_screen {
14502 self.request_autoscroll(Autoscroll::fit(), cx);
14503 SelectSyntaxNodeScrollBehavior::FitSelection
14504 } else if is_selection_reversed {
14505 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14506 SelectSyntaxNodeScrollBehavior::CursorTop
14507 } else {
14508 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14509 SelectSyntaxNodeScrollBehavior::CursorBottom
14510 };
14511
14512 self.select_syntax_node_history.push((
14513 old_selections,
14514 scroll_behavior,
14515 is_selection_reversed,
14516 ));
14517 }
14518
14519 pub fn select_smaller_syntax_node(
14520 &mut self,
14521 _: &SelectSmallerSyntaxNode,
14522 window: &mut Window,
14523 cx: &mut Context<Self>,
14524 ) {
14525 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14526
14527 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14528 self.select_syntax_node_history.pop()
14529 {
14530 if let Some(selection) = selections.last_mut() {
14531 selection.reversed = is_selection_reversed;
14532 }
14533
14534 self.select_syntax_node_history.disable_clearing = true;
14535 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14536 s.select(selections.to_vec());
14537 });
14538 self.select_syntax_node_history.disable_clearing = false;
14539
14540 match scroll_behavior {
14541 SelectSyntaxNodeScrollBehavior::CursorTop => {
14542 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14543 }
14544 SelectSyntaxNodeScrollBehavior::FitSelection => {
14545 self.request_autoscroll(Autoscroll::fit(), cx);
14546 }
14547 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14548 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14549 }
14550 }
14551 }
14552 }
14553
14554 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14555 if !EditorSettings::get_global(cx).gutter.runnables {
14556 self.clear_tasks();
14557 return Task::ready(());
14558 }
14559 let project = self.project.as_ref().map(Entity::downgrade);
14560 let task_sources = self.lsp_task_sources(cx);
14561 let multi_buffer = self.buffer.downgrade();
14562 cx.spawn_in(window, async move |editor, cx| {
14563 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14564 let Some(project) = project.and_then(|p| p.upgrade()) else {
14565 return;
14566 };
14567 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14568 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14569 }) else {
14570 return;
14571 };
14572
14573 let hide_runnables = project
14574 .update(cx, |project, cx| {
14575 // Do not display any test indicators in non-dev server remote projects.
14576 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14577 })
14578 .unwrap_or(true);
14579 if hide_runnables {
14580 return;
14581 }
14582 let new_rows =
14583 cx.background_spawn({
14584 let snapshot = display_snapshot.clone();
14585 async move {
14586 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14587 }
14588 })
14589 .await;
14590 let Ok(lsp_tasks) =
14591 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14592 else {
14593 return;
14594 };
14595 let lsp_tasks = lsp_tasks.await;
14596
14597 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14598 lsp_tasks
14599 .into_iter()
14600 .flat_map(|(kind, tasks)| {
14601 tasks.into_iter().filter_map(move |(location, task)| {
14602 Some((kind.clone(), location?, task))
14603 })
14604 })
14605 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14606 let buffer = location.target.buffer;
14607 let buffer_snapshot = buffer.read(cx).snapshot();
14608 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14609 |(excerpt_id, snapshot, _)| {
14610 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14611 display_snapshot
14612 .buffer_snapshot
14613 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14614 } else {
14615 None
14616 }
14617 },
14618 );
14619 if let Some(offset) = offset {
14620 let task_buffer_range =
14621 location.target.range.to_point(&buffer_snapshot);
14622 let context_buffer_range =
14623 task_buffer_range.to_offset(&buffer_snapshot);
14624 let context_range = BufferOffset(context_buffer_range.start)
14625 ..BufferOffset(context_buffer_range.end);
14626
14627 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14628 .or_insert_with(|| RunnableTasks {
14629 templates: Vec::new(),
14630 offset,
14631 column: task_buffer_range.start.column,
14632 extra_variables: HashMap::default(),
14633 context_range,
14634 })
14635 .templates
14636 .push((kind, task.original_task().clone()));
14637 }
14638
14639 acc
14640 })
14641 }) else {
14642 return;
14643 };
14644
14645 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14646 buffer.language_settings(cx).tasks.prefer_lsp
14647 }) else {
14648 return;
14649 };
14650
14651 let rows = Self::runnable_rows(
14652 project,
14653 display_snapshot,
14654 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14655 new_rows,
14656 cx.clone(),
14657 )
14658 .await;
14659 editor
14660 .update(cx, |editor, _| {
14661 editor.clear_tasks();
14662 for (key, mut value) in rows {
14663 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14664 value.templates.extend(lsp_tasks.templates);
14665 }
14666
14667 editor.insert_tasks(key, value);
14668 }
14669 for (key, value) in lsp_tasks_by_rows {
14670 editor.insert_tasks(key, value);
14671 }
14672 })
14673 .ok();
14674 })
14675 }
14676 fn fetch_runnable_ranges(
14677 snapshot: &DisplaySnapshot,
14678 range: Range<Anchor>,
14679 ) -> Vec<language::RunnableRange> {
14680 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14681 }
14682
14683 fn runnable_rows(
14684 project: Entity<Project>,
14685 snapshot: DisplaySnapshot,
14686 prefer_lsp: bool,
14687 runnable_ranges: Vec<RunnableRange>,
14688 cx: AsyncWindowContext,
14689 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14690 cx.spawn(async move |cx| {
14691 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14692 for mut runnable in runnable_ranges {
14693 let Some(tasks) = cx
14694 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14695 .ok()
14696 else {
14697 continue;
14698 };
14699 let mut tasks = tasks.await;
14700
14701 if prefer_lsp {
14702 tasks.retain(|(task_kind, _)| {
14703 !matches!(task_kind, TaskSourceKind::Language { .. })
14704 });
14705 }
14706 if tasks.is_empty() {
14707 continue;
14708 }
14709
14710 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14711 let Some(row) = snapshot
14712 .buffer_snapshot
14713 .buffer_line_for_row(MultiBufferRow(point.row))
14714 .map(|(_, range)| range.start.row)
14715 else {
14716 continue;
14717 };
14718
14719 let context_range =
14720 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14721 runnable_rows.push((
14722 (runnable.buffer_id, row),
14723 RunnableTasks {
14724 templates: tasks,
14725 offset: snapshot
14726 .buffer_snapshot
14727 .anchor_before(runnable.run_range.start),
14728 context_range,
14729 column: point.column,
14730 extra_variables: runnable.extra_captures,
14731 },
14732 ));
14733 }
14734 runnable_rows
14735 })
14736 }
14737
14738 fn templates_with_tags(
14739 project: &Entity<Project>,
14740 runnable: &mut Runnable,
14741 cx: &mut App,
14742 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14743 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14744 let (worktree_id, file) = project
14745 .buffer_for_id(runnable.buffer, cx)
14746 .and_then(|buffer| buffer.read(cx).file())
14747 .map(|file| (file.worktree_id(cx), file.clone()))
14748 .unzip();
14749
14750 (
14751 project.task_store().read(cx).task_inventory().cloned(),
14752 worktree_id,
14753 file,
14754 )
14755 });
14756
14757 let tags = mem::take(&mut runnable.tags);
14758 let language = runnable.language.clone();
14759 cx.spawn(async move |cx| {
14760 let mut templates_with_tags = Vec::new();
14761 if let Some(inventory) = inventory {
14762 for RunnableTag(tag) in tags {
14763 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14764 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14765 }) else {
14766 return templates_with_tags;
14767 };
14768 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14769 move |(_, template)| {
14770 template.tags.iter().any(|source_tag| source_tag == &tag)
14771 },
14772 ));
14773 }
14774 }
14775 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14776
14777 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14778 // Strongest source wins; if we have worktree tag binding, prefer that to
14779 // global and language bindings;
14780 // if we have a global binding, prefer that to language binding.
14781 let first_mismatch = templates_with_tags
14782 .iter()
14783 .position(|(tag_source, _)| tag_source != leading_tag_source);
14784 if let Some(index) = first_mismatch {
14785 templates_with_tags.truncate(index);
14786 }
14787 }
14788
14789 templates_with_tags
14790 })
14791 }
14792
14793 pub fn move_to_enclosing_bracket(
14794 &mut self,
14795 _: &MoveToEnclosingBracket,
14796 window: &mut Window,
14797 cx: &mut Context<Self>,
14798 ) {
14799 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14800 self.change_selections(Default::default(), window, cx, |s| {
14801 s.move_offsets_with(|snapshot, selection| {
14802 let Some(enclosing_bracket_ranges) =
14803 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14804 else {
14805 return;
14806 };
14807
14808 let mut best_length = usize::MAX;
14809 let mut best_inside = false;
14810 let mut best_in_bracket_range = false;
14811 let mut best_destination = None;
14812 for (open, close) in enclosing_bracket_ranges {
14813 let close = close.to_inclusive();
14814 let length = close.end() - open.start;
14815 let inside = selection.start >= open.end && selection.end <= *close.start();
14816 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14817 || close.contains(&selection.head());
14818
14819 // If best is next to a bracket and current isn't, skip
14820 if !in_bracket_range && best_in_bracket_range {
14821 continue;
14822 }
14823
14824 // Prefer smaller lengths unless best is inside and current isn't
14825 if length > best_length && (best_inside || !inside) {
14826 continue;
14827 }
14828
14829 best_length = length;
14830 best_inside = inside;
14831 best_in_bracket_range = in_bracket_range;
14832 best_destination = Some(
14833 if close.contains(&selection.start) && close.contains(&selection.end) {
14834 if inside { open.end } else { open.start }
14835 } else if inside {
14836 *close.start()
14837 } else {
14838 *close.end()
14839 },
14840 );
14841 }
14842
14843 if let Some(destination) = best_destination {
14844 selection.collapse_to(destination, SelectionGoal::None);
14845 }
14846 })
14847 });
14848 }
14849
14850 pub fn undo_selection(
14851 &mut self,
14852 _: &UndoSelection,
14853 window: &mut Window,
14854 cx: &mut Context<Self>,
14855 ) {
14856 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14857 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14858 self.selection_history.mode = SelectionHistoryMode::Undoing;
14859 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14860 this.end_selection(window, cx);
14861 this.change_selections(
14862 SelectionEffects::scroll(Autoscroll::newest()),
14863 window,
14864 cx,
14865 |s| s.select_anchors(entry.selections.to_vec()),
14866 );
14867 });
14868 self.selection_history.mode = SelectionHistoryMode::Normal;
14869
14870 self.select_next_state = entry.select_next_state;
14871 self.select_prev_state = entry.select_prev_state;
14872 self.add_selections_state = entry.add_selections_state;
14873 }
14874 }
14875
14876 pub fn redo_selection(
14877 &mut self,
14878 _: &RedoSelection,
14879 window: &mut Window,
14880 cx: &mut Context<Self>,
14881 ) {
14882 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14883 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14884 self.selection_history.mode = SelectionHistoryMode::Redoing;
14885 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14886 this.end_selection(window, cx);
14887 this.change_selections(
14888 SelectionEffects::scroll(Autoscroll::newest()),
14889 window,
14890 cx,
14891 |s| s.select_anchors(entry.selections.to_vec()),
14892 );
14893 });
14894 self.selection_history.mode = SelectionHistoryMode::Normal;
14895
14896 self.select_next_state = entry.select_next_state;
14897 self.select_prev_state = entry.select_prev_state;
14898 self.add_selections_state = entry.add_selections_state;
14899 }
14900 }
14901
14902 pub fn expand_excerpts(
14903 &mut self,
14904 action: &ExpandExcerpts,
14905 _: &mut Window,
14906 cx: &mut Context<Self>,
14907 ) {
14908 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14909 }
14910
14911 pub fn expand_excerpts_down(
14912 &mut self,
14913 action: &ExpandExcerptsDown,
14914 _: &mut Window,
14915 cx: &mut Context<Self>,
14916 ) {
14917 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14918 }
14919
14920 pub fn expand_excerpts_up(
14921 &mut self,
14922 action: &ExpandExcerptsUp,
14923 _: &mut Window,
14924 cx: &mut Context<Self>,
14925 ) {
14926 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14927 }
14928
14929 pub fn expand_excerpts_for_direction(
14930 &mut self,
14931 lines: u32,
14932 direction: ExpandExcerptDirection,
14933
14934 cx: &mut Context<Self>,
14935 ) {
14936 let selections = self.selections.disjoint_anchors();
14937
14938 let lines = if lines == 0 {
14939 EditorSettings::get_global(cx).expand_excerpt_lines
14940 } else {
14941 lines
14942 };
14943
14944 self.buffer.update(cx, |buffer, cx| {
14945 let snapshot = buffer.snapshot(cx);
14946 let mut excerpt_ids = selections
14947 .iter()
14948 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14949 .collect::<Vec<_>>();
14950 excerpt_ids.sort();
14951 excerpt_ids.dedup();
14952 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14953 })
14954 }
14955
14956 pub fn expand_excerpt(
14957 &mut self,
14958 excerpt: ExcerptId,
14959 direction: ExpandExcerptDirection,
14960 window: &mut Window,
14961 cx: &mut Context<Self>,
14962 ) {
14963 let current_scroll_position = self.scroll_position(cx);
14964 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14965 let mut should_scroll_up = false;
14966
14967 if direction == ExpandExcerptDirection::Down {
14968 let multi_buffer = self.buffer.read(cx);
14969 let snapshot = multi_buffer.snapshot(cx);
14970 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14971 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14972 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14973 let buffer_snapshot = buffer.read(cx).snapshot();
14974 let excerpt_end_row =
14975 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14976 let last_row = buffer_snapshot.max_point().row;
14977 let lines_below = last_row.saturating_sub(excerpt_end_row);
14978 should_scroll_up = lines_below >= lines_to_expand;
14979 }
14980 }
14981 }
14982 }
14983
14984 self.buffer.update(cx, |buffer, cx| {
14985 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14986 });
14987
14988 if should_scroll_up {
14989 let new_scroll_position =
14990 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14991 self.set_scroll_position(new_scroll_position, window, cx);
14992 }
14993 }
14994
14995 pub fn go_to_singleton_buffer_point(
14996 &mut self,
14997 point: Point,
14998 window: &mut Window,
14999 cx: &mut Context<Self>,
15000 ) {
15001 self.go_to_singleton_buffer_range(point..point, window, cx);
15002 }
15003
15004 pub fn go_to_singleton_buffer_range(
15005 &mut self,
15006 range: Range<Point>,
15007 window: &mut Window,
15008 cx: &mut Context<Self>,
15009 ) {
15010 let multibuffer = self.buffer().read(cx);
15011 let Some(buffer) = multibuffer.as_singleton() else {
15012 return;
15013 };
15014 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15015 return;
15016 };
15017 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15018 return;
15019 };
15020 self.change_selections(
15021 SelectionEffects::default().nav_history(true),
15022 window,
15023 cx,
15024 |s| s.select_anchor_ranges([start..end]),
15025 );
15026 }
15027
15028 pub fn go_to_diagnostic(
15029 &mut self,
15030 _: &GoToDiagnostic,
15031 window: &mut Window,
15032 cx: &mut Context<Self>,
15033 ) {
15034 if !self.diagnostics_enabled() {
15035 return;
15036 }
15037 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15038 self.go_to_diagnostic_impl(Direction::Next, window, cx)
15039 }
15040
15041 pub fn go_to_prev_diagnostic(
15042 &mut self,
15043 _: &GoToPreviousDiagnostic,
15044 window: &mut Window,
15045 cx: &mut Context<Self>,
15046 ) {
15047 if !self.diagnostics_enabled() {
15048 return;
15049 }
15050 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15051 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
15052 }
15053
15054 pub fn go_to_diagnostic_impl(
15055 &mut self,
15056 direction: Direction,
15057 window: &mut Window,
15058 cx: &mut Context<Self>,
15059 ) {
15060 let buffer = self.buffer.read(cx).snapshot(cx);
15061 let selection = self.selections.newest::<usize>(cx);
15062
15063 let mut active_group_id = None;
15064 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
15065 if active_group.active_range.start.to_offset(&buffer) == selection.start {
15066 active_group_id = Some(active_group.group_id);
15067 }
15068 }
15069
15070 fn filtered(
15071 snapshot: EditorSnapshot,
15072 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15073 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15074 diagnostics
15075 .filter(|entry| entry.range.start != entry.range.end)
15076 .filter(|entry| !entry.diagnostic.is_unnecessary)
15077 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15078 }
15079
15080 let snapshot = self.snapshot(window, cx);
15081 let before = filtered(
15082 snapshot.clone(),
15083 buffer
15084 .diagnostics_in_range(0..selection.start)
15085 .filter(|entry| entry.range.start <= selection.start),
15086 );
15087 let after = filtered(
15088 snapshot,
15089 buffer
15090 .diagnostics_in_range(selection.start..buffer.len())
15091 .filter(|entry| entry.range.start >= selection.start),
15092 );
15093
15094 let mut found: Option<DiagnosticEntry<usize>> = None;
15095 if direction == Direction::Prev {
15096 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15097 {
15098 for diagnostic in prev_diagnostics.into_iter().rev() {
15099 if diagnostic.range.start != selection.start
15100 || active_group_id
15101 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15102 {
15103 found = Some(diagnostic);
15104 break 'outer;
15105 }
15106 }
15107 }
15108 } else {
15109 for diagnostic in after.chain(before) {
15110 if diagnostic.range.start != selection.start
15111 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15112 {
15113 found = Some(diagnostic);
15114 break;
15115 }
15116 }
15117 }
15118 let Some(next_diagnostic) = found else {
15119 return;
15120 };
15121
15122 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
15123 return;
15124 };
15125 self.change_selections(Default::default(), window, cx, |s| {
15126 s.select_ranges(vec![
15127 next_diagnostic.range.start..next_diagnostic.range.start,
15128 ])
15129 });
15130 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15131 self.refresh_inline_completion(false, true, window, cx);
15132 }
15133
15134 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15135 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15136 let snapshot = self.snapshot(window, cx);
15137 let selection = self.selections.newest::<Point>(cx);
15138 self.go_to_hunk_before_or_after_position(
15139 &snapshot,
15140 selection.head(),
15141 Direction::Next,
15142 window,
15143 cx,
15144 );
15145 }
15146
15147 pub fn go_to_hunk_before_or_after_position(
15148 &mut self,
15149 snapshot: &EditorSnapshot,
15150 position: Point,
15151 direction: Direction,
15152 window: &mut Window,
15153 cx: &mut Context<Editor>,
15154 ) {
15155 let row = if direction == Direction::Next {
15156 self.hunk_after_position(snapshot, position)
15157 .map(|hunk| hunk.row_range.start)
15158 } else {
15159 self.hunk_before_position(snapshot, position)
15160 };
15161
15162 if let Some(row) = row {
15163 let destination = Point::new(row.0, 0);
15164 let autoscroll = Autoscroll::center();
15165
15166 self.unfold_ranges(&[destination..destination], false, false, cx);
15167 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15168 s.select_ranges([destination..destination]);
15169 });
15170 }
15171 }
15172
15173 fn hunk_after_position(
15174 &mut self,
15175 snapshot: &EditorSnapshot,
15176 position: Point,
15177 ) -> Option<MultiBufferDiffHunk> {
15178 snapshot
15179 .buffer_snapshot
15180 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15181 .find(|hunk| hunk.row_range.start.0 > position.row)
15182 .or_else(|| {
15183 snapshot
15184 .buffer_snapshot
15185 .diff_hunks_in_range(Point::zero()..position)
15186 .find(|hunk| hunk.row_range.end.0 < position.row)
15187 })
15188 }
15189
15190 fn go_to_prev_hunk(
15191 &mut self,
15192 _: &GoToPreviousHunk,
15193 window: &mut Window,
15194 cx: &mut Context<Self>,
15195 ) {
15196 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15197 let snapshot = self.snapshot(window, cx);
15198 let selection = self.selections.newest::<Point>(cx);
15199 self.go_to_hunk_before_or_after_position(
15200 &snapshot,
15201 selection.head(),
15202 Direction::Prev,
15203 window,
15204 cx,
15205 );
15206 }
15207
15208 fn hunk_before_position(
15209 &mut self,
15210 snapshot: &EditorSnapshot,
15211 position: Point,
15212 ) -> Option<MultiBufferRow> {
15213 snapshot
15214 .buffer_snapshot
15215 .diff_hunk_before(position)
15216 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15217 }
15218
15219 fn go_to_next_change(
15220 &mut self,
15221 _: &GoToNextChange,
15222 window: &mut Window,
15223 cx: &mut Context<Self>,
15224 ) {
15225 if let Some(selections) = self
15226 .change_list
15227 .next_change(1, Direction::Next)
15228 .map(|s| s.to_vec())
15229 {
15230 self.change_selections(Default::default(), window, cx, |s| {
15231 let map = s.display_map();
15232 s.select_display_ranges(selections.iter().map(|a| {
15233 let point = a.to_display_point(&map);
15234 point..point
15235 }))
15236 })
15237 }
15238 }
15239
15240 fn go_to_previous_change(
15241 &mut self,
15242 _: &GoToPreviousChange,
15243 window: &mut Window,
15244 cx: &mut Context<Self>,
15245 ) {
15246 if let Some(selections) = self
15247 .change_list
15248 .next_change(1, Direction::Prev)
15249 .map(|s| s.to_vec())
15250 {
15251 self.change_selections(Default::default(), window, cx, |s| {
15252 let map = s.display_map();
15253 s.select_display_ranges(selections.iter().map(|a| {
15254 let point = a.to_display_point(&map);
15255 point..point
15256 }))
15257 })
15258 }
15259 }
15260
15261 fn go_to_line<T: 'static>(
15262 &mut self,
15263 position: Anchor,
15264 highlight_color: Option<Hsla>,
15265 window: &mut Window,
15266 cx: &mut Context<Self>,
15267 ) {
15268 let snapshot = self.snapshot(window, cx).display_snapshot;
15269 let position = position.to_point(&snapshot.buffer_snapshot);
15270 let start = snapshot
15271 .buffer_snapshot
15272 .clip_point(Point::new(position.row, 0), Bias::Left);
15273 let end = start + Point::new(1, 0);
15274 let start = snapshot.buffer_snapshot.anchor_before(start);
15275 let end = snapshot.buffer_snapshot.anchor_before(end);
15276
15277 self.highlight_rows::<T>(
15278 start..end,
15279 highlight_color
15280 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15281 Default::default(),
15282 cx,
15283 );
15284
15285 if self.buffer.read(cx).is_singleton() {
15286 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15287 }
15288 }
15289
15290 pub fn go_to_definition(
15291 &mut self,
15292 _: &GoToDefinition,
15293 window: &mut Window,
15294 cx: &mut Context<Self>,
15295 ) -> Task<Result<Navigated>> {
15296 let definition =
15297 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15298 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15299 cx.spawn_in(window, async move |editor, cx| {
15300 if definition.await? == Navigated::Yes {
15301 return Ok(Navigated::Yes);
15302 }
15303 match fallback_strategy {
15304 GoToDefinitionFallback::None => Ok(Navigated::No),
15305 GoToDefinitionFallback::FindAllReferences => {
15306 match editor.update_in(cx, |editor, window, cx| {
15307 editor.find_all_references(&FindAllReferences, window, cx)
15308 })? {
15309 Some(references) => references.await,
15310 None => Ok(Navigated::No),
15311 }
15312 }
15313 }
15314 })
15315 }
15316
15317 pub fn go_to_declaration(
15318 &mut self,
15319 _: &GoToDeclaration,
15320 window: &mut Window,
15321 cx: &mut Context<Self>,
15322 ) -> Task<Result<Navigated>> {
15323 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15324 }
15325
15326 pub fn go_to_declaration_split(
15327 &mut self,
15328 _: &GoToDeclaration,
15329 window: &mut Window,
15330 cx: &mut Context<Self>,
15331 ) -> Task<Result<Navigated>> {
15332 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15333 }
15334
15335 pub fn go_to_implementation(
15336 &mut self,
15337 _: &GoToImplementation,
15338 window: &mut Window,
15339 cx: &mut Context<Self>,
15340 ) -> Task<Result<Navigated>> {
15341 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15342 }
15343
15344 pub fn go_to_implementation_split(
15345 &mut self,
15346 _: &GoToImplementationSplit,
15347 window: &mut Window,
15348 cx: &mut Context<Self>,
15349 ) -> Task<Result<Navigated>> {
15350 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15351 }
15352
15353 pub fn go_to_type_definition(
15354 &mut self,
15355 _: &GoToTypeDefinition,
15356 window: &mut Window,
15357 cx: &mut Context<Self>,
15358 ) -> Task<Result<Navigated>> {
15359 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15360 }
15361
15362 pub fn go_to_definition_split(
15363 &mut self,
15364 _: &GoToDefinitionSplit,
15365 window: &mut Window,
15366 cx: &mut Context<Self>,
15367 ) -> Task<Result<Navigated>> {
15368 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15369 }
15370
15371 pub fn go_to_type_definition_split(
15372 &mut self,
15373 _: &GoToTypeDefinitionSplit,
15374 window: &mut Window,
15375 cx: &mut Context<Self>,
15376 ) -> Task<Result<Navigated>> {
15377 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15378 }
15379
15380 fn go_to_definition_of_kind(
15381 &mut self,
15382 kind: GotoDefinitionKind,
15383 split: bool,
15384 window: &mut Window,
15385 cx: &mut Context<Self>,
15386 ) -> Task<Result<Navigated>> {
15387 let Some(provider) = self.semantics_provider.clone() else {
15388 return Task::ready(Ok(Navigated::No));
15389 };
15390 let head = self.selections.newest::<usize>(cx).head();
15391 let buffer = self.buffer.read(cx);
15392 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
15393 text_anchor
15394 } else {
15395 return Task::ready(Ok(Navigated::No));
15396 };
15397
15398 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15399 return Task::ready(Ok(Navigated::No));
15400 };
15401
15402 cx.spawn_in(window, async move |editor, cx| {
15403 let definitions = definitions.await?;
15404 let navigated = editor
15405 .update_in(cx, |editor, window, cx| {
15406 editor.navigate_to_hover_links(
15407 Some(kind),
15408 definitions
15409 .into_iter()
15410 .filter(|location| {
15411 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15412 })
15413 .map(HoverLink::Text)
15414 .collect::<Vec<_>>(),
15415 split,
15416 window,
15417 cx,
15418 )
15419 })?
15420 .await?;
15421 anyhow::Ok(navigated)
15422 })
15423 }
15424
15425 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15426 let selection = self.selections.newest_anchor();
15427 let head = selection.head();
15428 let tail = selection.tail();
15429
15430 let Some((buffer, start_position)) =
15431 self.buffer.read(cx).text_anchor_for_position(head, cx)
15432 else {
15433 return;
15434 };
15435
15436 let end_position = if head != tail {
15437 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15438 return;
15439 };
15440 Some(pos)
15441 } else {
15442 None
15443 };
15444
15445 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15446 let url = if let Some(end_pos) = end_position {
15447 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15448 } else {
15449 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15450 };
15451
15452 if let Some(url) = url {
15453 editor.update(cx, |_, cx| {
15454 cx.open_url(&url);
15455 })
15456 } else {
15457 Ok(())
15458 }
15459 });
15460
15461 url_finder.detach();
15462 }
15463
15464 pub fn open_selected_filename(
15465 &mut self,
15466 _: &OpenSelectedFilename,
15467 window: &mut Window,
15468 cx: &mut Context<Self>,
15469 ) {
15470 let Some(workspace) = self.workspace() else {
15471 return;
15472 };
15473
15474 let position = self.selections.newest_anchor().head();
15475
15476 let Some((buffer, buffer_position)) =
15477 self.buffer.read(cx).text_anchor_for_position(position, cx)
15478 else {
15479 return;
15480 };
15481
15482 let project = self.project.clone();
15483
15484 cx.spawn_in(window, async move |_, cx| {
15485 let result = find_file(&buffer, project, buffer_position, cx).await;
15486
15487 if let Some((_, path)) = result {
15488 workspace
15489 .update_in(cx, |workspace, window, cx| {
15490 workspace.open_resolved_path(path, window, cx)
15491 })?
15492 .await?;
15493 }
15494 anyhow::Ok(())
15495 })
15496 .detach();
15497 }
15498
15499 pub(crate) fn navigate_to_hover_links(
15500 &mut self,
15501 kind: Option<GotoDefinitionKind>,
15502 mut definitions: Vec<HoverLink>,
15503 split: bool,
15504 window: &mut Window,
15505 cx: &mut Context<Editor>,
15506 ) -> Task<Result<Navigated>> {
15507 // If there is one definition, just open it directly
15508 if definitions.len() == 1 {
15509 let definition = definitions.pop().unwrap();
15510
15511 enum TargetTaskResult {
15512 Location(Option<Location>),
15513 AlreadyNavigated,
15514 }
15515
15516 let target_task = match definition {
15517 HoverLink::Text(link) => {
15518 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
15519 }
15520 HoverLink::InlayHint(lsp_location, server_id) => {
15521 let computation =
15522 self.compute_target_location(lsp_location, server_id, window, cx);
15523 cx.background_spawn(async move {
15524 let location = computation.await?;
15525 Ok(TargetTaskResult::Location(location))
15526 })
15527 }
15528 HoverLink::Url(url) => {
15529 cx.open_url(&url);
15530 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
15531 }
15532 HoverLink::File(path) => {
15533 if let Some(workspace) = self.workspace() {
15534 cx.spawn_in(window, async move |_, cx| {
15535 workspace
15536 .update_in(cx, |workspace, window, cx| {
15537 workspace.open_resolved_path(path, window, cx)
15538 })?
15539 .await
15540 .map(|_| TargetTaskResult::AlreadyNavigated)
15541 })
15542 } else {
15543 Task::ready(Ok(TargetTaskResult::Location(None)))
15544 }
15545 }
15546 };
15547 cx.spawn_in(window, async move |editor, cx| {
15548 let target = match target_task.await.context("target resolution task")? {
15549 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
15550 TargetTaskResult::Location(None) => return Ok(Navigated::No),
15551 TargetTaskResult::Location(Some(target)) => target,
15552 };
15553
15554 editor.update_in(cx, |editor, window, cx| {
15555 let Some(workspace) = editor.workspace() else {
15556 return Navigated::No;
15557 };
15558 let pane = workspace.read(cx).active_pane().clone();
15559
15560 let range = target.range.to_point(target.buffer.read(cx));
15561 let range = editor.range_for_match(&range);
15562 let range = collapse_multiline_range(range);
15563
15564 if !split
15565 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15566 {
15567 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15568 } else {
15569 window.defer(cx, move |window, cx| {
15570 let target_editor: Entity<Self> =
15571 workspace.update(cx, |workspace, cx| {
15572 let pane = if split {
15573 workspace.adjacent_pane(window, cx)
15574 } else {
15575 workspace.active_pane().clone()
15576 };
15577
15578 workspace.open_project_item(
15579 pane,
15580 target.buffer.clone(),
15581 true,
15582 true,
15583 window,
15584 cx,
15585 )
15586 });
15587 target_editor.update(cx, |target_editor, cx| {
15588 // When selecting a definition in a different buffer, disable the nav history
15589 // to avoid creating a history entry at the previous cursor location.
15590 pane.update(cx, |pane, _| pane.disable_history());
15591 target_editor.go_to_singleton_buffer_range(range, window, cx);
15592 pane.update(cx, |pane, _| pane.enable_history());
15593 });
15594 });
15595 }
15596 Navigated::Yes
15597 })
15598 })
15599 } else if !definitions.is_empty() {
15600 cx.spawn_in(window, async move |editor, cx| {
15601 let (title, location_tasks, workspace) = editor
15602 .update_in(cx, |editor, window, cx| {
15603 let tab_kind = match kind {
15604 Some(GotoDefinitionKind::Implementation) => "Implementations",
15605 _ => "Definitions",
15606 };
15607 let title = definitions
15608 .iter()
15609 .find_map(|definition| match definition {
15610 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15611 let buffer = origin.buffer.read(cx);
15612 format!(
15613 "{} for {}",
15614 tab_kind,
15615 buffer
15616 .text_for_range(origin.range.clone())
15617 .collect::<String>()
15618 )
15619 }),
15620 HoverLink::InlayHint(_, _) => None,
15621 HoverLink::Url(_) => None,
15622 HoverLink::File(_) => None,
15623 })
15624 .unwrap_or(tab_kind.to_string());
15625 let location_tasks = definitions
15626 .into_iter()
15627 .map(|definition| match definition {
15628 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15629 HoverLink::InlayHint(lsp_location, server_id) => editor
15630 .compute_target_location(lsp_location, server_id, window, cx),
15631 HoverLink::Url(_) => Task::ready(Ok(None)),
15632 HoverLink::File(_) => Task::ready(Ok(None)),
15633 })
15634 .collect::<Vec<_>>();
15635 (title, location_tasks, editor.workspace().clone())
15636 })
15637 .context("location tasks preparation")?;
15638
15639 let locations: Vec<Location> = future::join_all(location_tasks)
15640 .await
15641 .into_iter()
15642 .filter_map(|location| location.transpose())
15643 .collect::<Result<_>>()
15644 .context("location tasks")?;
15645
15646 if locations.is_empty() {
15647 return Ok(Navigated::No);
15648 }
15649
15650 let Some(workspace) = workspace else {
15651 return Ok(Navigated::No);
15652 };
15653
15654 let opened = workspace
15655 .update_in(cx, |workspace, window, cx| {
15656 Self::open_locations_in_multibuffer(
15657 workspace,
15658 locations,
15659 title,
15660 split,
15661 MultibufferSelectionMode::First,
15662 window,
15663 cx,
15664 )
15665 })
15666 .ok();
15667
15668 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15669 })
15670 } else {
15671 Task::ready(Ok(Navigated::No))
15672 }
15673 }
15674
15675 fn compute_target_location(
15676 &self,
15677 lsp_location: lsp::Location,
15678 server_id: LanguageServerId,
15679 window: &mut Window,
15680 cx: &mut Context<Self>,
15681 ) -> Task<anyhow::Result<Option<Location>>> {
15682 let Some(project) = self.project.clone() else {
15683 return Task::ready(Ok(None));
15684 };
15685
15686 cx.spawn_in(window, async move |editor, cx| {
15687 let location_task = editor.update(cx, |_, cx| {
15688 project.update(cx, |project, cx| {
15689 let language_server_name = project
15690 .language_server_statuses(cx)
15691 .find(|(id, _)| server_id == *id)
15692 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15693 language_server_name.map(|language_server_name| {
15694 project.open_local_buffer_via_lsp(
15695 lsp_location.uri.clone(),
15696 server_id,
15697 language_server_name,
15698 cx,
15699 )
15700 })
15701 })
15702 })?;
15703 let location = match location_task {
15704 Some(task) => Some({
15705 let target_buffer_handle = task.await.context("open local buffer")?;
15706 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15707 let target_start = target_buffer
15708 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15709 let target_end = target_buffer
15710 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15711 target_buffer.anchor_after(target_start)
15712 ..target_buffer.anchor_before(target_end)
15713 })?;
15714 Location {
15715 buffer: target_buffer_handle,
15716 range,
15717 }
15718 }),
15719 None => None,
15720 };
15721 Ok(location)
15722 })
15723 }
15724
15725 pub fn find_all_references(
15726 &mut self,
15727 _: &FindAllReferences,
15728 window: &mut Window,
15729 cx: &mut Context<Self>,
15730 ) -> Option<Task<Result<Navigated>>> {
15731 let selection = self.selections.newest::<usize>(cx);
15732 let multi_buffer = self.buffer.read(cx);
15733 let head = selection.head();
15734
15735 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15736 let head_anchor = multi_buffer_snapshot.anchor_at(
15737 head,
15738 if head < selection.tail() {
15739 Bias::Right
15740 } else {
15741 Bias::Left
15742 },
15743 );
15744
15745 match self
15746 .find_all_references_task_sources
15747 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15748 {
15749 Ok(_) => {
15750 log::info!(
15751 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15752 );
15753 return None;
15754 }
15755 Err(i) => {
15756 self.find_all_references_task_sources.insert(i, head_anchor);
15757 }
15758 }
15759
15760 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15761 let workspace = self.workspace()?;
15762 let project = workspace.read(cx).project().clone();
15763 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15764 Some(cx.spawn_in(window, async move |editor, cx| {
15765 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15766 if let Ok(i) = editor
15767 .find_all_references_task_sources
15768 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15769 {
15770 editor.find_all_references_task_sources.remove(i);
15771 }
15772 });
15773
15774 let locations = references.await?;
15775 if locations.is_empty() {
15776 return anyhow::Ok(Navigated::No);
15777 }
15778
15779 workspace.update_in(cx, |workspace, window, cx| {
15780 let title = locations
15781 .first()
15782 .as_ref()
15783 .map(|location| {
15784 let buffer = location.buffer.read(cx);
15785 format!(
15786 "References to `{}`",
15787 buffer
15788 .text_for_range(location.range.clone())
15789 .collect::<String>()
15790 )
15791 })
15792 .unwrap();
15793 Self::open_locations_in_multibuffer(
15794 workspace,
15795 locations,
15796 title,
15797 false,
15798 MultibufferSelectionMode::First,
15799 window,
15800 cx,
15801 );
15802 Navigated::Yes
15803 })
15804 }))
15805 }
15806
15807 /// Opens a multibuffer with the given project locations in it
15808 pub fn open_locations_in_multibuffer(
15809 workspace: &mut Workspace,
15810 mut locations: Vec<Location>,
15811 title: String,
15812 split: bool,
15813 multibuffer_selection_mode: MultibufferSelectionMode,
15814 window: &mut Window,
15815 cx: &mut Context<Workspace>,
15816 ) {
15817 if locations.is_empty() {
15818 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
15819 return;
15820 }
15821
15822 // If there are multiple definitions, open them in a multibuffer
15823 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15824 let mut locations = locations.into_iter().peekable();
15825 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15826 let capability = workspace.project().read(cx).capability();
15827
15828 let excerpt_buffer = cx.new(|cx| {
15829 let mut multibuffer = MultiBuffer::new(capability);
15830 while let Some(location) = locations.next() {
15831 let buffer = location.buffer.read(cx);
15832 let mut ranges_for_buffer = Vec::new();
15833 let range = location.range.to_point(buffer);
15834 ranges_for_buffer.push(range.clone());
15835
15836 while let Some(next_location) = locations.peek() {
15837 if next_location.buffer == location.buffer {
15838 ranges_for_buffer.push(next_location.range.to_point(buffer));
15839 locations.next();
15840 } else {
15841 break;
15842 }
15843 }
15844
15845 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15846 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15847 PathKey::for_buffer(&location.buffer, cx),
15848 location.buffer.clone(),
15849 ranges_for_buffer,
15850 DEFAULT_MULTIBUFFER_CONTEXT,
15851 cx,
15852 );
15853 ranges.extend(new_ranges)
15854 }
15855
15856 multibuffer.with_title(title)
15857 });
15858
15859 let editor = cx.new(|cx| {
15860 Editor::for_multibuffer(
15861 excerpt_buffer,
15862 Some(workspace.project().clone()),
15863 window,
15864 cx,
15865 )
15866 });
15867 editor.update(cx, |editor, cx| {
15868 match multibuffer_selection_mode {
15869 MultibufferSelectionMode::First => {
15870 if let Some(first_range) = ranges.first() {
15871 editor.change_selections(
15872 SelectionEffects::no_scroll(),
15873 window,
15874 cx,
15875 |selections| {
15876 selections.clear_disjoint();
15877 selections
15878 .select_anchor_ranges(std::iter::once(first_range.clone()));
15879 },
15880 );
15881 }
15882 editor.highlight_background::<Self>(
15883 &ranges,
15884 |theme| theme.colors().editor_highlighted_line_background,
15885 cx,
15886 );
15887 }
15888 MultibufferSelectionMode::All => {
15889 editor.change_selections(
15890 SelectionEffects::no_scroll(),
15891 window,
15892 cx,
15893 |selections| {
15894 selections.clear_disjoint();
15895 selections.select_anchor_ranges(ranges);
15896 },
15897 );
15898 }
15899 }
15900 editor.register_buffers_with_language_servers(cx);
15901 });
15902
15903 let item = Box::new(editor);
15904 let item_id = item.item_id();
15905
15906 if split {
15907 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15908 } else {
15909 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15910 let (preview_item_id, preview_item_idx) =
15911 workspace.active_pane().read_with(cx, |pane, _| {
15912 (pane.preview_item_id(), pane.preview_item_idx())
15913 });
15914
15915 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15916
15917 if let Some(preview_item_id) = preview_item_id {
15918 workspace.active_pane().update(cx, |pane, cx| {
15919 pane.remove_item(preview_item_id, false, false, window, cx);
15920 });
15921 }
15922 } else {
15923 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15924 }
15925 }
15926 workspace.active_pane().update(cx, |pane, cx| {
15927 pane.set_preview_item_id(Some(item_id), cx);
15928 });
15929 }
15930
15931 pub fn rename(
15932 &mut self,
15933 _: &Rename,
15934 window: &mut Window,
15935 cx: &mut Context<Self>,
15936 ) -> Option<Task<Result<()>>> {
15937 use language::ToOffset as _;
15938
15939 let provider = self.semantics_provider.clone()?;
15940 let selection = self.selections.newest_anchor().clone();
15941 let (cursor_buffer, cursor_buffer_position) = self
15942 .buffer
15943 .read(cx)
15944 .text_anchor_for_position(selection.head(), cx)?;
15945 let (tail_buffer, cursor_buffer_position_end) = self
15946 .buffer
15947 .read(cx)
15948 .text_anchor_for_position(selection.tail(), cx)?;
15949 if tail_buffer != cursor_buffer {
15950 return None;
15951 }
15952
15953 let snapshot = cursor_buffer.read(cx).snapshot();
15954 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15955 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15956 let prepare_rename = provider
15957 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15958 .unwrap_or_else(|| Task::ready(Ok(None)));
15959 drop(snapshot);
15960
15961 Some(cx.spawn_in(window, async move |this, cx| {
15962 let rename_range = if let Some(range) = prepare_rename.await? {
15963 Some(range)
15964 } else {
15965 this.update(cx, |this, cx| {
15966 let buffer = this.buffer.read(cx).snapshot(cx);
15967 let mut buffer_highlights = this
15968 .document_highlights_for_position(selection.head(), &buffer)
15969 .filter(|highlight| {
15970 highlight.start.excerpt_id == selection.head().excerpt_id
15971 && highlight.end.excerpt_id == selection.head().excerpt_id
15972 });
15973 buffer_highlights
15974 .next()
15975 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15976 })?
15977 };
15978 if let Some(rename_range) = rename_range {
15979 this.update_in(cx, |this, window, cx| {
15980 let snapshot = cursor_buffer.read(cx).snapshot();
15981 let rename_buffer_range = rename_range.to_offset(&snapshot);
15982 let cursor_offset_in_rename_range =
15983 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15984 let cursor_offset_in_rename_range_end =
15985 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15986
15987 this.take_rename(false, window, cx);
15988 let buffer = this.buffer.read(cx).read(cx);
15989 let cursor_offset = selection.head().to_offset(&buffer);
15990 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15991 let rename_end = rename_start + rename_buffer_range.len();
15992 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15993 let mut old_highlight_id = None;
15994 let old_name: Arc<str> = buffer
15995 .chunks(rename_start..rename_end, true)
15996 .map(|chunk| {
15997 if old_highlight_id.is_none() {
15998 old_highlight_id = chunk.syntax_highlight_id;
15999 }
16000 chunk.text
16001 })
16002 .collect::<String>()
16003 .into();
16004
16005 drop(buffer);
16006
16007 // Position the selection in the rename editor so that it matches the current selection.
16008 this.show_local_selections = false;
16009 let rename_editor = cx.new(|cx| {
16010 let mut editor = Editor::single_line(window, cx);
16011 editor.buffer.update(cx, |buffer, cx| {
16012 buffer.edit([(0..0, old_name.clone())], None, cx)
16013 });
16014 let rename_selection_range = match cursor_offset_in_rename_range
16015 .cmp(&cursor_offset_in_rename_range_end)
16016 {
16017 Ordering::Equal => {
16018 editor.select_all(&SelectAll, window, cx);
16019 return editor;
16020 }
16021 Ordering::Less => {
16022 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16023 }
16024 Ordering::Greater => {
16025 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16026 }
16027 };
16028 if rename_selection_range.end > old_name.len() {
16029 editor.select_all(&SelectAll, window, cx);
16030 } else {
16031 editor.change_selections(Default::default(), window, cx, |s| {
16032 s.select_ranges([rename_selection_range]);
16033 });
16034 }
16035 editor
16036 });
16037 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16038 if e == &EditorEvent::Focused {
16039 cx.emit(EditorEvent::FocusedIn)
16040 }
16041 })
16042 .detach();
16043
16044 let write_highlights =
16045 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16046 let read_highlights =
16047 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16048 let ranges = write_highlights
16049 .iter()
16050 .flat_map(|(_, ranges)| ranges.iter())
16051 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16052 .cloned()
16053 .collect();
16054
16055 this.highlight_text::<Rename>(
16056 ranges,
16057 HighlightStyle {
16058 fade_out: Some(0.6),
16059 ..Default::default()
16060 },
16061 cx,
16062 );
16063 let rename_focus_handle = rename_editor.focus_handle(cx);
16064 window.focus(&rename_focus_handle);
16065 let block_id = this.insert_blocks(
16066 [BlockProperties {
16067 style: BlockStyle::Flex,
16068 placement: BlockPlacement::Below(range.start),
16069 height: Some(1),
16070 render: Arc::new({
16071 let rename_editor = rename_editor.clone();
16072 move |cx: &mut BlockContext| {
16073 let mut text_style = cx.editor_style.text.clone();
16074 if let Some(highlight_style) = old_highlight_id
16075 .and_then(|h| h.style(&cx.editor_style.syntax))
16076 {
16077 text_style = text_style.highlight(highlight_style);
16078 }
16079 div()
16080 .block_mouse_except_scroll()
16081 .pl(cx.anchor_x)
16082 .child(EditorElement::new(
16083 &rename_editor,
16084 EditorStyle {
16085 background: cx.theme().system().transparent,
16086 local_player: cx.editor_style.local_player,
16087 text: text_style,
16088 scrollbar_width: cx.editor_style.scrollbar_width,
16089 syntax: cx.editor_style.syntax.clone(),
16090 status: cx.editor_style.status.clone(),
16091 inlay_hints_style: HighlightStyle {
16092 font_weight: Some(FontWeight::BOLD),
16093 ..make_inlay_hints_style(cx.app)
16094 },
16095 inline_completion_styles: make_suggestion_styles(
16096 cx.app,
16097 ),
16098 ..EditorStyle::default()
16099 },
16100 ))
16101 .into_any_element()
16102 }
16103 }),
16104 priority: 0,
16105 render_in_minimap: true,
16106 }],
16107 Some(Autoscroll::fit()),
16108 cx,
16109 )[0];
16110 this.pending_rename = Some(RenameState {
16111 range,
16112 old_name,
16113 editor: rename_editor,
16114 block_id,
16115 });
16116 })?;
16117 }
16118
16119 Ok(())
16120 }))
16121 }
16122
16123 pub fn confirm_rename(
16124 &mut self,
16125 _: &ConfirmRename,
16126 window: &mut Window,
16127 cx: &mut Context<Self>,
16128 ) -> Option<Task<Result<()>>> {
16129 let rename = self.take_rename(false, window, cx)?;
16130 let workspace = self.workspace()?.downgrade();
16131 let (buffer, start) = self
16132 .buffer
16133 .read(cx)
16134 .text_anchor_for_position(rename.range.start, cx)?;
16135 let (end_buffer, _) = self
16136 .buffer
16137 .read(cx)
16138 .text_anchor_for_position(rename.range.end, cx)?;
16139 if buffer != end_buffer {
16140 return None;
16141 }
16142
16143 let old_name = rename.old_name;
16144 let new_name = rename.editor.read(cx).text(cx);
16145
16146 let rename = self.semantics_provider.as_ref()?.perform_rename(
16147 &buffer,
16148 start,
16149 new_name.clone(),
16150 cx,
16151 )?;
16152
16153 Some(cx.spawn_in(window, async move |editor, cx| {
16154 let project_transaction = rename.await?;
16155 Self::open_project_transaction(
16156 &editor,
16157 workspace,
16158 project_transaction,
16159 format!("Rename: {} → {}", old_name, new_name),
16160 cx,
16161 )
16162 .await?;
16163
16164 editor.update(cx, |editor, cx| {
16165 editor.refresh_document_highlights(cx);
16166 })?;
16167 Ok(())
16168 }))
16169 }
16170
16171 fn take_rename(
16172 &mut self,
16173 moving_cursor: bool,
16174 window: &mut Window,
16175 cx: &mut Context<Self>,
16176 ) -> Option<RenameState> {
16177 let rename = self.pending_rename.take()?;
16178 if rename.editor.focus_handle(cx).is_focused(window) {
16179 window.focus(&self.focus_handle);
16180 }
16181
16182 self.remove_blocks(
16183 [rename.block_id].into_iter().collect(),
16184 Some(Autoscroll::fit()),
16185 cx,
16186 );
16187 self.clear_highlights::<Rename>(cx);
16188 self.show_local_selections = true;
16189
16190 if moving_cursor {
16191 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16192 editor.selections.newest::<usize>(cx).head()
16193 });
16194
16195 // Update the selection to match the position of the selection inside
16196 // the rename editor.
16197 let snapshot = self.buffer.read(cx).read(cx);
16198 let rename_range = rename.range.to_offset(&snapshot);
16199 let cursor_in_editor = snapshot
16200 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16201 .min(rename_range.end);
16202 drop(snapshot);
16203
16204 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16205 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16206 });
16207 } else {
16208 self.refresh_document_highlights(cx);
16209 }
16210
16211 Some(rename)
16212 }
16213
16214 pub fn pending_rename(&self) -> Option<&RenameState> {
16215 self.pending_rename.as_ref()
16216 }
16217
16218 fn format(
16219 &mut self,
16220 _: &Format,
16221 window: &mut Window,
16222 cx: &mut Context<Self>,
16223 ) -> Option<Task<Result<()>>> {
16224 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16225
16226 let project = match &self.project {
16227 Some(project) => project.clone(),
16228 None => return None,
16229 };
16230
16231 Some(self.perform_format(
16232 project,
16233 FormatTrigger::Manual,
16234 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16235 window,
16236 cx,
16237 ))
16238 }
16239
16240 fn format_selections(
16241 &mut self,
16242 _: &FormatSelections,
16243 window: &mut Window,
16244 cx: &mut Context<Self>,
16245 ) -> Option<Task<Result<()>>> {
16246 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16247
16248 let project = match &self.project {
16249 Some(project) => project.clone(),
16250 None => return None,
16251 };
16252
16253 let ranges = self
16254 .selections
16255 .all_adjusted(cx)
16256 .into_iter()
16257 .map(|selection| selection.range())
16258 .collect_vec();
16259
16260 Some(self.perform_format(
16261 project,
16262 FormatTrigger::Manual,
16263 FormatTarget::Ranges(ranges),
16264 window,
16265 cx,
16266 ))
16267 }
16268
16269 fn perform_format(
16270 &mut self,
16271 project: Entity<Project>,
16272 trigger: FormatTrigger,
16273 target: FormatTarget,
16274 window: &mut Window,
16275 cx: &mut Context<Self>,
16276 ) -> Task<Result<()>> {
16277 let buffer = self.buffer.clone();
16278 let (buffers, target) = match target {
16279 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16280 FormatTarget::Ranges(selection_ranges) => {
16281 let multi_buffer = buffer.read(cx);
16282 let snapshot = multi_buffer.read(cx);
16283 let mut buffers = HashSet::default();
16284 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16285 BTreeMap::new();
16286 for selection_range in selection_ranges {
16287 for (buffer, buffer_range, _) in
16288 snapshot.range_to_buffer_ranges(selection_range)
16289 {
16290 let buffer_id = buffer.remote_id();
16291 let start = buffer.anchor_before(buffer_range.start);
16292 let end = buffer.anchor_after(buffer_range.end);
16293 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16294 buffer_id_to_ranges
16295 .entry(buffer_id)
16296 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16297 .or_insert_with(|| vec![start..end]);
16298 }
16299 }
16300 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16301 }
16302 };
16303
16304 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16305 let selections_prev = transaction_id_prev
16306 .and_then(|transaction_id_prev| {
16307 // default to selections as they were after the last edit, if we have them,
16308 // instead of how they are now.
16309 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16310 // will take you back to where you made the last edit, instead of staying where you scrolled
16311 self.selection_history
16312 .transaction(transaction_id_prev)
16313 .map(|t| t.0.clone())
16314 })
16315 .unwrap_or_else(|| {
16316 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16317 self.selections.disjoint_anchors()
16318 });
16319
16320 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16321 let format = project.update(cx, |project, cx| {
16322 project.format(buffers, target, true, trigger, cx)
16323 });
16324
16325 cx.spawn_in(window, async move |editor, cx| {
16326 let transaction = futures::select_biased! {
16327 transaction = format.log_err().fuse() => transaction,
16328 () = timeout => {
16329 log::warn!("timed out waiting for formatting");
16330 None
16331 }
16332 };
16333
16334 buffer
16335 .update(cx, |buffer, cx| {
16336 if let Some(transaction) = transaction {
16337 if !buffer.is_singleton() {
16338 buffer.push_transaction(&transaction.0, cx);
16339 }
16340 }
16341 cx.notify();
16342 })
16343 .ok();
16344
16345 if let Some(transaction_id_now) =
16346 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16347 {
16348 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16349 if has_new_transaction {
16350 _ = editor.update(cx, |editor, _| {
16351 editor
16352 .selection_history
16353 .insert_transaction(transaction_id_now, selections_prev);
16354 });
16355 }
16356 }
16357
16358 Ok(())
16359 })
16360 }
16361
16362 fn organize_imports(
16363 &mut self,
16364 _: &OrganizeImports,
16365 window: &mut Window,
16366 cx: &mut Context<Self>,
16367 ) -> Option<Task<Result<()>>> {
16368 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16369 let project = match &self.project {
16370 Some(project) => project.clone(),
16371 None => return None,
16372 };
16373 Some(self.perform_code_action_kind(
16374 project,
16375 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16376 window,
16377 cx,
16378 ))
16379 }
16380
16381 fn perform_code_action_kind(
16382 &mut self,
16383 project: Entity<Project>,
16384 kind: CodeActionKind,
16385 window: &mut Window,
16386 cx: &mut Context<Self>,
16387 ) -> Task<Result<()>> {
16388 let buffer = self.buffer.clone();
16389 let buffers = buffer.read(cx).all_buffers();
16390 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16391 let apply_action = project.update(cx, |project, cx| {
16392 project.apply_code_action_kind(buffers, kind, true, cx)
16393 });
16394 cx.spawn_in(window, async move |_, cx| {
16395 let transaction = futures::select_biased! {
16396 () = timeout => {
16397 log::warn!("timed out waiting for executing code action");
16398 None
16399 }
16400 transaction = apply_action.log_err().fuse() => transaction,
16401 };
16402 buffer
16403 .update(cx, |buffer, cx| {
16404 // check if we need this
16405 if let Some(transaction) = transaction {
16406 if !buffer.is_singleton() {
16407 buffer.push_transaction(&transaction.0, cx);
16408 }
16409 }
16410 cx.notify();
16411 })
16412 .ok();
16413 Ok(())
16414 })
16415 }
16416
16417 pub fn restart_language_server(
16418 &mut self,
16419 _: &RestartLanguageServer,
16420 _: &mut Window,
16421 cx: &mut Context<Self>,
16422 ) {
16423 if let Some(project) = self.project.clone() {
16424 self.buffer.update(cx, |multi_buffer, cx| {
16425 project.update(cx, |project, cx| {
16426 project.restart_language_servers_for_buffers(
16427 multi_buffer.all_buffers().into_iter().collect(),
16428 HashSet::default(),
16429 cx,
16430 );
16431 });
16432 })
16433 }
16434 }
16435
16436 pub fn stop_language_server(
16437 &mut self,
16438 _: &StopLanguageServer,
16439 _: &mut Window,
16440 cx: &mut Context<Self>,
16441 ) {
16442 if let Some(project) = self.project.clone() {
16443 self.buffer.update(cx, |multi_buffer, cx| {
16444 project.update(cx, |project, cx| {
16445 project.stop_language_servers_for_buffers(
16446 multi_buffer.all_buffers().into_iter().collect(),
16447 HashSet::default(),
16448 cx,
16449 );
16450 cx.emit(project::Event::RefreshInlayHints);
16451 });
16452 });
16453 }
16454 }
16455
16456 fn cancel_language_server_work(
16457 workspace: &mut Workspace,
16458 _: &actions::CancelLanguageServerWork,
16459 _: &mut Window,
16460 cx: &mut Context<Workspace>,
16461 ) {
16462 let project = workspace.project();
16463 let buffers = workspace
16464 .active_item(cx)
16465 .and_then(|item| item.act_as::<Editor>(cx))
16466 .map_or(HashSet::default(), |editor| {
16467 editor.read(cx).buffer.read(cx).all_buffers()
16468 });
16469 project.update(cx, |project, cx| {
16470 project.cancel_language_server_work_for_buffers(buffers, cx);
16471 });
16472 }
16473
16474 fn show_character_palette(
16475 &mut self,
16476 _: &ShowCharacterPalette,
16477 window: &mut Window,
16478 _: &mut Context<Self>,
16479 ) {
16480 window.show_character_palette();
16481 }
16482
16483 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16484 if !self.diagnostics_enabled() {
16485 return;
16486 }
16487
16488 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16489 let buffer = self.buffer.read(cx).snapshot(cx);
16490 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16491 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16492 let is_valid = buffer
16493 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16494 .any(|entry| {
16495 entry.diagnostic.is_primary
16496 && !entry.range.is_empty()
16497 && entry.range.start == primary_range_start
16498 && entry.diagnostic.message == active_diagnostics.active_message
16499 });
16500
16501 if !is_valid {
16502 self.dismiss_diagnostics(cx);
16503 }
16504 }
16505 }
16506
16507 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16508 match &self.active_diagnostics {
16509 ActiveDiagnostic::Group(group) => Some(group),
16510 _ => None,
16511 }
16512 }
16513
16514 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16515 if !self.diagnostics_enabled() {
16516 return;
16517 }
16518 self.dismiss_diagnostics(cx);
16519 self.active_diagnostics = ActiveDiagnostic::All;
16520 }
16521
16522 fn activate_diagnostics(
16523 &mut self,
16524 buffer_id: BufferId,
16525 diagnostic: DiagnosticEntry<usize>,
16526 window: &mut Window,
16527 cx: &mut Context<Self>,
16528 ) {
16529 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16530 return;
16531 }
16532 self.dismiss_diagnostics(cx);
16533 let snapshot = self.snapshot(window, cx);
16534 let buffer = self.buffer.read(cx).snapshot(cx);
16535 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16536 return;
16537 };
16538
16539 let diagnostic_group = buffer
16540 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16541 .collect::<Vec<_>>();
16542
16543 let blocks =
16544 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16545
16546 let blocks = self.display_map.update(cx, |display_map, cx| {
16547 display_map.insert_blocks(blocks, cx).into_iter().collect()
16548 });
16549 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16550 active_range: buffer.anchor_before(diagnostic.range.start)
16551 ..buffer.anchor_after(diagnostic.range.end),
16552 active_message: diagnostic.diagnostic.message.clone(),
16553 group_id: diagnostic.diagnostic.group_id,
16554 blocks,
16555 });
16556 cx.notify();
16557 }
16558
16559 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16560 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16561 return;
16562 };
16563
16564 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16565 if let ActiveDiagnostic::Group(group) = prev {
16566 self.display_map.update(cx, |display_map, cx| {
16567 display_map.remove_blocks(group.blocks, cx);
16568 });
16569 cx.notify();
16570 }
16571 }
16572
16573 /// Disable inline diagnostics rendering for this editor.
16574 pub fn disable_inline_diagnostics(&mut self) {
16575 self.inline_diagnostics_enabled = false;
16576 self.inline_diagnostics_update = Task::ready(());
16577 self.inline_diagnostics.clear();
16578 }
16579
16580 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16581 self.diagnostics_enabled = false;
16582 self.dismiss_diagnostics(cx);
16583 self.inline_diagnostics_update = Task::ready(());
16584 self.inline_diagnostics.clear();
16585 }
16586
16587 pub fn diagnostics_enabled(&self) -> bool {
16588 self.diagnostics_enabled && self.mode.is_full()
16589 }
16590
16591 pub fn inline_diagnostics_enabled(&self) -> bool {
16592 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16593 }
16594
16595 pub fn show_inline_diagnostics(&self) -> bool {
16596 self.show_inline_diagnostics
16597 }
16598
16599 pub fn toggle_inline_diagnostics(
16600 &mut self,
16601 _: &ToggleInlineDiagnostics,
16602 window: &mut Window,
16603 cx: &mut Context<Editor>,
16604 ) {
16605 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16606 self.refresh_inline_diagnostics(false, window, cx);
16607 }
16608
16609 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16610 self.diagnostics_max_severity = severity;
16611 self.display_map.update(cx, |display_map, _| {
16612 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16613 });
16614 }
16615
16616 pub fn toggle_diagnostics(
16617 &mut self,
16618 _: &ToggleDiagnostics,
16619 window: &mut Window,
16620 cx: &mut Context<Editor>,
16621 ) {
16622 if !self.diagnostics_enabled() {
16623 return;
16624 }
16625
16626 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16627 EditorSettings::get_global(cx)
16628 .diagnostics_max_severity
16629 .filter(|severity| severity != &DiagnosticSeverity::Off)
16630 .unwrap_or(DiagnosticSeverity::Hint)
16631 } else {
16632 DiagnosticSeverity::Off
16633 };
16634 self.set_max_diagnostics_severity(new_severity, cx);
16635 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16636 self.active_diagnostics = ActiveDiagnostic::None;
16637 self.inline_diagnostics_update = Task::ready(());
16638 self.inline_diagnostics.clear();
16639 } else {
16640 self.refresh_inline_diagnostics(false, window, cx);
16641 }
16642
16643 cx.notify();
16644 }
16645
16646 pub fn toggle_minimap(
16647 &mut self,
16648 _: &ToggleMinimap,
16649 window: &mut Window,
16650 cx: &mut Context<Editor>,
16651 ) {
16652 if self.supports_minimap(cx) {
16653 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16654 }
16655 }
16656
16657 fn refresh_inline_diagnostics(
16658 &mut self,
16659 debounce: bool,
16660 window: &mut Window,
16661 cx: &mut Context<Self>,
16662 ) {
16663 let max_severity = ProjectSettings::get_global(cx)
16664 .diagnostics
16665 .inline
16666 .max_severity
16667 .unwrap_or(self.diagnostics_max_severity);
16668
16669 if !self.inline_diagnostics_enabled()
16670 || !self.show_inline_diagnostics
16671 || max_severity == DiagnosticSeverity::Off
16672 {
16673 self.inline_diagnostics_update = Task::ready(());
16674 self.inline_diagnostics.clear();
16675 return;
16676 }
16677
16678 let debounce_ms = ProjectSettings::get_global(cx)
16679 .diagnostics
16680 .inline
16681 .update_debounce_ms;
16682 let debounce = if debounce && debounce_ms > 0 {
16683 Some(Duration::from_millis(debounce_ms))
16684 } else {
16685 None
16686 };
16687 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16688 if let Some(debounce) = debounce {
16689 cx.background_executor().timer(debounce).await;
16690 }
16691 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16692 editor
16693 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16694 .ok()
16695 }) else {
16696 return;
16697 };
16698
16699 let new_inline_diagnostics = cx
16700 .background_spawn(async move {
16701 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16702 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16703 let message = diagnostic_entry
16704 .diagnostic
16705 .message
16706 .split_once('\n')
16707 .map(|(line, _)| line)
16708 .map(SharedString::new)
16709 .unwrap_or_else(|| {
16710 SharedString::from(diagnostic_entry.diagnostic.message)
16711 });
16712 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16713 let (Ok(i) | Err(i)) = inline_diagnostics
16714 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16715 inline_diagnostics.insert(
16716 i,
16717 (
16718 start_anchor,
16719 InlineDiagnostic {
16720 message,
16721 group_id: diagnostic_entry.diagnostic.group_id,
16722 start: diagnostic_entry.range.start.to_point(&snapshot),
16723 is_primary: diagnostic_entry.diagnostic.is_primary,
16724 severity: diagnostic_entry.diagnostic.severity,
16725 },
16726 ),
16727 );
16728 }
16729 inline_diagnostics
16730 })
16731 .await;
16732
16733 editor
16734 .update(cx, |editor, cx| {
16735 editor.inline_diagnostics = new_inline_diagnostics;
16736 cx.notify();
16737 })
16738 .ok();
16739 });
16740 }
16741
16742 fn pull_diagnostics(
16743 &mut self,
16744 buffer_id: Option<BufferId>,
16745 window: &Window,
16746 cx: &mut Context<Self>,
16747 ) -> Option<()> {
16748 if !self.mode().is_full() {
16749 return None;
16750 }
16751 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16752 .diagnostics
16753 .lsp_pull_diagnostics;
16754 if !pull_diagnostics_settings.enabled {
16755 return None;
16756 }
16757 let project = self.project.as_ref()?.downgrade();
16758 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16759 let mut buffers = self.buffer.read(cx).all_buffers();
16760 if let Some(buffer_id) = buffer_id {
16761 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16762 }
16763
16764 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16765 cx.background_executor().timer(debounce).await;
16766
16767 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16768 buffers
16769 .into_iter()
16770 .filter_map(|buffer| {
16771 project
16772 .update(cx, |project, cx| {
16773 project.lsp_store().update(cx, |lsp_store, cx| {
16774 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
16775 })
16776 })
16777 .ok()
16778 })
16779 .collect::<FuturesUnordered<_>>()
16780 }) else {
16781 return;
16782 };
16783
16784 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16785 match pull_task {
16786 Ok(()) => {
16787 if editor
16788 .update_in(cx, |editor, window, cx| {
16789 editor.update_diagnostics_state(window, cx);
16790 })
16791 .is_err()
16792 {
16793 return;
16794 }
16795 }
16796 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16797 }
16798 }
16799 });
16800
16801 Some(())
16802 }
16803
16804 pub fn set_selections_from_remote(
16805 &mut self,
16806 selections: Vec<Selection<Anchor>>,
16807 pending_selection: Option<Selection<Anchor>>,
16808 window: &mut Window,
16809 cx: &mut Context<Self>,
16810 ) {
16811 let old_cursor_position = self.selections.newest_anchor().head();
16812 self.selections.change_with(cx, |s| {
16813 s.select_anchors(selections);
16814 if let Some(pending_selection) = pending_selection {
16815 s.set_pending(pending_selection, SelectMode::Character);
16816 } else {
16817 s.clear_pending();
16818 }
16819 });
16820 self.selections_did_change(
16821 false,
16822 &old_cursor_position,
16823 SelectionEffects::default(),
16824 window,
16825 cx,
16826 );
16827 }
16828
16829 pub fn transact(
16830 &mut self,
16831 window: &mut Window,
16832 cx: &mut Context<Self>,
16833 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16834 ) -> Option<TransactionId> {
16835 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16836 this.start_transaction_at(Instant::now(), window, cx);
16837 update(this, window, cx);
16838 this.end_transaction_at(Instant::now(), cx)
16839 })
16840 }
16841
16842 pub fn start_transaction_at(
16843 &mut self,
16844 now: Instant,
16845 window: &mut Window,
16846 cx: &mut Context<Self>,
16847 ) {
16848 self.end_selection(window, cx);
16849 if let Some(tx_id) = self
16850 .buffer
16851 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16852 {
16853 self.selection_history
16854 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16855 cx.emit(EditorEvent::TransactionBegun {
16856 transaction_id: tx_id,
16857 })
16858 }
16859 }
16860
16861 pub fn end_transaction_at(
16862 &mut self,
16863 now: Instant,
16864 cx: &mut Context<Self>,
16865 ) -> Option<TransactionId> {
16866 if let Some(transaction_id) = self
16867 .buffer
16868 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16869 {
16870 if let Some((_, end_selections)) =
16871 self.selection_history.transaction_mut(transaction_id)
16872 {
16873 *end_selections = Some(self.selections.disjoint_anchors());
16874 } else {
16875 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16876 }
16877
16878 cx.emit(EditorEvent::Edited { transaction_id });
16879 Some(transaction_id)
16880 } else {
16881 None
16882 }
16883 }
16884
16885 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16886 if self.selection_mark_mode {
16887 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16888 s.move_with(|_, sel| {
16889 sel.collapse_to(sel.head(), SelectionGoal::None);
16890 });
16891 })
16892 }
16893 self.selection_mark_mode = true;
16894 cx.notify();
16895 }
16896
16897 pub fn swap_selection_ends(
16898 &mut self,
16899 _: &actions::SwapSelectionEnds,
16900 window: &mut Window,
16901 cx: &mut Context<Self>,
16902 ) {
16903 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16904 s.move_with(|_, sel| {
16905 if sel.start != sel.end {
16906 sel.reversed = !sel.reversed
16907 }
16908 });
16909 });
16910 self.request_autoscroll(Autoscroll::newest(), cx);
16911 cx.notify();
16912 }
16913
16914 pub fn toggle_fold(
16915 &mut self,
16916 _: &actions::ToggleFold,
16917 window: &mut Window,
16918 cx: &mut Context<Self>,
16919 ) {
16920 if self.is_singleton(cx) {
16921 let selection = self.selections.newest::<Point>(cx);
16922
16923 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16924 let range = if selection.is_empty() {
16925 let point = selection.head().to_display_point(&display_map);
16926 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16927 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16928 .to_point(&display_map);
16929 start..end
16930 } else {
16931 selection.range()
16932 };
16933 if display_map.folds_in_range(range).next().is_some() {
16934 self.unfold_lines(&Default::default(), window, cx)
16935 } else {
16936 self.fold(&Default::default(), window, cx)
16937 }
16938 } else {
16939 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16940 let buffer_ids: HashSet<_> = self
16941 .selections
16942 .disjoint_anchor_ranges()
16943 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16944 .collect();
16945
16946 let should_unfold = buffer_ids
16947 .iter()
16948 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16949
16950 for buffer_id in buffer_ids {
16951 if should_unfold {
16952 self.unfold_buffer(buffer_id, cx);
16953 } else {
16954 self.fold_buffer(buffer_id, cx);
16955 }
16956 }
16957 }
16958 }
16959
16960 pub fn toggle_fold_recursive(
16961 &mut self,
16962 _: &actions::ToggleFoldRecursive,
16963 window: &mut Window,
16964 cx: &mut Context<Self>,
16965 ) {
16966 let selection = self.selections.newest::<Point>(cx);
16967
16968 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16969 let range = if selection.is_empty() {
16970 let point = selection.head().to_display_point(&display_map);
16971 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16972 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16973 .to_point(&display_map);
16974 start..end
16975 } else {
16976 selection.range()
16977 };
16978 if display_map.folds_in_range(range).next().is_some() {
16979 self.unfold_recursive(&Default::default(), window, cx)
16980 } else {
16981 self.fold_recursive(&Default::default(), window, cx)
16982 }
16983 }
16984
16985 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16986 if self.is_singleton(cx) {
16987 let mut to_fold = Vec::new();
16988 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16989 let selections = self.selections.all_adjusted(cx);
16990
16991 for selection in selections {
16992 let range = selection.range().sorted();
16993 let buffer_start_row = range.start.row;
16994
16995 if range.start.row != range.end.row {
16996 let mut found = false;
16997 let mut row = range.start.row;
16998 while row <= range.end.row {
16999 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17000 {
17001 found = true;
17002 row = crease.range().end.row + 1;
17003 to_fold.push(crease);
17004 } else {
17005 row += 1
17006 }
17007 }
17008 if found {
17009 continue;
17010 }
17011 }
17012
17013 for row in (0..=range.start.row).rev() {
17014 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17015 if crease.range().end.row >= buffer_start_row {
17016 to_fold.push(crease);
17017 if row <= range.start.row {
17018 break;
17019 }
17020 }
17021 }
17022 }
17023 }
17024
17025 self.fold_creases(to_fold, true, window, cx);
17026 } else {
17027 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17028 let buffer_ids = self
17029 .selections
17030 .disjoint_anchor_ranges()
17031 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17032 .collect::<HashSet<_>>();
17033 for buffer_id in buffer_ids {
17034 self.fold_buffer(buffer_id, cx);
17035 }
17036 }
17037 }
17038
17039 fn fold_at_level(
17040 &mut self,
17041 fold_at: &FoldAtLevel,
17042 window: &mut Window,
17043 cx: &mut Context<Self>,
17044 ) {
17045 if !self.buffer.read(cx).is_singleton() {
17046 return;
17047 }
17048
17049 let fold_at_level = fold_at.0;
17050 let snapshot = self.buffer.read(cx).snapshot(cx);
17051 let mut to_fold = Vec::new();
17052 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17053
17054 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17055 while start_row < end_row {
17056 match self
17057 .snapshot(window, cx)
17058 .crease_for_buffer_row(MultiBufferRow(start_row))
17059 {
17060 Some(crease) => {
17061 let nested_start_row = crease.range().start.row + 1;
17062 let nested_end_row = crease.range().end.row;
17063
17064 if current_level < fold_at_level {
17065 stack.push((nested_start_row, nested_end_row, current_level + 1));
17066 } else if current_level == fold_at_level {
17067 to_fold.push(crease);
17068 }
17069
17070 start_row = nested_end_row + 1;
17071 }
17072 None => start_row += 1,
17073 }
17074 }
17075 }
17076
17077 self.fold_creases(to_fold, true, window, cx);
17078 }
17079
17080 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17081 if self.buffer.read(cx).is_singleton() {
17082 let mut fold_ranges = Vec::new();
17083 let snapshot = self.buffer.read(cx).snapshot(cx);
17084
17085 for row in 0..snapshot.max_row().0 {
17086 if let Some(foldable_range) = self
17087 .snapshot(window, cx)
17088 .crease_for_buffer_row(MultiBufferRow(row))
17089 {
17090 fold_ranges.push(foldable_range);
17091 }
17092 }
17093
17094 self.fold_creases(fold_ranges, true, window, cx);
17095 } else {
17096 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17097 editor
17098 .update_in(cx, |editor, _, cx| {
17099 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17100 editor.fold_buffer(buffer_id, cx);
17101 }
17102 })
17103 .ok();
17104 });
17105 }
17106 }
17107
17108 pub fn fold_function_bodies(
17109 &mut self,
17110 _: &actions::FoldFunctionBodies,
17111 window: &mut Window,
17112 cx: &mut Context<Self>,
17113 ) {
17114 let snapshot = self.buffer.read(cx).snapshot(cx);
17115
17116 let ranges = snapshot
17117 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17118 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17119 .collect::<Vec<_>>();
17120
17121 let creases = ranges
17122 .into_iter()
17123 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17124 .collect();
17125
17126 self.fold_creases(creases, true, window, cx);
17127 }
17128
17129 pub fn fold_recursive(
17130 &mut self,
17131 _: &actions::FoldRecursive,
17132 window: &mut Window,
17133 cx: &mut Context<Self>,
17134 ) {
17135 let mut to_fold = Vec::new();
17136 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17137 let selections = self.selections.all_adjusted(cx);
17138
17139 for selection in selections {
17140 let range = selection.range().sorted();
17141 let buffer_start_row = range.start.row;
17142
17143 if range.start.row != range.end.row {
17144 let mut found = false;
17145 for row in range.start.row..=range.end.row {
17146 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17147 found = true;
17148 to_fold.push(crease);
17149 }
17150 }
17151 if found {
17152 continue;
17153 }
17154 }
17155
17156 for row in (0..=range.start.row).rev() {
17157 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17158 if crease.range().end.row >= buffer_start_row {
17159 to_fold.push(crease);
17160 } else {
17161 break;
17162 }
17163 }
17164 }
17165 }
17166
17167 self.fold_creases(to_fold, true, window, cx);
17168 }
17169
17170 pub fn fold_at(
17171 &mut self,
17172 buffer_row: MultiBufferRow,
17173 window: &mut Window,
17174 cx: &mut Context<Self>,
17175 ) {
17176 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17177
17178 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17179 let autoscroll = self
17180 .selections
17181 .all::<Point>(cx)
17182 .iter()
17183 .any(|selection| crease.range().overlaps(&selection.range()));
17184
17185 self.fold_creases(vec![crease], autoscroll, window, cx);
17186 }
17187 }
17188
17189 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17190 if self.is_singleton(cx) {
17191 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17192 let buffer = &display_map.buffer_snapshot;
17193 let selections = self.selections.all::<Point>(cx);
17194 let ranges = selections
17195 .iter()
17196 .map(|s| {
17197 let range = s.display_range(&display_map).sorted();
17198 let mut start = range.start.to_point(&display_map);
17199 let mut end = range.end.to_point(&display_map);
17200 start.column = 0;
17201 end.column = buffer.line_len(MultiBufferRow(end.row));
17202 start..end
17203 })
17204 .collect::<Vec<_>>();
17205
17206 self.unfold_ranges(&ranges, true, true, cx);
17207 } else {
17208 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17209 let buffer_ids = self
17210 .selections
17211 .disjoint_anchor_ranges()
17212 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17213 .collect::<HashSet<_>>();
17214 for buffer_id in buffer_ids {
17215 self.unfold_buffer(buffer_id, cx);
17216 }
17217 }
17218 }
17219
17220 pub fn unfold_recursive(
17221 &mut self,
17222 _: &UnfoldRecursive,
17223 _window: &mut Window,
17224 cx: &mut Context<Self>,
17225 ) {
17226 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17227 let selections = self.selections.all::<Point>(cx);
17228 let ranges = selections
17229 .iter()
17230 .map(|s| {
17231 let mut range = s.display_range(&display_map).sorted();
17232 *range.start.column_mut() = 0;
17233 *range.end.column_mut() = display_map.line_len(range.end.row());
17234 let start = range.start.to_point(&display_map);
17235 let end = range.end.to_point(&display_map);
17236 start..end
17237 })
17238 .collect::<Vec<_>>();
17239
17240 self.unfold_ranges(&ranges, true, true, cx);
17241 }
17242
17243 pub fn unfold_at(
17244 &mut self,
17245 buffer_row: MultiBufferRow,
17246 _window: &mut Window,
17247 cx: &mut Context<Self>,
17248 ) {
17249 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17250
17251 let intersection_range = Point::new(buffer_row.0, 0)
17252 ..Point::new(
17253 buffer_row.0,
17254 display_map.buffer_snapshot.line_len(buffer_row),
17255 );
17256
17257 let autoscroll = self
17258 .selections
17259 .all::<Point>(cx)
17260 .iter()
17261 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17262
17263 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17264 }
17265
17266 pub fn unfold_all(
17267 &mut self,
17268 _: &actions::UnfoldAll,
17269 _window: &mut Window,
17270 cx: &mut Context<Self>,
17271 ) {
17272 if self.buffer.read(cx).is_singleton() {
17273 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17274 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17275 } else {
17276 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17277 editor
17278 .update(cx, |editor, cx| {
17279 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17280 editor.unfold_buffer(buffer_id, cx);
17281 }
17282 })
17283 .ok();
17284 });
17285 }
17286 }
17287
17288 pub fn fold_selected_ranges(
17289 &mut self,
17290 _: &FoldSelectedRanges,
17291 window: &mut Window,
17292 cx: &mut Context<Self>,
17293 ) {
17294 let selections = self.selections.all_adjusted(cx);
17295 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17296 let ranges = selections
17297 .into_iter()
17298 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17299 .collect::<Vec<_>>();
17300 self.fold_creases(ranges, true, window, cx);
17301 }
17302
17303 pub fn fold_ranges<T: ToOffset + Clone>(
17304 &mut self,
17305 ranges: Vec<Range<T>>,
17306 auto_scroll: bool,
17307 window: &mut Window,
17308 cx: &mut Context<Self>,
17309 ) {
17310 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17311 let ranges = ranges
17312 .into_iter()
17313 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17314 .collect::<Vec<_>>();
17315 self.fold_creases(ranges, auto_scroll, window, cx);
17316 }
17317
17318 pub fn fold_creases<T: ToOffset + Clone>(
17319 &mut self,
17320 creases: Vec<Crease<T>>,
17321 auto_scroll: bool,
17322 _window: &mut Window,
17323 cx: &mut Context<Self>,
17324 ) {
17325 if creases.is_empty() {
17326 return;
17327 }
17328
17329 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17330
17331 if auto_scroll {
17332 self.request_autoscroll(Autoscroll::fit(), cx);
17333 }
17334
17335 cx.notify();
17336
17337 self.scrollbar_marker_state.dirty = true;
17338 self.folds_did_change(cx);
17339 }
17340
17341 /// Removes any folds whose ranges intersect any of the given ranges.
17342 pub fn unfold_ranges<T: ToOffset + Clone>(
17343 &mut self,
17344 ranges: &[Range<T>],
17345 inclusive: bool,
17346 auto_scroll: bool,
17347 cx: &mut Context<Self>,
17348 ) {
17349 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17350 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17351 });
17352 self.folds_did_change(cx);
17353 }
17354
17355 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17356 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17357 return;
17358 }
17359 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17360 self.display_map.update(cx, |display_map, cx| {
17361 display_map.fold_buffers([buffer_id], cx)
17362 });
17363 cx.emit(EditorEvent::BufferFoldToggled {
17364 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17365 folded: true,
17366 });
17367 cx.notify();
17368 }
17369
17370 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17371 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17372 return;
17373 }
17374 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17375 self.display_map.update(cx, |display_map, cx| {
17376 display_map.unfold_buffers([buffer_id], cx);
17377 });
17378 cx.emit(EditorEvent::BufferFoldToggled {
17379 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17380 folded: false,
17381 });
17382 cx.notify();
17383 }
17384
17385 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17386 self.display_map.read(cx).is_buffer_folded(buffer)
17387 }
17388
17389 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17390 self.display_map.read(cx).folded_buffers()
17391 }
17392
17393 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17394 self.display_map.update(cx, |display_map, cx| {
17395 display_map.disable_header_for_buffer(buffer_id, cx);
17396 });
17397 cx.notify();
17398 }
17399
17400 /// Removes any folds with the given ranges.
17401 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17402 &mut self,
17403 ranges: &[Range<T>],
17404 type_id: TypeId,
17405 auto_scroll: bool,
17406 cx: &mut Context<Self>,
17407 ) {
17408 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17409 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17410 });
17411 self.folds_did_change(cx);
17412 }
17413
17414 fn remove_folds_with<T: ToOffset + Clone>(
17415 &mut self,
17416 ranges: &[Range<T>],
17417 auto_scroll: bool,
17418 cx: &mut Context<Self>,
17419 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17420 ) {
17421 if ranges.is_empty() {
17422 return;
17423 }
17424
17425 let mut buffers_affected = HashSet::default();
17426 let multi_buffer = self.buffer().read(cx);
17427 for range in ranges {
17428 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17429 buffers_affected.insert(buffer.read(cx).remote_id());
17430 };
17431 }
17432
17433 self.display_map.update(cx, update);
17434
17435 if auto_scroll {
17436 self.request_autoscroll(Autoscroll::fit(), cx);
17437 }
17438
17439 cx.notify();
17440 self.scrollbar_marker_state.dirty = true;
17441 self.active_indent_guides_state.dirty = true;
17442 }
17443
17444 pub fn update_renderer_widths(
17445 &mut self,
17446 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17447 cx: &mut Context<Self>,
17448 ) -> bool {
17449 self.display_map
17450 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17451 }
17452
17453 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17454 self.display_map.read(cx).fold_placeholder.clone()
17455 }
17456
17457 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17458 self.buffer.update(cx, |buffer, cx| {
17459 buffer.set_all_diff_hunks_expanded(cx);
17460 });
17461 }
17462
17463 pub fn expand_all_diff_hunks(
17464 &mut self,
17465 _: &ExpandAllDiffHunks,
17466 _window: &mut Window,
17467 cx: &mut Context<Self>,
17468 ) {
17469 self.buffer.update(cx, |buffer, cx| {
17470 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17471 });
17472 }
17473
17474 pub fn toggle_selected_diff_hunks(
17475 &mut self,
17476 _: &ToggleSelectedDiffHunks,
17477 _window: &mut Window,
17478 cx: &mut Context<Self>,
17479 ) {
17480 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17481 self.toggle_diff_hunks_in_ranges(ranges, cx);
17482 }
17483
17484 pub fn diff_hunks_in_ranges<'a>(
17485 &'a self,
17486 ranges: &'a [Range<Anchor>],
17487 buffer: &'a MultiBufferSnapshot,
17488 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17489 ranges.iter().flat_map(move |range| {
17490 let end_excerpt_id = range.end.excerpt_id;
17491 let range = range.to_point(buffer);
17492 let mut peek_end = range.end;
17493 if range.end.row < buffer.max_row().0 {
17494 peek_end = Point::new(range.end.row + 1, 0);
17495 }
17496 buffer
17497 .diff_hunks_in_range(range.start..peek_end)
17498 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17499 })
17500 }
17501
17502 pub fn has_stageable_diff_hunks_in_ranges(
17503 &self,
17504 ranges: &[Range<Anchor>],
17505 snapshot: &MultiBufferSnapshot,
17506 ) -> bool {
17507 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17508 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17509 }
17510
17511 pub fn toggle_staged_selected_diff_hunks(
17512 &mut self,
17513 _: &::git::ToggleStaged,
17514 _: &mut Window,
17515 cx: &mut Context<Self>,
17516 ) {
17517 let snapshot = self.buffer.read(cx).snapshot(cx);
17518 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17519 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17520 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17521 }
17522
17523 pub fn set_render_diff_hunk_controls(
17524 &mut self,
17525 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17526 cx: &mut Context<Self>,
17527 ) {
17528 self.render_diff_hunk_controls = render_diff_hunk_controls;
17529 cx.notify();
17530 }
17531
17532 pub fn stage_and_next(
17533 &mut self,
17534 _: &::git::StageAndNext,
17535 window: &mut Window,
17536 cx: &mut Context<Self>,
17537 ) {
17538 self.do_stage_or_unstage_and_next(true, window, cx);
17539 }
17540
17541 pub fn unstage_and_next(
17542 &mut self,
17543 _: &::git::UnstageAndNext,
17544 window: &mut Window,
17545 cx: &mut Context<Self>,
17546 ) {
17547 self.do_stage_or_unstage_and_next(false, window, cx);
17548 }
17549
17550 pub fn stage_or_unstage_diff_hunks(
17551 &mut self,
17552 stage: bool,
17553 ranges: Vec<Range<Anchor>>,
17554 cx: &mut Context<Self>,
17555 ) {
17556 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17557 cx.spawn(async move |this, cx| {
17558 task.await?;
17559 this.update(cx, |this, cx| {
17560 let snapshot = this.buffer.read(cx).snapshot(cx);
17561 let chunk_by = this
17562 .diff_hunks_in_ranges(&ranges, &snapshot)
17563 .chunk_by(|hunk| hunk.buffer_id);
17564 for (buffer_id, hunks) in &chunk_by {
17565 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17566 }
17567 })
17568 })
17569 .detach_and_log_err(cx);
17570 }
17571
17572 fn save_buffers_for_ranges_if_needed(
17573 &mut self,
17574 ranges: &[Range<Anchor>],
17575 cx: &mut Context<Editor>,
17576 ) -> Task<Result<()>> {
17577 let multibuffer = self.buffer.read(cx);
17578 let snapshot = multibuffer.read(cx);
17579 let buffer_ids: HashSet<_> = ranges
17580 .iter()
17581 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17582 .collect();
17583 drop(snapshot);
17584
17585 let mut buffers = HashSet::default();
17586 for buffer_id in buffer_ids {
17587 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17588 let buffer = buffer_entity.read(cx);
17589 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17590 {
17591 buffers.insert(buffer_entity);
17592 }
17593 }
17594 }
17595
17596 if let Some(project) = &self.project {
17597 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17598 } else {
17599 Task::ready(Ok(()))
17600 }
17601 }
17602
17603 fn do_stage_or_unstage_and_next(
17604 &mut self,
17605 stage: bool,
17606 window: &mut Window,
17607 cx: &mut Context<Self>,
17608 ) {
17609 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17610
17611 if ranges.iter().any(|range| range.start != range.end) {
17612 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17613 return;
17614 }
17615
17616 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17617 let snapshot = self.snapshot(window, cx);
17618 let position = self.selections.newest::<Point>(cx).head();
17619 let mut row = snapshot
17620 .buffer_snapshot
17621 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17622 .find(|hunk| hunk.row_range.start.0 > position.row)
17623 .map(|hunk| hunk.row_range.start);
17624
17625 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17626 // Outside of the project diff editor, wrap around to the beginning.
17627 if !all_diff_hunks_expanded {
17628 row = row.or_else(|| {
17629 snapshot
17630 .buffer_snapshot
17631 .diff_hunks_in_range(Point::zero()..position)
17632 .find(|hunk| hunk.row_range.end.0 < position.row)
17633 .map(|hunk| hunk.row_range.start)
17634 });
17635 }
17636
17637 if let Some(row) = row {
17638 let destination = Point::new(row.0, 0);
17639 let autoscroll = Autoscroll::center();
17640
17641 self.unfold_ranges(&[destination..destination], false, false, cx);
17642 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17643 s.select_ranges([destination..destination]);
17644 });
17645 }
17646 }
17647
17648 fn do_stage_or_unstage(
17649 &self,
17650 stage: bool,
17651 buffer_id: BufferId,
17652 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17653 cx: &mut App,
17654 ) -> Option<()> {
17655 let project = self.project.as_ref()?;
17656 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17657 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17658 let buffer_snapshot = buffer.read(cx).snapshot();
17659 let file_exists = buffer_snapshot
17660 .file()
17661 .is_some_and(|file| file.disk_state().exists());
17662 diff.update(cx, |diff, cx| {
17663 diff.stage_or_unstage_hunks(
17664 stage,
17665 &hunks
17666 .map(|hunk| buffer_diff::DiffHunk {
17667 buffer_range: hunk.buffer_range,
17668 diff_base_byte_range: hunk.diff_base_byte_range,
17669 secondary_status: hunk.secondary_status,
17670 range: Point::zero()..Point::zero(), // unused
17671 })
17672 .collect::<Vec<_>>(),
17673 &buffer_snapshot,
17674 file_exists,
17675 cx,
17676 )
17677 });
17678 None
17679 }
17680
17681 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17682 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17683 self.buffer
17684 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17685 }
17686
17687 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17688 self.buffer.update(cx, |buffer, cx| {
17689 let ranges = vec![Anchor::min()..Anchor::max()];
17690 if !buffer.all_diff_hunks_expanded()
17691 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17692 {
17693 buffer.collapse_diff_hunks(ranges, cx);
17694 true
17695 } else {
17696 false
17697 }
17698 })
17699 }
17700
17701 fn toggle_diff_hunks_in_ranges(
17702 &mut self,
17703 ranges: Vec<Range<Anchor>>,
17704 cx: &mut Context<Editor>,
17705 ) {
17706 self.buffer.update(cx, |buffer, cx| {
17707 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17708 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17709 })
17710 }
17711
17712 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17713 self.buffer.update(cx, |buffer, cx| {
17714 let snapshot = buffer.snapshot(cx);
17715 let excerpt_id = range.end.excerpt_id;
17716 let point_range = range.to_point(&snapshot);
17717 let expand = !buffer.single_hunk_is_expanded(range, cx);
17718 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17719 })
17720 }
17721
17722 pub(crate) fn apply_all_diff_hunks(
17723 &mut self,
17724 _: &ApplyAllDiffHunks,
17725 window: &mut Window,
17726 cx: &mut Context<Self>,
17727 ) {
17728 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17729
17730 let buffers = self.buffer.read(cx).all_buffers();
17731 for branch_buffer in buffers {
17732 branch_buffer.update(cx, |branch_buffer, cx| {
17733 branch_buffer.merge_into_base(Vec::new(), cx);
17734 });
17735 }
17736
17737 if let Some(project) = self.project.clone() {
17738 self.save(
17739 SaveOptions {
17740 format: true,
17741 autosave: false,
17742 },
17743 project,
17744 window,
17745 cx,
17746 )
17747 .detach_and_log_err(cx);
17748 }
17749 }
17750
17751 pub(crate) fn apply_selected_diff_hunks(
17752 &mut self,
17753 _: &ApplyDiffHunk,
17754 window: &mut Window,
17755 cx: &mut Context<Self>,
17756 ) {
17757 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17758 let snapshot = self.snapshot(window, cx);
17759 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17760 let mut ranges_by_buffer = HashMap::default();
17761 self.transact(window, cx, |editor, _window, cx| {
17762 for hunk in hunks {
17763 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17764 ranges_by_buffer
17765 .entry(buffer.clone())
17766 .or_insert_with(Vec::new)
17767 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17768 }
17769 }
17770
17771 for (buffer, ranges) in ranges_by_buffer {
17772 buffer.update(cx, |buffer, cx| {
17773 buffer.merge_into_base(ranges, cx);
17774 });
17775 }
17776 });
17777
17778 if let Some(project) = self.project.clone() {
17779 self.save(
17780 SaveOptions {
17781 format: true,
17782 autosave: false,
17783 },
17784 project,
17785 window,
17786 cx,
17787 )
17788 .detach_and_log_err(cx);
17789 }
17790 }
17791
17792 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17793 if hovered != self.gutter_hovered {
17794 self.gutter_hovered = hovered;
17795 cx.notify();
17796 }
17797 }
17798
17799 pub fn insert_blocks(
17800 &mut self,
17801 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17802 autoscroll: Option<Autoscroll>,
17803 cx: &mut Context<Self>,
17804 ) -> Vec<CustomBlockId> {
17805 let blocks = self
17806 .display_map
17807 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17808 if let Some(autoscroll) = autoscroll {
17809 self.request_autoscroll(autoscroll, cx);
17810 }
17811 cx.notify();
17812 blocks
17813 }
17814
17815 pub fn resize_blocks(
17816 &mut self,
17817 heights: HashMap<CustomBlockId, u32>,
17818 autoscroll: Option<Autoscroll>,
17819 cx: &mut Context<Self>,
17820 ) {
17821 self.display_map
17822 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17823 if let Some(autoscroll) = autoscroll {
17824 self.request_autoscroll(autoscroll, cx);
17825 }
17826 cx.notify();
17827 }
17828
17829 pub fn replace_blocks(
17830 &mut self,
17831 renderers: HashMap<CustomBlockId, RenderBlock>,
17832 autoscroll: Option<Autoscroll>,
17833 cx: &mut Context<Self>,
17834 ) {
17835 self.display_map
17836 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17837 if let Some(autoscroll) = autoscroll {
17838 self.request_autoscroll(autoscroll, cx);
17839 }
17840 cx.notify();
17841 }
17842
17843 pub fn remove_blocks(
17844 &mut self,
17845 block_ids: HashSet<CustomBlockId>,
17846 autoscroll: Option<Autoscroll>,
17847 cx: &mut Context<Self>,
17848 ) {
17849 self.display_map.update(cx, |display_map, cx| {
17850 display_map.remove_blocks(block_ids, cx)
17851 });
17852 if let Some(autoscroll) = autoscroll {
17853 self.request_autoscroll(autoscroll, cx);
17854 }
17855 cx.notify();
17856 }
17857
17858 pub fn row_for_block(
17859 &self,
17860 block_id: CustomBlockId,
17861 cx: &mut Context<Self>,
17862 ) -> Option<DisplayRow> {
17863 self.display_map
17864 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17865 }
17866
17867 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17868 self.focused_block = Some(focused_block);
17869 }
17870
17871 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17872 self.focused_block.take()
17873 }
17874
17875 pub fn insert_creases(
17876 &mut self,
17877 creases: impl IntoIterator<Item = Crease<Anchor>>,
17878 cx: &mut Context<Self>,
17879 ) -> Vec<CreaseId> {
17880 self.display_map
17881 .update(cx, |map, cx| map.insert_creases(creases, cx))
17882 }
17883
17884 pub fn remove_creases(
17885 &mut self,
17886 ids: impl IntoIterator<Item = CreaseId>,
17887 cx: &mut Context<Self>,
17888 ) -> Vec<(CreaseId, Range<Anchor>)> {
17889 self.display_map
17890 .update(cx, |map, cx| map.remove_creases(ids, cx))
17891 }
17892
17893 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17894 self.display_map
17895 .update(cx, |map, cx| map.snapshot(cx))
17896 .longest_row()
17897 }
17898
17899 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17900 self.display_map
17901 .update(cx, |map, cx| map.snapshot(cx))
17902 .max_point()
17903 }
17904
17905 pub fn text(&self, cx: &App) -> String {
17906 self.buffer.read(cx).read(cx).text()
17907 }
17908
17909 pub fn is_empty(&self, cx: &App) -> bool {
17910 self.buffer.read(cx).read(cx).is_empty()
17911 }
17912
17913 pub fn text_option(&self, cx: &App) -> Option<String> {
17914 let text = self.text(cx);
17915 let text = text.trim();
17916
17917 if text.is_empty() {
17918 return None;
17919 }
17920
17921 Some(text.to_string())
17922 }
17923
17924 pub fn set_text(
17925 &mut self,
17926 text: impl Into<Arc<str>>,
17927 window: &mut Window,
17928 cx: &mut Context<Self>,
17929 ) {
17930 self.transact(window, cx, |this, _, cx| {
17931 this.buffer
17932 .read(cx)
17933 .as_singleton()
17934 .expect("you can only call set_text on editors for singleton buffers")
17935 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17936 });
17937 }
17938
17939 pub fn display_text(&self, cx: &mut App) -> String {
17940 self.display_map
17941 .update(cx, |map, cx| map.snapshot(cx))
17942 .text()
17943 }
17944
17945 fn create_minimap(
17946 &self,
17947 minimap_settings: MinimapSettings,
17948 window: &mut Window,
17949 cx: &mut Context<Self>,
17950 ) -> Option<Entity<Self>> {
17951 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17952 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17953 }
17954
17955 fn initialize_new_minimap(
17956 &self,
17957 minimap_settings: MinimapSettings,
17958 window: &mut Window,
17959 cx: &mut Context<Self>,
17960 ) -> Entity<Self> {
17961 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17962
17963 let mut minimap = Editor::new_internal(
17964 EditorMode::Minimap {
17965 parent: cx.weak_entity(),
17966 },
17967 self.buffer.clone(),
17968 self.project.clone(),
17969 Some(self.display_map.clone()),
17970 window,
17971 cx,
17972 );
17973 minimap.scroll_manager.clone_state(&self.scroll_manager);
17974 minimap.set_text_style_refinement(TextStyleRefinement {
17975 font_size: Some(MINIMAP_FONT_SIZE),
17976 font_weight: Some(MINIMAP_FONT_WEIGHT),
17977 ..Default::default()
17978 });
17979 minimap.update_minimap_configuration(minimap_settings, cx);
17980 cx.new(|_| minimap)
17981 }
17982
17983 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17984 let current_line_highlight = minimap_settings
17985 .current_line_highlight
17986 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17987 self.set_current_line_highlight(Some(current_line_highlight));
17988 }
17989
17990 pub fn minimap(&self) -> Option<&Entity<Self>> {
17991 self.minimap
17992 .as_ref()
17993 .filter(|_| self.minimap_visibility.visible())
17994 }
17995
17996 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17997 let mut wrap_guides = smallvec![];
17998
17999 if self.show_wrap_guides == Some(false) {
18000 return wrap_guides;
18001 }
18002
18003 let settings = self.buffer.read(cx).language_settings(cx);
18004 if settings.show_wrap_guides {
18005 match self.soft_wrap_mode(cx) {
18006 SoftWrap::Column(soft_wrap) => {
18007 wrap_guides.push((soft_wrap as usize, true));
18008 }
18009 SoftWrap::Bounded(soft_wrap) => {
18010 wrap_guides.push((soft_wrap as usize, true));
18011 }
18012 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18013 }
18014 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18015 }
18016
18017 wrap_guides
18018 }
18019
18020 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18021 let settings = self.buffer.read(cx).language_settings(cx);
18022 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18023 match mode {
18024 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18025 SoftWrap::None
18026 }
18027 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18028 language_settings::SoftWrap::PreferredLineLength => {
18029 SoftWrap::Column(settings.preferred_line_length)
18030 }
18031 language_settings::SoftWrap::Bounded => {
18032 SoftWrap::Bounded(settings.preferred_line_length)
18033 }
18034 }
18035 }
18036
18037 pub fn set_soft_wrap_mode(
18038 &mut self,
18039 mode: language_settings::SoftWrap,
18040
18041 cx: &mut Context<Self>,
18042 ) {
18043 self.soft_wrap_mode_override = Some(mode);
18044 cx.notify();
18045 }
18046
18047 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18048 self.hard_wrap = hard_wrap;
18049 cx.notify();
18050 }
18051
18052 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18053 self.text_style_refinement = Some(style);
18054 }
18055
18056 /// called by the Element so we know what style we were most recently rendered with.
18057 pub(crate) fn set_style(
18058 &mut self,
18059 style: EditorStyle,
18060 window: &mut Window,
18061 cx: &mut Context<Self>,
18062 ) {
18063 // We intentionally do not inform the display map about the minimap style
18064 // so that wrapping is not recalculated and stays consistent for the editor
18065 // and its linked minimap.
18066 if !self.mode.is_minimap() {
18067 let rem_size = window.rem_size();
18068 self.display_map.update(cx, |map, cx| {
18069 map.set_font(
18070 style.text.font(),
18071 style.text.font_size.to_pixels(rem_size),
18072 cx,
18073 )
18074 });
18075 }
18076 self.style = Some(style);
18077 }
18078
18079 pub fn style(&self) -> Option<&EditorStyle> {
18080 self.style.as_ref()
18081 }
18082
18083 // Called by the element. This method is not designed to be called outside of the editor
18084 // element's layout code because it does not notify when rewrapping is computed synchronously.
18085 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18086 self.display_map
18087 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18088 }
18089
18090 pub fn set_soft_wrap(&mut self) {
18091 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18092 }
18093
18094 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18095 if self.soft_wrap_mode_override.is_some() {
18096 self.soft_wrap_mode_override.take();
18097 } else {
18098 let soft_wrap = match self.soft_wrap_mode(cx) {
18099 SoftWrap::GitDiff => return,
18100 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18101 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18102 language_settings::SoftWrap::None
18103 }
18104 };
18105 self.soft_wrap_mode_override = Some(soft_wrap);
18106 }
18107 cx.notify();
18108 }
18109
18110 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18111 let Some(workspace) = self.workspace() else {
18112 return;
18113 };
18114 let fs = workspace.read(cx).app_state().fs.clone();
18115 let current_show = TabBarSettings::get_global(cx).show;
18116 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18117 setting.show = Some(!current_show);
18118 });
18119 }
18120
18121 pub fn toggle_indent_guides(
18122 &mut self,
18123 _: &ToggleIndentGuides,
18124 _: &mut Window,
18125 cx: &mut Context<Self>,
18126 ) {
18127 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18128 self.buffer
18129 .read(cx)
18130 .language_settings(cx)
18131 .indent_guides
18132 .enabled
18133 });
18134 self.show_indent_guides = Some(!currently_enabled);
18135 cx.notify();
18136 }
18137
18138 fn should_show_indent_guides(&self) -> Option<bool> {
18139 self.show_indent_guides
18140 }
18141
18142 pub fn toggle_line_numbers(
18143 &mut self,
18144 _: &ToggleLineNumbers,
18145 _: &mut Window,
18146 cx: &mut Context<Self>,
18147 ) {
18148 let mut editor_settings = EditorSettings::get_global(cx).clone();
18149 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18150 EditorSettings::override_global(editor_settings, cx);
18151 }
18152
18153 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18154 if let Some(show_line_numbers) = self.show_line_numbers {
18155 return show_line_numbers;
18156 }
18157 EditorSettings::get_global(cx).gutter.line_numbers
18158 }
18159
18160 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18161 self.use_relative_line_numbers
18162 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18163 }
18164
18165 pub fn toggle_relative_line_numbers(
18166 &mut self,
18167 _: &ToggleRelativeLineNumbers,
18168 _: &mut Window,
18169 cx: &mut Context<Self>,
18170 ) {
18171 let is_relative = self.should_use_relative_line_numbers(cx);
18172 self.set_relative_line_number(Some(!is_relative), cx)
18173 }
18174
18175 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18176 self.use_relative_line_numbers = is_relative;
18177 cx.notify();
18178 }
18179
18180 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18181 self.show_gutter = show_gutter;
18182 cx.notify();
18183 }
18184
18185 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18186 self.show_scrollbars = ScrollbarAxes {
18187 horizontal: show,
18188 vertical: show,
18189 };
18190 cx.notify();
18191 }
18192
18193 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18194 self.show_scrollbars.vertical = show;
18195 cx.notify();
18196 }
18197
18198 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18199 self.show_scrollbars.horizontal = show;
18200 cx.notify();
18201 }
18202
18203 pub fn set_minimap_visibility(
18204 &mut self,
18205 minimap_visibility: MinimapVisibility,
18206 window: &mut Window,
18207 cx: &mut Context<Self>,
18208 ) {
18209 if self.minimap_visibility != minimap_visibility {
18210 if minimap_visibility.visible() && self.minimap.is_none() {
18211 let minimap_settings = EditorSettings::get_global(cx).minimap;
18212 self.minimap =
18213 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18214 }
18215 self.minimap_visibility = minimap_visibility;
18216 cx.notify();
18217 }
18218 }
18219
18220 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18221 self.set_show_scrollbars(false, cx);
18222 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18223 }
18224
18225 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18226 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18227 }
18228
18229 /// Normally the text in full mode and auto height editors is padded on the
18230 /// left side by roughly half a character width for improved hit testing.
18231 ///
18232 /// Use this method to disable this for cases where this is not wanted (e.g.
18233 /// if you want to align the editor text with some other text above or below)
18234 /// or if you want to add this padding to single-line editors.
18235 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18236 self.offset_content = offset_content;
18237 cx.notify();
18238 }
18239
18240 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18241 self.show_line_numbers = Some(show_line_numbers);
18242 cx.notify();
18243 }
18244
18245 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18246 self.disable_expand_excerpt_buttons = true;
18247 cx.notify();
18248 }
18249
18250 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18251 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18252 cx.notify();
18253 }
18254
18255 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18256 self.show_code_actions = Some(show_code_actions);
18257 cx.notify();
18258 }
18259
18260 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18261 self.show_runnables = Some(show_runnables);
18262 cx.notify();
18263 }
18264
18265 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18266 self.show_breakpoints = Some(show_breakpoints);
18267 cx.notify();
18268 }
18269
18270 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18271 if self.display_map.read(cx).masked != masked {
18272 self.display_map.update(cx, |map, _| map.masked = masked);
18273 }
18274 cx.notify()
18275 }
18276
18277 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18278 self.show_wrap_guides = Some(show_wrap_guides);
18279 cx.notify();
18280 }
18281
18282 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18283 self.show_indent_guides = Some(show_indent_guides);
18284 cx.notify();
18285 }
18286
18287 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18288 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18289 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
18290 if let Some(dir) = file.abs_path(cx).parent() {
18291 return Some(dir.to_owned());
18292 }
18293 }
18294
18295 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18296 return Some(project_path.path.to_path_buf());
18297 }
18298 }
18299
18300 None
18301 }
18302
18303 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18304 self.active_excerpt(cx)?
18305 .1
18306 .read(cx)
18307 .file()
18308 .and_then(|f| f.as_local())
18309 }
18310
18311 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18312 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18313 let buffer = buffer.read(cx);
18314 if let Some(project_path) = buffer.project_path(cx) {
18315 let project = self.project.as_ref()?.read(cx);
18316 project.absolute_path(&project_path, cx)
18317 } else {
18318 buffer
18319 .file()
18320 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18321 }
18322 })
18323 }
18324
18325 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18326 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18327 let project_path = buffer.read(cx).project_path(cx)?;
18328 let project = self.project.as_ref()?.read(cx);
18329 let entry = project.entry_for_path(&project_path, cx)?;
18330 let path = entry.path.to_path_buf();
18331 Some(path)
18332 })
18333 }
18334
18335 pub fn reveal_in_finder(
18336 &mut self,
18337 _: &RevealInFileManager,
18338 _window: &mut Window,
18339 cx: &mut Context<Self>,
18340 ) {
18341 if let Some(target) = self.target_file(cx) {
18342 cx.reveal_path(&target.abs_path(cx));
18343 }
18344 }
18345
18346 pub fn copy_path(
18347 &mut self,
18348 _: &zed_actions::workspace::CopyPath,
18349 _window: &mut Window,
18350 cx: &mut Context<Self>,
18351 ) {
18352 if let Some(path) = self.target_file_abs_path(cx) {
18353 if let Some(path) = path.to_str() {
18354 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18355 }
18356 }
18357 }
18358
18359 pub fn copy_relative_path(
18360 &mut self,
18361 _: &zed_actions::workspace::CopyRelativePath,
18362 _window: &mut Window,
18363 cx: &mut Context<Self>,
18364 ) {
18365 if let Some(path) = self.target_file_path(cx) {
18366 if let Some(path) = path.to_str() {
18367 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18368 }
18369 }
18370 }
18371
18372 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18373 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18374 buffer.read(cx).project_path(cx)
18375 } else {
18376 None
18377 }
18378 }
18379
18380 // Returns true if the editor handled a go-to-line request
18381 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18382 maybe!({
18383 let breakpoint_store = self.breakpoint_store.as_ref()?;
18384
18385 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18386 else {
18387 self.clear_row_highlights::<ActiveDebugLine>();
18388 return None;
18389 };
18390
18391 let position = active_stack_frame.position;
18392 let buffer_id = position.buffer_id?;
18393 let snapshot = self
18394 .project
18395 .as_ref()?
18396 .read(cx)
18397 .buffer_for_id(buffer_id, cx)?
18398 .read(cx)
18399 .snapshot();
18400
18401 let mut handled = false;
18402 for (id, ExcerptRange { context, .. }) in
18403 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18404 {
18405 if context.start.cmp(&position, &snapshot).is_ge()
18406 || context.end.cmp(&position, &snapshot).is_lt()
18407 {
18408 continue;
18409 }
18410 let snapshot = self.buffer.read(cx).snapshot(cx);
18411 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18412
18413 handled = true;
18414 self.clear_row_highlights::<ActiveDebugLine>();
18415
18416 self.go_to_line::<ActiveDebugLine>(
18417 multibuffer_anchor,
18418 Some(cx.theme().colors().editor_debugger_active_line_background),
18419 window,
18420 cx,
18421 );
18422
18423 cx.notify();
18424 }
18425
18426 handled.then_some(())
18427 })
18428 .is_some()
18429 }
18430
18431 pub fn copy_file_name_without_extension(
18432 &mut self,
18433 _: &CopyFileNameWithoutExtension,
18434 _: &mut Window,
18435 cx: &mut Context<Self>,
18436 ) {
18437 if let Some(file) = self.target_file(cx) {
18438 if let Some(file_stem) = file.path().file_stem() {
18439 if let Some(name) = file_stem.to_str() {
18440 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18441 }
18442 }
18443 }
18444 }
18445
18446 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18447 if let Some(file) = self.target_file(cx) {
18448 if let Some(file_name) = file.path().file_name() {
18449 if let Some(name) = file_name.to_str() {
18450 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18451 }
18452 }
18453 }
18454 }
18455
18456 pub fn toggle_git_blame(
18457 &mut self,
18458 _: &::git::Blame,
18459 window: &mut Window,
18460 cx: &mut Context<Self>,
18461 ) {
18462 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18463
18464 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18465 self.start_git_blame(true, window, cx);
18466 }
18467
18468 cx.notify();
18469 }
18470
18471 pub fn toggle_git_blame_inline(
18472 &mut self,
18473 _: &ToggleGitBlameInline,
18474 window: &mut Window,
18475 cx: &mut Context<Self>,
18476 ) {
18477 self.toggle_git_blame_inline_internal(true, window, cx);
18478 cx.notify();
18479 }
18480
18481 pub fn open_git_blame_commit(
18482 &mut self,
18483 _: &OpenGitBlameCommit,
18484 window: &mut Window,
18485 cx: &mut Context<Self>,
18486 ) {
18487 self.open_git_blame_commit_internal(window, cx);
18488 }
18489
18490 fn open_git_blame_commit_internal(
18491 &mut self,
18492 window: &mut Window,
18493 cx: &mut Context<Self>,
18494 ) -> Option<()> {
18495 let blame = self.blame.as_ref()?;
18496 let snapshot = self.snapshot(window, cx);
18497 let cursor = self.selections.newest::<Point>(cx).head();
18498 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18499 let blame_entry = blame
18500 .update(cx, |blame, cx| {
18501 blame
18502 .blame_for_rows(
18503 &[RowInfo {
18504 buffer_id: Some(buffer.remote_id()),
18505 buffer_row: Some(point.row),
18506 ..Default::default()
18507 }],
18508 cx,
18509 )
18510 .next()
18511 })
18512 .flatten()?;
18513 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18514 let repo = blame.read(cx).repository(cx)?;
18515 let workspace = self.workspace()?.downgrade();
18516 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18517 None
18518 }
18519
18520 pub fn git_blame_inline_enabled(&self) -> bool {
18521 self.git_blame_inline_enabled
18522 }
18523
18524 pub fn toggle_selection_menu(
18525 &mut self,
18526 _: &ToggleSelectionMenu,
18527 _: &mut Window,
18528 cx: &mut Context<Self>,
18529 ) {
18530 self.show_selection_menu = self
18531 .show_selection_menu
18532 .map(|show_selections_menu| !show_selections_menu)
18533 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18534
18535 cx.notify();
18536 }
18537
18538 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18539 self.show_selection_menu
18540 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18541 }
18542
18543 fn start_git_blame(
18544 &mut self,
18545 user_triggered: bool,
18546 window: &mut Window,
18547 cx: &mut Context<Self>,
18548 ) {
18549 if let Some(project) = self.project.as_ref() {
18550 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18551 return;
18552 };
18553
18554 if buffer.read(cx).file().is_none() {
18555 return;
18556 }
18557
18558 let focused = self.focus_handle(cx).contains_focused(window, cx);
18559
18560 let project = project.clone();
18561 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18562 self.blame_subscription =
18563 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18564 self.blame = Some(blame);
18565 }
18566 }
18567
18568 fn toggle_git_blame_inline_internal(
18569 &mut self,
18570 user_triggered: bool,
18571 window: &mut Window,
18572 cx: &mut Context<Self>,
18573 ) {
18574 if self.git_blame_inline_enabled {
18575 self.git_blame_inline_enabled = false;
18576 self.show_git_blame_inline = false;
18577 self.show_git_blame_inline_delay_task.take();
18578 } else {
18579 self.git_blame_inline_enabled = true;
18580 self.start_git_blame_inline(user_triggered, window, cx);
18581 }
18582
18583 cx.notify();
18584 }
18585
18586 fn start_git_blame_inline(
18587 &mut self,
18588 user_triggered: bool,
18589 window: &mut Window,
18590 cx: &mut Context<Self>,
18591 ) {
18592 self.start_git_blame(user_triggered, window, cx);
18593
18594 if ProjectSettings::get_global(cx)
18595 .git
18596 .inline_blame_delay()
18597 .is_some()
18598 {
18599 self.start_inline_blame_timer(window, cx);
18600 } else {
18601 self.show_git_blame_inline = true
18602 }
18603 }
18604
18605 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18606 self.blame.as_ref()
18607 }
18608
18609 pub fn show_git_blame_gutter(&self) -> bool {
18610 self.show_git_blame_gutter
18611 }
18612
18613 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18614 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18615 }
18616
18617 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18618 self.show_git_blame_inline
18619 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18620 && !self.newest_selection_head_on_empty_line(cx)
18621 && self.has_blame_entries(cx)
18622 }
18623
18624 fn has_blame_entries(&self, cx: &App) -> bool {
18625 self.blame()
18626 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18627 }
18628
18629 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18630 let cursor_anchor = self.selections.newest_anchor().head();
18631
18632 let snapshot = self.buffer.read(cx).snapshot(cx);
18633 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18634
18635 snapshot.line_len(buffer_row) == 0
18636 }
18637
18638 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18639 let buffer_and_selection = maybe!({
18640 let selection = self.selections.newest::<Point>(cx);
18641 let selection_range = selection.range();
18642
18643 let multi_buffer = self.buffer().read(cx);
18644 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18645 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18646
18647 let (buffer, range, _) = if selection.reversed {
18648 buffer_ranges.first()
18649 } else {
18650 buffer_ranges.last()
18651 }?;
18652
18653 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18654 ..text::ToPoint::to_point(&range.end, &buffer).row;
18655 Some((
18656 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18657 selection,
18658 ))
18659 });
18660
18661 let Some((buffer, selection)) = buffer_and_selection else {
18662 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18663 };
18664
18665 let Some(project) = self.project.as_ref() else {
18666 return Task::ready(Err(anyhow!("editor does not have project")));
18667 };
18668
18669 project.update(cx, |project, cx| {
18670 project.get_permalink_to_line(&buffer, selection, cx)
18671 })
18672 }
18673
18674 pub fn copy_permalink_to_line(
18675 &mut self,
18676 _: &CopyPermalinkToLine,
18677 window: &mut Window,
18678 cx: &mut Context<Self>,
18679 ) {
18680 let permalink_task = self.get_permalink_to_line(cx);
18681 let workspace = self.workspace();
18682
18683 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18684 Ok(permalink) => {
18685 cx.update(|_, cx| {
18686 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18687 })
18688 .ok();
18689 }
18690 Err(err) => {
18691 let message = format!("Failed to copy permalink: {err}");
18692
18693 anyhow::Result::<()>::Err(err).log_err();
18694
18695 if let Some(workspace) = workspace {
18696 workspace
18697 .update_in(cx, |workspace, _, cx| {
18698 struct CopyPermalinkToLine;
18699
18700 workspace.show_toast(
18701 Toast::new(
18702 NotificationId::unique::<CopyPermalinkToLine>(),
18703 message,
18704 ),
18705 cx,
18706 )
18707 })
18708 .ok();
18709 }
18710 }
18711 })
18712 .detach();
18713 }
18714
18715 pub fn copy_file_location(
18716 &mut self,
18717 _: &CopyFileLocation,
18718 _: &mut Window,
18719 cx: &mut Context<Self>,
18720 ) {
18721 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18722 if let Some(file) = self.target_file(cx) {
18723 if let Some(path) = file.path().to_str() {
18724 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18725 }
18726 }
18727 }
18728
18729 pub fn open_permalink_to_line(
18730 &mut self,
18731 _: &OpenPermalinkToLine,
18732 window: &mut Window,
18733 cx: &mut Context<Self>,
18734 ) {
18735 let permalink_task = self.get_permalink_to_line(cx);
18736 let workspace = self.workspace();
18737
18738 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18739 Ok(permalink) => {
18740 cx.update(|_, cx| {
18741 cx.open_url(permalink.as_ref());
18742 })
18743 .ok();
18744 }
18745 Err(err) => {
18746 let message = format!("Failed to open permalink: {err}");
18747
18748 anyhow::Result::<()>::Err(err).log_err();
18749
18750 if let Some(workspace) = workspace {
18751 workspace
18752 .update(cx, |workspace, cx| {
18753 struct OpenPermalinkToLine;
18754
18755 workspace.show_toast(
18756 Toast::new(
18757 NotificationId::unique::<OpenPermalinkToLine>(),
18758 message,
18759 ),
18760 cx,
18761 )
18762 })
18763 .ok();
18764 }
18765 }
18766 })
18767 .detach();
18768 }
18769
18770 pub fn insert_uuid_v4(
18771 &mut self,
18772 _: &InsertUuidV4,
18773 window: &mut Window,
18774 cx: &mut Context<Self>,
18775 ) {
18776 self.insert_uuid(UuidVersion::V4, window, cx);
18777 }
18778
18779 pub fn insert_uuid_v7(
18780 &mut self,
18781 _: &InsertUuidV7,
18782 window: &mut Window,
18783 cx: &mut Context<Self>,
18784 ) {
18785 self.insert_uuid(UuidVersion::V7, window, cx);
18786 }
18787
18788 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18789 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18790 self.transact(window, cx, |this, window, cx| {
18791 let edits = this
18792 .selections
18793 .all::<Point>(cx)
18794 .into_iter()
18795 .map(|selection| {
18796 let uuid = match version {
18797 UuidVersion::V4 => uuid::Uuid::new_v4(),
18798 UuidVersion::V7 => uuid::Uuid::now_v7(),
18799 };
18800
18801 (selection.range(), uuid.to_string())
18802 });
18803 this.edit(edits, cx);
18804 this.refresh_inline_completion(true, false, window, cx);
18805 });
18806 }
18807
18808 pub fn open_selections_in_multibuffer(
18809 &mut self,
18810 _: &OpenSelectionsInMultibuffer,
18811 window: &mut Window,
18812 cx: &mut Context<Self>,
18813 ) {
18814 let multibuffer = self.buffer.read(cx);
18815
18816 let Some(buffer) = multibuffer.as_singleton() else {
18817 return;
18818 };
18819
18820 let Some(workspace) = self.workspace() else {
18821 return;
18822 };
18823
18824 let title = multibuffer.title(cx).to_string();
18825
18826 let locations = self
18827 .selections
18828 .all_anchors(cx)
18829 .into_iter()
18830 .map(|selection| Location {
18831 buffer: buffer.clone(),
18832 range: selection.start.text_anchor..selection.end.text_anchor,
18833 })
18834 .collect::<Vec<_>>();
18835
18836 cx.spawn_in(window, async move |_, cx| {
18837 workspace.update_in(cx, |workspace, window, cx| {
18838 Self::open_locations_in_multibuffer(
18839 workspace,
18840 locations,
18841 format!("Selections for '{title}'"),
18842 false,
18843 MultibufferSelectionMode::All,
18844 window,
18845 cx,
18846 );
18847 })
18848 })
18849 .detach();
18850 }
18851
18852 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18853 /// last highlight added will be used.
18854 ///
18855 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18856 pub fn highlight_rows<T: 'static>(
18857 &mut self,
18858 range: Range<Anchor>,
18859 color: Hsla,
18860 options: RowHighlightOptions,
18861 cx: &mut Context<Self>,
18862 ) {
18863 let snapshot = self.buffer().read(cx).snapshot(cx);
18864 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18865 let ix = row_highlights.binary_search_by(|highlight| {
18866 Ordering::Equal
18867 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18868 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18869 });
18870
18871 if let Err(mut ix) = ix {
18872 let index = post_inc(&mut self.highlight_order);
18873
18874 // If this range intersects with the preceding highlight, then merge it with
18875 // the preceding highlight. Otherwise insert a new highlight.
18876 let mut merged = false;
18877 if ix > 0 {
18878 let prev_highlight = &mut row_highlights[ix - 1];
18879 if prev_highlight
18880 .range
18881 .end
18882 .cmp(&range.start, &snapshot)
18883 .is_ge()
18884 {
18885 ix -= 1;
18886 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18887 prev_highlight.range.end = range.end;
18888 }
18889 merged = true;
18890 prev_highlight.index = index;
18891 prev_highlight.color = color;
18892 prev_highlight.options = options;
18893 }
18894 }
18895
18896 if !merged {
18897 row_highlights.insert(
18898 ix,
18899 RowHighlight {
18900 range: range.clone(),
18901 index,
18902 color,
18903 options,
18904 type_id: TypeId::of::<T>(),
18905 },
18906 );
18907 }
18908
18909 // If any of the following highlights intersect with this one, merge them.
18910 while let Some(next_highlight) = row_highlights.get(ix + 1) {
18911 let highlight = &row_highlights[ix];
18912 if next_highlight
18913 .range
18914 .start
18915 .cmp(&highlight.range.end, &snapshot)
18916 .is_le()
18917 {
18918 if next_highlight
18919 .range
18920 .end
18921 .cmp(&highlight.range.end, &snapshot)
18922 .is_gt()
18923 {
18924 row_highlights[ix].range.end = next_highlight.range.end;
18925 }
18926 row_highlights.remove(ix + 1);
18927 } else {
18928 break;
18929 }
18930 }
18931 }
18932 }
18933
18934 /// Remove any highlighted row ranges of the given type that intersect the
18935 /// given ranges.
18936 pub fn remove_highlighted_rows<T: 'static>(
18937 &mut self,
18938 ranges_to_remove: Vec<Range<Anchor>>,
18939 cx: &mut Context<Self>,
18940 ) {
18941 let snapshot = self.buffer().read(cx).snapshot(cx);
18942 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18943 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18944 row_highlights.retain(|highlight| {
18945 while let Some(range_to_remove) = ranges_to_remove.peek() {
18946 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18947 Ordering::Less | Ordering::Equal => {
18948 ranges_to_remove.next();
18949 }
18950 Ordering::Greater => {
18951 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18952 Ordering::Less | Ordering::Equal => {
18953 return false;
18954 }
18955 Ordering::Greater => break,
18956 }
18957 }
18958 }
18959 }
18960
18961 true
18962 })
18963 }
18964
18965 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18966 pub fn clear_row_highlights<T: 'static>(&mut self) {
18967 self.highlighted_rows.remove(&TypeId::of::<T>());
18968 }
18969
18970 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18971 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18972 self.highlighted_rows
18973 .get(&TypeId::of::<T>())
18974 .map_or(&[] as &[_], |vec| vec.as_slice())
18975 .iter()
18976 .map(|highlight| (highlight.range.clone(), highlight.color))
18977 }
18978
18979 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18980 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18981 /// Allows to ignore certain kinds of highlights.
18982 pub fn highlighted_display_rows(
18983 &self,
18984 window: &mut Window,
18985 cx: &mut App,
18986 ) -> BTreeMap<DisplayRow, LineHighlight> {
18987 let snapshot = self.snapshot(window, cx);
18988 let mut used_highlight_orders = HashMap::default();
18989 self.highlighted_rows
18990 .iter()
18991 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18992 .fold(
18993 BTreeMap::<DisplayRow, LineHighlight>::new(),
18994 |mut unique_rows, highlight| {
18995 let start = highlight.range.start.to_display_point(&snapshot);
18996 let end = highlight.range.end.to_display_point(&snapshot);
18997 let start_row = start.row().0;
18998 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18999 && end.column() == 0
19000 {
19001 end.row().0.saturating_sub(1)
19002 } else {
19003 end.row().0
19004 };
19005 for row in start_row..=end_row {
19006 let used_index =
19007 used_highlight_orders.entry(row).or_insert(highlight.index);
19008 if highlight.index >= *used_index {
19009 *used_index = highlight.index;
19010 unique_rows.insert(
19011 DisplayRow(row),
19012 LineHighlight {
19013 include_gutter: highlight.options.include_gutter,
19014 border: None,
19015 background: highlight.color.into(),
19016 type_id: Some(highlight.type_id),
19017 },
19018 );
19019 }
19020 }
19021 unique_rows
19022 },
19023 )
19024 }
19025
19026 pub fn highlighted_display_row_for_autoscroll(
19027 &self,
19028 snapshot: &DisplaySnapshot,
19029 ) -> Option<DisplayRow> {
19030 self.highlighted_rows
19031 .values()
19032 .flat_map(|highlighted_rows| highlighted_rows.iter())
19033 .filter_map(|highlight| {
19034 if highlight.options.autoscroll {
19035 Some(highlight.range.start.to_display_point(snapshot).row())
19036 } else {
19037 None
19038 }
19039 })
19040 .min()
19041 }
19042
19043 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19044 self.highlight_background::<SearchWithinRange>(
19045 ranges,
19046 |colors| colors.colors().editor_document_highlight_read_background,
19047 cx,
19048 )
19049 }
19050
19051 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19052 self.breadcrumb_header = Some(new_header);
19053 }
19054
19055 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19056 self.clear_background_highlights::<SearchWithinRange>(cx);
19057 }
19058
19059 pub fn highlight_background<T: 'static>(
19060 &mut self,
19061 ranges: &[Range<Anchor>],
19062 color_fetcher: fn(&Theme) -> Hsla,
19063 cx: &mut Context<Self>,
19064 ) {
19065 self.background_highlights.insert(
19066 HighlightKey::Type(TypeId::of::<T>()),
19067 (color_fetcher, Arc::from(ranges)),
19068 );
19069 self.scrollbar_marker_state.dirty = true;
19070 cx.notify();
19071 }
19072
19073 pub fn highlight_background_key<T: 'static>(
19074 &mut self,
19075 key: usize,
19076 ranges: &[Range<Anchor>],
19077 color_fetcher: fn(&Theme) -> Hsla,
19078 cx: &mut Context<Self>,
19079 ) {
19080 self.background_highlights.insert(
19081 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19082 (color_fetcher, Arc::from(ranges)),
19083 );
19084 self.scrollbar_marker_state.dirty = true;
19085 cx.notify();
19086 }
19087
19088 pub fn clear_background_highlights<T: 'static>(
19089 &mut self,
19090 cx: &mut Context<Self>,
19091 ) -> Option<BackgroundHighlight> {
19092 let text_highlights = self
19093 .background_highlights
19094 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19095 if !text_highlights.1.is_empty() {
19096 self.scrollbar_marker_state.dirty = true;
19097 cx.notify();
19098 }
19099 Some(text_highlights)
19100 }
19101
19102 pub fn highlight_gutter<T: 'static>(
19103 &mut self,
19104 ranges: impl Into<Vec<Range<Anchor>>>,
19105 color_fetcher: fn(&App) -> Hsla,
19106 cx: &mut Context<Self>,
19107 ) {
19108 self.gutter_highlights
19109 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19110 cx.notify();
19111 }
19112
19113 pub fn clear_gutter_highlights<T: 'static>(
19114 &mut self,
19115 cx: &mut Context<Self>,
19116 ) -> Option<GutterHighlight> {
19117 cx.notify();
19118 self.gutter_highlights.remove(&TypeId::of::<T>())
19119 }
19120
19121 pub fn insert_gutter_highlight<T: 'static>(
19122 &mut self,
19123 range: Range<Anchor>,
19124 color_fetcher: fn(&App) -> Hsla,
19125 cx: &mut Context<Self>,
19126 ) {
19127 let snapshot = self.buffer().read(cx).snapshot(cx);
19128 let mut highlights = self
19129 .gutter_highlights
19130 .remove(&TypeId::of::<T>())
19131 .map(|(_, highlights)| highlights)
19132 .unwrap_or_default();
19133 let ix = highlights.binary_search_by(|highlight| {
19134 Ordering::Equal
19135 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19136 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19137 });
19138 if let Err(ix) = ix {
19139 highlights.insert(ix, range);
19140 }
19141 self.gutter_highlights
19142 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19143 }
19144
19145 pub fn remove_gutter_highlights<T: 'static>(
19146 &mut self,
19147 ranges_to_remove: Vec<Range<Anchor>>,
19148 cx: &mut Context<Self>,
19149 ) {
19150 let snapshot = self.buffer().read(cx).snapshot(cx);
19151 let Some((color_fetcher, mut gutter_highlights)) =
19152 self.gutter_highlights.remove(&TypeId::of::<T>())
19153 else {
19154 return;
19155 };
19156 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19157 gutter_highlights.retain(|highlight| {
19158 while let Some(range_to_remove) = ranges_to_remove.peek() {
19159 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19160 Ordering::Less | Ordering::Equal => {
19161 ranges_to_remove.next();
19162 }
19163 Ordering::Greater => {
19164 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19165 Ordering::Less | Ordering::Equal => {
19166 return false;
19167 }
19168 Ordering::Greater => break,
19169 }
19170 }
19171 }
19172 }
19173
19174 true
19175 });
19176 self.gutter_highlights
19177 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19178 }
19179
19180 #[cfg(feature = "test-support")]
19181 pub fn all_text_highlights(
19182 &self,
19183 window: &mut Window,
19184 cx: &mut Context<Self>,
19185 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19186 let snapshot = self.snapshot(window, cx);
19187 self.display_map.update(cx, |display_map, _| {
19188 display_map
19189 .all_text_highlights()
19190 .map(|highlight| {
19191 let (style, ranges) = highlight.as_ref();
19192 (
19193 *style,
19194 ranges
19195 .iter()
19196 .map(|range| range.clone().to_display_points(&snapshot))
19197 .collect(),
19198 )
19199 })
19200 .collect()
19201 })
19202 }
19203
19204 #[cfg(feature = "test-support")]
19205 pub fn all_text_background_highlights(
19206 &self,
19207 window: &mut Window,
19208 cx: &mut Context<Self>,
19209 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19210 let snapshot = self.snapshot(window, cx);
19211 let buffer = &snapshot.buffer_snapshot;
19212 let start = buffer.anchor_before(0);
19213 let end = buffer.anchor_after(buffer.len());
19214 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19215 }
19216
19217 #[cfg(feature = "test-support")]
19218 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19219 let snapshot = self.buffer().read(cx).snapshot(cx);
19220
19221 let highlights = self
19222 .background_highlights
19223 .get(&HighlightKey::Type(TypeId::of::<
19224 items::BufferSearchHighlights,
19225 >()));
19226
19227 if let Some((_color, ranges)) = highlights {
19228 ranges
19229 .iter()
19230 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19231 .collect_vec()
19232 } else {
19233 vec![]
19234 }
19235 }
19236
19237 fn document_highlights_for_position<'a>(
19238 &'a self,
19239 position: Anchor,
19240 buffer: &'a MultiBufferSnapshot,
19241 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19242 let read_highlights = self
19243 .background_highlights
19244 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19245 .map(|h| &h.1);
19246 let write_highlights = self
19247 .background_highlights
19248 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19249 .map(|h| &h.1);
19250 let left_position = position.bias_left(buffer);
19251 let right_position = position.bias_right(buffer);
19252 read_highlights
19253 .into_iter()
19254 .chain(write_highlights)
19255 .flat_map(move |ranges| {
19256 let start_ix = match ranges.binary_search_by(|probe| {
19257 let cmp = probe.end.cmp(&left_position, buffer);
19258 if cmp.is_ge() {
19259 Ordering::Greater
19260 } else {
19261 Ordering::Less
19262 }
19263 }) {
19264 Ok(i) | Err(i) => i,
19265 };
19266
19267 ranges[start_ix..]
19268 .iter()
19269 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19270 })
19271 }
19272
19273 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19274 self.background_highlights
19275 .get(&HighlightKey::Type(TypeId::of::<T>()))
19276 .map_or(false, |(_, highlights)| !highlights.is_empty())
19277 }
19278
19279 pub fn background_highlights_in_range(
19280 &self,
19281 search_range: Range<Anchor>,
19282 display_snapshot: &DisplaySnapshot,
19283 theme: &Theme,
19284 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19285 let mut results = Vec::new();
19286 for (color_fetcher, ranges) in self.background_highlights.values() {
19287 let color = color_fetcher(theme);
19288 let start_ix = match ranges.binary_search_by(|probe| {
19289 let cmp = probe
19290 .end
19291 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19292 if cmp.is_gt() {
19293 Ordering::Greater
19294 } else {
19295 Ordering::Less
19296 }
19297 }) {
19298 Ok(i) | Err(i) => i,
19299 };
19300 for range in &ranges[start_ix..] {
19301 if range
19302 .start
19303 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19304 .is_ge()
19305 {
19306 break;
19307 }
19308
19309 let start = range.start.to_display_point(display_snapshot);
19310 let end = range.end.to_display_point(display_snapshot);
19311 results.push((start..end, color))
19312 }
19313 }
19314 results
19315 }
19316
19317 pub fn background_highlight_row_ranges<T: 'static>(
19318 &self,
19319 search_range: Range<Anchor>,
19320 display_snapshot: &DisplaySnapshot,
19321 count: usize,
19322 ) -> Vec<RangeInclusive<DisplayPoint>> {
19323 let mut results = Vec::new();
19324 let Some((_, ranges)) = self
19325 .background_highlights
19326 .get(&HighlightKey::Type(TypeId::of::<T>()))
19327 else {
19328 return vec![];
19329 };
19330
19331 let start_ix = match ranges.binary_search_by(|probe| {
19332 let cmp = probe
19333 .end
19334 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19335 if cmp.is_gt() {
19336 Ordering::Greater
19337 } else {
19338 Ordering::Less
19339 }
19340 }) {
19341 Ok(i) | Err(i) => i,
19342 };
19343 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19344 if let (Some(start_display), Some(end_display)) = (start, end) {
19345 results.push(
19346 start_display.to_display_point(display_snapshot)
19347 ..=end_display.to_display_point(display_snapshot),
19348 );
19349 }
19350 };
19351 let mut start_row: Option<Point> = None;
19352 let mut end_row: Option<Point> = None;
19353 if ranges.len() > count {
19354 return Vec::new();
19355 }
19356 for range in &ranges[start_ix..] {
19357 if range
19358 .start
19359 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19360 .is_ge()
19361 {
19362 break;
19363 }
19364 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19365 if let Some(current_row) = &end_row {
19366 if end.row == current_row.row {
19367 continue;
19368 }
19369 }
19370 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19371 if start_row.is_none() {
19372 assert_eq!(end_row, None);
19373 start_row = Some(start);
19374 end_row = Some(end);
19375 continue;
19376 }
19377 if let Some(current_end) = end_row.as_mut() {
19378 if start.row > current_end.row + 1 {
19379 push_region(start_row, end_row);
19380 start_row = Some(start);
19381 end_row = Some(end);
19382 } else {
19383 // Merge two hunks.
19384 *current_end = end;
19385 }
19386 } else {
19387 unreachable!();
19388 }
19389 }
19390 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19391 push_region(start_row, end_row);
19392 results
19393 }
19394
19395 pub fn gutter_highlights_in_range(
19396 &self,
19397 search_range: Range<Anchor>,
19398 display_snapshot: &DisplaySnapshot,
19399 cx: &App,
19400 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19401 let mut results = Vec::new();
19402 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19403 let color = color_fetcher(cx);
19404 let start_ix = match ranges.binary_search_by(|probe| {
19405 let cmp = probe
19406 .end
19407 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19408 if cmp.is_gt() {
19409 Ordering::Greater
19410 } else {
19411 Ordering::Less
19412 }
19413 }) {
19414 Ok(i) | Err(i) => i,
19415 };
19416 for range in &ranges[start_ix..] {
19417 if range
19418 .start
19419 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19420 .is_ge()
19421 {
19422 break;
19423 }
19424
19425 let start = range.start.to_display_point(display_snapshot);
19426 let end = range.end.to_display_point(display_snapshot);
19427 results.push((start..end, color))
19428 }
19429 }
19430 results
19431 }
19432
19433 /// Get the text ranges corresponding to the redaction query
19434 pub fn redacted_ranges(
19435 &self,
19436 search_range: Range<Anchor>,
19437 display_snapshot: &DisplaySnapshot,
19438 cx: &App,
19439 ) -> Vec<Range<DisplayPoint>> {
19440 display_snapshot
19441 .buffer_snapshot
19442 .redacted_ranges(search_range, |file| {
19443 if let Some(file) = file {
19444 file.is_private()
19445 && EditorSettings::get(
19446 Some(SettingsLocation {
19447 worktree_id: file.worktree_id(cx),
19448 path: file.path().as_ref(),
19449 }),
19450 cx,
19451 )
19452 .redact_private_values
19453 } else {
19454 false
19455 }
19456 })
19457 .map(|range| {
19458 range.start.to_display_point(display_snapshot)
19459 ..range.end.to_display_point(display_snapshot)
19460 })
19461 .collect()
19462 }
19463
19464 pub fn highlight_text_key<T: 'static>(
19465 &mut self,
19466 key: usize,
19467 ranges: Vec<Range<Anchor>>,
19468 style: HighlightStyle,
19469 cx: &mut Context<Self>,
19470 ) {
19471 self.display_map.update(cx, |map, _| {
19472 map.highlight_text(
19473 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19474 ranges,
19475 style,
19476 );
19477 });
19478 cx.notify();
19479 }
19480
19481 pub fn highlight_text<T: 'static>(
19482 &mut self,
19483 ranges: Vec<Range<Anchor>>,
19484 style: HighlightStyle,
19485 cx: &mut Context<Self>,
19486 ) {
19487 self.display_map.update(cx, |map, _| {
19488 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19489 });
19490 cx.notify();
19491 }
19492
19493 pub(crate) fn highlight_inlays<T: 'static>(
19494 &mut self,
19495 highlights: Vec<InlayHighlight>,
19496 style: HighlightStyle,
19497 cx: &mut Context<Self>,
19498 ) {
19499 self.display_map.update(cx, |map, _| {
19500 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19501 });
19502 cx.notify();
19503 }
19504
19505 pub fn text_highlights<'a, T: 'static>(
19506 &'a self,
19507 cx: &'a App,
19508 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19509 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19510 }
19511
19512 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19513 let cleared = self
19514 .display_map
19515 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19516 if cleared {
19517 cx.notify();
19518 }
19519 }
19520
19521 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19522 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19523 && self.focus_handle.is_focused(window)
19524 }
19525
19526 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19527 self.show_cursor_when_unfocused = is_enabled;
19528 cx.notify();
19529 }
19530
19531 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19532 cx.notify();
19533 }
19534
19535 fn on_debug_session_event(
19536 &mut self,
19537 _session: Entity<Session>,
19538 event: &SessionEvent,
19539 cx: &mut Context<Self>,
19540 ) {
19541 match event {
19542 SessionEvent::InvalidateInlineValue => {
19543 self.refresh_inline_values(cx);
19544 }
19545 _ => {}
19546 }
19547 }
19548
19549 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19550 let Some(project) = self.project.clone() else {
19551 return;
19552 };
19553
19554 if !self.inline_value_cache.enabled {
19555 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19556 self.splice_inlays(&inlays, Vec::new(), cx);
19557 return;
19558 }
19559
19560 let current_execution_position = self
19561 .highlighted_rows
19562 .get(&TypeId::of::<ActiveDebugLine>())
19563 .and_then(|lines| lines.last().map(|line| line.range.end));
19564
19565 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19566 let inline_values = editor
19567 .update(cx, |editor, cx| {
19568 let Some(current_execution_position) = current_execution_position else {
19569 return Some(Task::ready(Ok(Vec::new())));
19570 };
19571
19572 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19573 let snapshot = buffer.snapshot(cx);
19574
19575 let excerpt = snapshot.excerpt_containing(
19576 current_execution_position..current_execution_position,
19577 )?;
19578
19579 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19580 })?;
19581
19582 let range =
19583 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19584
19585 project.inline_values(buffer, range, cx)
19586 })
19587 .ok()
19588 .flatten()?
19589 .await
19590 .context("refreshing debugger inlays")
19591 .log_err()?;
19592
19593 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19594
19595 for (buffer_id, inline_value) in inline_values
19596 .into_iter()
19597 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19598 {
19599 buffer_inline_values
19600 .entry(buffer_id)
19601 .or_default()
19602 .push(inline_value);
19603 }
19604
19605 editor
19606 .update(cx, |editor, cx| {
19607 let snapshot = editor.buffer.read(cx).snapshot(cx);
19608 let mut new_inlays = Vec::default();
19609
19610 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19611 let buffer_id = buffer_snapshot.remote_id();
19612 buffer_inline_values
19613 .get(&buffer_id)
19614 .into_iter()
19615 .flatten()
19616 .for_each(|hint| {
19617 let inlay = Inlay::debugger(
19618 post_inc(&mut editor.next_inlay_id),
19619 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19620 hint.text(),
19621 );
19622
19623 new_inlays.push(inlay);
19624 });
19625 }
19626
19627 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19628 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19629
19630 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19631 })
19632 .ok()?;
19633 Some(())
19634 });
19635 }
19636
19637 fn on_buffer_event(
19638 &mut self,
19639 multibuffer: &Entity<MultiBuffer>,
19640 event: &multi_buffer::Event,
19641 window: &mut Window,
19642 cx: &mut Context<Self>,
19643 ) {
19644 match event {
19645 multi_buffer::Event::Edited {
19646 singleton_buffer_edited,
19647 edited_buffer,
19648 } => {
19649 self.scrollbar_marker_state.dirty = true;
19650 self.active_indent_guides_state.dirty = true;
19651 self.refresh_active_diagnostics(cx);
19652 self.refresh_code_actions(window, cx);
19653 self.refresh_selected_text_highlights(true, window, cx);
19654 self.refresh_single_line_folds(window, cx);
19655 refresh_matching_bracket_highlights(self, window, cx);
19656 if self.has_active_inline_completion() {
19657 self.update_visible_inline_completion(window, cx);
19658 }
19659 if let Some(project) = self.project.as_ref() {
19660 if let Some(edited_buffer) = edited_buffer {
19661 project.update(cx, |project, cx| {
19662 self.registered_buffers
19663 .entry(edited_buffer.read(cx).remote_id())
19664 .or_insert_with(|| {
19665 project
19666 .register_buffer_with_language_servers(&edited_buffer, cx)
19667 });
19668 });
19669 }
19670 }
19671 cx.emit(EditorEvent::BufferEdited);
19672 cx.emit(SearchEvent::MatchesInvalidated);
19673
19674 if let Some(buffer) = edited_buffer {
19675 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
19676 }
19677
19678 if *singleton_buffer_edited {
19679 if let Some(buffer) = edited_buffer {
19680 if buffer.read(cx).file().is_none() {
19681 cx.emit(EditorEvent::TitleChanged);
19682 }
19683 }
19684 if let Some(project) = &self.project {
19685 #[allow(clippy::mutable_key_type)]
19686 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19687 multibuffer
19688 .all_buffers()
19689 .into_iter()
19690 .filter_map(|buffer| {
19691 buffer.update(cx, |buffer, cx| {
19692 let language = buffer.language()?;
19693 let should_discard = project.update(cx, |project, cx| {
19694 project.is_local()
19695 && !project.has_language_servers_for(buffer, cx)
19696 });
19697 should_discard.not().then_some(language.clone())
19698 })
19699 })
19700 .collect::<HashSet<_>>()
19701 });
19702 if !languages_affected.is_empty() {
19703 self.refresh_inlay_hints(
19704 InlayHintRefreshReason::BufferEdited(languages_affected),
19705 cx,
19706 );
19707 }
19708 }
19709 }
19710
19711 let Some(project) = &self.project else { return };
19712 let (telemetry, is_via_ssh) = {
19713 let project = project.read(cx);
19714 let telemetry = project.client().telemetry().clone();
19715 let is_via_ssh = project.is_via_ssh();
19716 (telemetry, is_via_ssh)
19717 };
19718 refresh_linked_ranges(self, window, cx);
19719 telemetry.log_edit_event("editor", is_via_ssh);
19720 }
19721 multi_buffer::Event::ExcerptsAdded {
19722 buffer,
19723 predecessor,
19724 excerpts,
19725 } => {
19726 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19727 let buffer_id = buffer.read(cx).remote_id();
19728 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19729 if let Some(project) = &self.project {
19730 update_uncommitted_diff_for_buffer(
19731 cx.entity(),
19732 project,
19733 [buffer.clone()],
19734 self.buffer.clone(),
19735 cx,
19736 )
19737 .detach();
19738 }
19739 }
19740 self.update_lsp_data(false, Some(buffer_id), window, cx);
19741 cx.emit(EditorEvent::ExcerptsAdded {
19742 buffer: buffer.clone(),
19743 predecessor: *predecessor,
19744 excerpts: excerpts.clone(),
19745 });
19746 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19747 }
19748 multi_buffer::Event::ExcerptsRemoved {
19749 ids,
19750 removed_buffer_ids,
19751 } => {
19752 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19753 let buffer = self.buffer.read(cx);
19754 self.registered_buffers
19755 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19756 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19757 cx.emit(EditorEvent::ExcerptsRemoved {
19758 ids: ids.clone(),
19759 removed_buffer_ids: removed_buffer_ids.clone(),
19760 });
19761 }
19762 multi_buffer::Event::ExcerptsEdited {
19763 excerpt_ids,
19764 buffer_ids,
19765 } => {
19766 self.display_map.update(cx, |map, cx| {
19767 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19768 });
19769 cx.emit(EditorEvent::ExcerptsEdited {
19770 ids: excerpt_ids.clone(),
19771 });
19772 }
19773 multi_buffer::Event::ExcerptsExpanded { ids } => {
19774 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19775 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19776 }
19777 multi_buffer::Event::Reparsed(buffer_id) => {
19778 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19779 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19780
19781 cx.emit(EditorEvent::Reparsed(*buffer_id));
19782 }
19783 multi_buffer::Event::DiffHunksToggled => {
19784 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19785 }
19786 multi_buffer::Event::LanguageChanged(buffer_id) => {
19787 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19788 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19789 cx.emit(EditorEvent::Reparsed(*buffer_id));
19790 cx.notify();
19791 }
19792 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19793 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19794 multi_buffer::Event::FileHandleChanged
19795 | multi_buffer::Event::Reloaded
19796 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19797 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19798 multi_buffer::Event::DiagnosticsUpdated => {
19799 self.update_diagnostics_state(window, cx);
19800 }
19801 _ => {}
19802 };
19803 }
19804
19805 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19806 if !self.diagnostics_enabled() {
19807 return;
19808 }
19809 self.refresh_active_diagnostics(cx);
19810 self.refresh_inline_diagnostics(true, window, cx);
19811 self.scrollbar_marker_state.dirty = true;
19812 cx.notify();
19813 }
19814
19815 pub fn start_temporary_diff_override(&mut self) {
19816 self.load_diff_task.take();
19817 self.temporary_diff_override = true;
19818 }
19819
19820 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19821 self.temporary_diff_override = false;
19822 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19823 self.buffer.update(cx, |buffer, cx| {
19824 buffer.set_all_diff_hunks_collapsed(cx);
19825 });
19826
19827 if let Some(project) = self.project.clone() {
19828 self.load_diff_task = Some(
19829 update_uncommitted_diff_for_buffer(
19830 cx.entity(),
19831 &project,
19832 self.buffer.read(cx).all_buffers(),
19833 self.buffer.clone(),
19834 cx,
19835 )
19836 .shared(),
19837 );
19838 }
19839 }
19840
19841 fn on_display_map_changed(
19842 &mut self,
19843 _: Entity<DisplayMap>,
19844 _: &mut Window,
19845 cx: &mut Context<Self>,
19846 ) {
19847 cx.notify();
19848 }
19849
19850 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19851 let new_severity = if self.diagnostics_enabled() {
19852 EditorSettings::get_global(cx)
19853 .diagnostics_max_severity
19854 .unwrap_or(DiagnosticSeverity::Hint)
19855 } else {
19856 DiagnosticSeverity::Off
19857 };
19858 self.set_max_diagnostics_severity(new_severity, cx);
19859 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19860 self.update_edit_prediction_settings(cx);
19861 self.refresh_inline_completion(true, false, window, cx);
19862 self.refresh_inline_values(cx);
19863 self.refresh_inlay_hints(
19864 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19865 self.selections.newest_anchor().head(),
19866 &self.buffer.read(cx).snapshot(cx),
19867 cx,
19868 )),
19869 cx,
19870 );
19871
19872 let old_cursor_shape = self.cursor_shape;
19873
19874 {
19875 let editor_settings = EditorSettings::get_global(cx);
19876 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19877 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19878 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19879 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19880 }
19881
19882 if old_cursor_shape != self.cursor_shape {
19883 cx.emit(EditorEvent::CursorShapeChanged);
19884 }
19885
19886 let project_settings = ProjectSettings::get_global(cx);
19887 self.serialize_dirty_buffers =
19888 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19889
19890 if self.mode.is_full() {
19891 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19892 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19893 if self.show_inline_diagnostics != show_inline_diagnostics {
19894 self.show_inline_diagnostics = show_inline_diagnostics;
19895 self.refresh_inline_diagnostics(false, window, cx);
19896 }
19897
19898 if self.git_blame_inline_enabled != inline_blame_enabled {
19899 self.toggle_git_blame_inline_internal(false, window, cx);
19900 }
19901
19902 let minimap_settings = EditorSettings::get_global(cx).minimap;
19903 if self.minimap_visibility != MinimapVisibility::Disabled {
19904 if self.minimap_visibility.settings_visibility()
19905 != minimap_settings.minimap_enabled()
19906 {
19907 self.set_minimap_visibility(
19908 MinimapVisibility::for_mode(self.mode(), cx),
19909 window,
19910 cx,
19911 );
19912 } else if let Some(minimap_entity) = self.minimap.as_ref() {
19913 minimap_entity.update(cx, |minimap_editor, cx| {
19914 minimap_editor.update_minimap_configuration(minimap_settings, cx)
19915 })
19916 }
19917 }
19918 }
19919
19920 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
19921 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
19922 }) {
19923 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
19924 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
19925 }
19926 self.refresh_colors(false, None, window, cx);
19927 }
19928
19929 cx.notify();
19930 }
19931
19932 pub fn set_searchable(&mut self, searchable: bool) {
19933 self.searchable = searchable;
19934 }
19935
19936 pub fn searchable(&self) -> bool {
19937 self.searchable
19938 }
19939
19940 fn open_proposed_changes_editor(
19941 &mut self,
19942 _: &OpenProposedChangesEditor,
19943 window: &mut Window,
19944 cx: &mut Context<Self>,
19945 ) {
19946 let Some(workspace) = self.workspace() else {
19947 cx.propagate();
19948 return;
19949 };
19950
19951 let selections = self.selections.all::<usize>(cx);
19952 let multi_buffer = self.buffer.read(cx);
19953 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19954 let mut new_selections_by_buffer = HashMap::default();
19955 for selection in selections {
19956 for (buffer, range, _) in
19957 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
19958 {
19959 let mut range = range.to_point(buffer);
19960 range.start.column = 0;
19961 range.end.column = buffer.line_len(range.end.row);
19962 new_selections_by_buffer
19963 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
19964 .or_insert(Vec::new())
19965 .push(range)
19966 }
19967 }
19968
19969 let proposed_changes_buffers = new_selections_by_buffer
19970 .into_iter()
19971 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
19972 .collect::<Vec<_>>();
19973 let proposed_changes_editor = cx.new(|cx| {
19974 ProposedChangesEditor::new(
19975 "Proposed changes",
19976 proposed_changes_buffers,
19977 self.project.clone(),
19978 window,
19979 cx,
19980 )
19981 });
19982
19983 window.defer(cx, move |window, cx| {
19984 workspace.update(cx, |workspace, cx| {
19985 workspace.active_pane().update(cx, |pane, cx| {
19986 pane.add_item(
19987 Box::new(proposed_changes_editor),
19988 true,
19989 true,
19990 None,
19991 window,
19992 cx,
19993 );
19994 });
19995 });
19996 });
19997 }
19998
19999 pub fn open_excerpts_in_split(
20000 &mut self,
20001 _: &OpenExcerptsSplit,
20002 window: &mut Window,
20003 cx: &mut Context<Self>,
20004 ) {
20005 self.open_excerpts_common(None, true, window, cx)
20006 }
20007
20008 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20009 self.open_excerpts_common(None, false, window, cx)
20010 }
20011
20012 fn open_excerpts_common(
20013 &mut self,
20014 jump_data: Option<JumpData>,
20015 split: bool,
20016 window: &mut Window,
20017 cx: &mut Context<Self>,
20018 ) {
20019 let Some(workspace) = self.workspace() else {
20020 cx.propagate();
20021 return;
20022 };
20023
20024 if self.buffer.read(cx).is_singleton() {
20025 cx.propagate();
20026 return;
20027 }
20028
20029 let mut new_selections_by_buffer = HashMap::default();
20030 match &jump_data {
20031 Some(JumpData::MultiBufferPoint {
20032 excerpt_id,
20033 position,
20034 anchor,
20035 line_offset_from_top,
20036 }) => {
20037 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20038 if let Some(buffer) = multi_buffer_snapshot
20039 .buffer_id_for_excerpt(*excerpt_id)
20040 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20041 {
20042 let buffer_snapshot = buffer.read(cx).snapshot();
20043 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20044 language::ToPoint::to_point(anchor, &buffer_snapshot)
20045 } else {
20046 buffer_snapshot.clip_point(*position, Bias::Left)
20047 };
20048 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20049 new_selections_by_buffer.insert(
20050 buffer,
20051 (
20052 vec![jump_to_offset..jump_to_offset],
20053 Some(*line_offset_from_top),
20054 ),
20055 );
20056 }
20057 }
20058 Some(JumpData::MultiBufferRow {
20059 row,
20060 line_offset_from_top,
20061 }) => {
20062 let point = MultiBufferPoint::new(row.0, 0);
20063 if let Some((buffer, buffer_point, _)) =
20064 self.buffer.read(cx).point_to_buffer_point(point, cx)
20065 {
20066 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20067 new_selections_by_buffer
20068 .entry(buffer)
20069 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20070 .0
20071 .push(buffer_offset..buffer_offset)
20072 }
20073 }
20074 None => {
20075 let selections = self.selections.all::<usize>(cx);
20076 let multi_buffer = self.buffer.read(cx);
20077 for selection in selections {
20078 for (snapshot, range, _, anchor) in multi_buffer
20079 .snapshot(cx)
20080 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20081 {
20082 if let Some(anchor) = anchor {
20083 // selection is in a deleted hunk
20084 let Some(buffer_id) = anchor.buffer_id else {
20085 continue;
20086 };
20087 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
20088 continue;
20089 };
20090 let offset = text::ToOffset::to_offset(
20091 &anchor.text_anchor,
20092 &buffer_handle.read(cx).snapshot(),
20093 );
20094 let range = offset..offset;
20095 new_selections_by_buffer
20096 .entry(buffer_handle)
20097 .or_insert((Vec::new(), None))
20098 .0
20099 .push(range)
20100 } else {
20101 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20102 else {
20103 continue;
20104 };
20105 new_selections_by_buffer
20106 .entry(buffer_handle)
20107 .or_insert((Vec::new(), None))
20108 .0
20109 .push(range)
20110 }
20111 }
20112 }
20113 }
20114 }
20115
20116 new_selections_by_buffer
20117 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20118
20119 if new_selections_by_buffer.is_empty() {
20120 return;
20121 }
20122
20123 // We defer the pane interaction because we ourselves are a workspace item
20124 // and activating a new item causes the pane to call a method on us reentrantly,
20125 // which panics if we're on the stack.
20126 window.defer(cx, move |window, cx| {
20127 workspace.update(cx, |workspace, cx| {
20128 let pane = if split {
20129 workspace.adjacent_pane(window, cx)
20130 } else {
20131 workspace.active_pane().clone()
20132 };
20133
20134 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20135 let editor = buffer
20136 .read(cx)
20137 .file()
20138 .is_none()
20139 .then(|| {
20140 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20141 // so `workspace.open_project_item` will never find them, always opening a new editor.
20142 // Instead, we try to activate the existing editor in the pane first.
20143 let (editor, pane_item_index) =
20144 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20145 let editor = item.downcast::<Editor>()?;
20146 let singleton_buffer =
20147 editor.read(cx).buffer().read(cx).as_singleton()?;
20148 if singleton_buffer == buffer {
20149 Some((editor, i))
20150 } else {
20151 None
20152 }
20153 })?;
20154 pane.update(cx, |pane, cx| {
20155 pane.activate_item(pane_item_index, true, true, window, cx)
20156 });
20157 Some(editor)
20158 })
20159 .flatten()
20160 .unwrap_or_else(|| {
20161 workspace.open_project_item::<Self>(
20162 pane.clone(),
20163 buffer,
20164 true,
20165 true,
20166 window,
20167 cx,
20168 )
20169 });
20170
20171 editor.update(cx, |editor, cx| {
20172 let autoscroll = match scroll_offset {
20173 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20174 None => Autoscroll::newest(),
20175 };
20176 let nav_history = editor.nav_history.take();
20177 editor.change_selections(
20178 SelectionEffects::scroll(autoscroll),
20179 window,
20180 cx,
20181 |s| {
20182 s.select_ranges(ranges);
20183 },
20184 );
20185 editor.nav_history = nav_history;
20186 });
20187 }
20188 })
20189 });
20190 }
20191
20192 // For now, don't allow opening excerpts in buffers that aren't backed by
20193 // regular project files.
20194 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20195 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
20196 }
20197
20198 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20199 let snapshot = self.buffer.read(cx).read(cx);
20200 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20201 Some(
20202 ranges
20203 .iter()
20204 .map(move |range| {
20205 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20206 })
20207 .collect(),
20208 )
20209 }
20210
20211 fn selection_replacement_ranges(
20212 &self,
20213 range: Range<OffsetUtf16>,
20214 cx: &mut App,
20215 ) -> Vec<Range<OffsetUtf16>> {
20216 let selections = self.selections.all::<OffsetUtf16>(cx);
20217 let newest_selection = selections
20218 .iter()
20219 .max_by_key(|selection| selection.id)
20220 .unwrap();
20221 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20222 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20223 let snapshot = self.buffer.read(cx).read(cx);
20224 selections
20225 .into_iter()
20226 .map(|mut selection| {
20227 selection.start.0 =
20228 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20229 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20230 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20231 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20232 })
20233 .collect()
20234 }
20235
20236 fn report_editor_event(
20237 &self,
20238 event_type: &'static str,
20239 file_extension: Option<String>,
20240 cx: &App,
20241 ) {
20242 if cfg!(any(test, feature = "test-support")) {
20243 return;
20244 }
20245
20246 let Some(project) = &self.project else { return };
20247
20248 // If None, we are in a file without an extension
20249 let file = self
20250 .buffer
20251 .read(cx)
20252 .as_singleton()
20253 .and_then(|b| b.read(cx).file());
20254 let file_extension = file_extension.or(file
20255 .as_ref()
20256 .and_then(|file| Path::new(file.file_name(cx)).extension())
20257 .and_then(|e| e.to_str())
20258 .map(|a| a.to_string()));
20259
20260 let vim_mode = vim_enabled(cx);
20261
20262 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20263 let copilot_enabled = edit_predictions_provider
20264 == language::language_settings::EditPredictionProvider::Copilot;
20265 let copilot_enabled_for_language = self
20266 .buffer
20267 .read(cx)
20268 .language_settings(cx)
20269 .show_edit_predictions;
20270
20271 let project = project.read(cx);
20272 telemetry::event!(
20273 event_type,
20274 file_extension,
20275 vim_mode,
20276 copilot_enabled,
20277 copilot_enabled_for_language,
20278 edit_predictions_provider,
20279 is_via_ssh = project.is_via_ssh(),
20280 );
20281 }
20282
20283 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20284 /// with each line being an array of {text, highlight} objects.
20285 fn copy_highlight_json(
20286 &mut self,
20287 _: &CopyHighlightJson,
20288 window: &mut Window,
20289 cx: &mut Context<Self>,
20290 ) {
20291 #[derive(Serialize)]
20292 struct Chunk<'a> {
20293 text: String,
20294 highlight: Option<&'a str>,
20295 }
20296
20297 let snapshot = self.buffer.read(cx).snapshot(cx);
20298 let range = self
20299 .selected_text_range(false, window, cx)
20300 .and_then(|selection| {
20301 if selection.range.is_empty() {
20302 None
20303 } else {
20304 Some(selection.range)
20305 }
20306 })
20307 .unwrap_or_else(|| 0..snapshot.len());
20308
20309 let chunks = snapshot.chunks(range, true);
20310 let mut lines = Vec::new();
20311 let mut line: VecDeque<Chunk> = VecDeque::new();
20312
20313 let Some(style) = self.style.as_ref() else {
20314 return;
20315 };
20316
20317 for chunk in chunks {
20318 let highlight = chunk
20319 .syntax_highlight_id
20320 .and_then(|id| id.name(&style.syntax));
20321 let mut chunk_lines = chunk.text.split('\n').peekable();
20322 while let Some(text) = chunk_lines.next() {
20323 let mut merged_with_last_token = false;
20324 if let Some(last_token) = line.back_mut() {
20325 if last_token.highlight == highlight {
20326 last_token.text.push_str(text);
20327 merged_with_last_token = true;
20328 }
20329 }
20330
20331 if !merged_with_last_token {
20332 line.push_back(Chunk {
20333 text: text.into(),
20334 highlight,
20335 });
20336 }
20337
20338 if chunk_lines.peek().is_some() {
20339 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20340 line.pop_front();
20341 }
20342 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20343 line.pop_back();
20344 }
20345
20346 lines.push(mem::take(&mut line));
20347 }
20348 }
20349 }
20350
20351 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20352 return;
20353 };
20354 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20355 }
20356
20357 pub fn open_context_menu(
20358 &mut self,
20359 _: &OpenContextMenu,
20360 window: &mut Window,
20361 cx: &mut Context<Self>,
20362 ) {
20363 self.request_autoscroll(Autoscroll::newest(), cx);
20364 let position = self.selections.newest_display(cx).start;
20365 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20366 }
20367
20368 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20369 &self.inlay_hint_cache
20370 }
20371
20372 pub fn replay_insert_event(
20373 &mut self,
20374 text: &str,
20375 relative_utf16_range: Option<Range<isize>>,
20376 window: &mut Window,
20377 cx: &mut Context<Self>,
20378 ) {
20379 if !self.input_enabled {
20380 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20381 return;
20382 }
20383 if let Some(relative_utf16_range) = relative_utf16_range {
20384 let selections = self.selections.all::<OffsetUtf16>(cx);
20385 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20386 let new_ranges = selections.into_iter().map(|range| {
20387 let start = OffsetUtf16(
20388 range
20389 .head()
20390 .0
20391 .saturating_add_signed(relative_utf16_range.start),
20392 );
20393 let end = OffsetUtf16(
20394 range
20395 .head()
20396 .0
20397 .saturating_add_signed(relative_utf16_range.end),
20398 );
20399 start..end
20400 });
20401 s.select_ranges(new_ranges);
20402 });
20403 }
20404
20405 self.handle_input(text, window, cx);
20406 }
20407
20408 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20409 let Some(provider) = self.semantics_provider.as_ref() else {
20410 return false;
20411 };
20412
20413 let mut supports = false;
20414 self.buffer().update(cx, |this, cx| {
20415 this.for_each_buffer(|buffer| {
20416 supports |= provider.supports_inlay_hints(buffer, cx);
20417 });
20418 });
20419
20420 supports
20421 }
20422
20423 pub fn is_focused(&self, window: &Window) -> bool {
20424 self.focus_handle.is_focused(window)
20425 }
20426
20427 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20428 cx.emit(EditorEvent::Focused);
20429
20430 if let Some(descendant) = self
20431 .last_focused_descendant
20432 .take()
20433 .and_then(|descendant| descendant.upgrade())
20434 {
20435 window.focus(&descendant);
20436 } else {
20437 if let Some(blame) = self.blame.as_ref() {
20438 blame.update(cx, GitBlame::focus)
20439 }
20440
20441 self.blink_manager.update(cx, BlinkManager::enable);
20442 self.show_cursor_names(window, cx);
20443 self.buffer.update(cx, |buffer, cx| {
20444 buffer.finalize_last_transaction(cx);
20445 if self.leader_id.is_none() {
20446 buffer.set_active_selections(
20447 &self.selections.disjoint_anchors(),
20448 self.selections.line_mode,
20449 self.cursor_shape,
20450 cx,
20451 );
20452 }
20453 });
20454 }
20455 }
20456
20457 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20458 cx.emit(EditorEvent::FocusedIn)
20459 }
20460
20461 fn handle_focus_out(
20462 &mut self,
20463 event: FocusOutEvent,
20464 _window: &mut Window,
20465 cx: &mut Context<Self>,
20466 ) {
20467 if event.blurred != self.focus_handle {
20468 self.last_focused_descendant = Some(event.blurred);
20469 }
20470 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20471 }
20472
20473 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20474 self.blink_manager.update(cx, BlinkManager::disable);
20475 self.buffer
20476 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20477
20478 if let Some(blame) = self.blame.as_ref() {
20479 blame.update(cx, GitBlame::blur)
20480 }
20481 if !self.hover_state.focused(window, cx) {
20482 hide_hover(self, cx);
20483 }
20484 if !self
20485 .context_menu
20486 .borrow()
20487 .as_ref()
20488 .is_some_and(|context_menu| context_menu.focused(window, cx))
20489 {
20490 self.hide_context_menu(window, cx);
20491 }
20492 self.discard_inline_completion(false, cx);
20493 cx.emit(EditorEvent::Blurred);
20494 cx.notify();
20495 }
20496
20497 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20498 let mut pending: String = window
20499 .pending_input_keystrokes()
20500 .into_iter()
20501 .flatten()
20502 .filter_map(|keystroke| {
20503 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20504 keystroke.key_char.clone()
20505 } else {
20506 None
20507 }
20508 })
20509 .collect();
20510
20511 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20512 pending = "".to_string();
20513 }
20514
20515 let existing_pending = self
20516 .text_highlights::<PendingInput>(cx)
20517 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20518 if existing_pending.is_none() && pending.is_empty() {
20519 return;
20520 }
20521 let transaction =
20522 self.transact(window, cx, |this, window, cx| {
20523 let selections = this.selections.all::<usize>(cx);
20524 let edits = selections
20525 .iter()
20526 .map(|selection| (selection.end..selection.end, pending.clone()));
20527 this.edit(edits, cx);
20528 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20529 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20530 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20531 }));
20532 });
20533 if let Some(existing_ranges) = existing_pending {
20534 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20535 this.edit(edits, cx);
20536 }
20537 });
20538
20539 let snapshot = self.snapshot(window, cx);
20540 let ranges = self
20541 .selections
20542 .all::<usize>(cx)
20543 .into_iter()
20544 .map(|selection| {
20545 snapshot.buffer_snapshot.anchor_after(selection.end)
20546 ..snapshot
20547 .buffer_snapshot
20548 .anchor_before(selection.end + pending.len())
20549 })
20550 .collect();
20551
20552 if pending.is_empty() {
20553 self.clear_highlights::<PendingInput>(cx);
20554 } else {
20555 self.highlight_text::<PendingInput>(
20556 ranges,
20557 HighlightStyle {
20558 underline: Some(UnderlineStyle {
20559 thickness: px(1.),
20560 color: None,
20561 wavy: false,
20562 }),
20563 ..Default::default()
20564 },
20565 cx,
20566 );
20567 }
20568
20569 self.ime_transaction = self.ime_transaction.or(transaction);
20570 if let Some(transaction) = self.ime_transaction {
20571 self.buffer.update(cx, |buffer, cx| {
20572 buffer.group_until_transaction(transaction, cx);
20573 });
20574 }
20575
20576 if self.text_highlights::<PendingInput>(cx).is_none() {
20577 self.ime_transaction.take();
20578 }
20579 }
20580
20581 pub fn register_action_renderer(
20582 &mut self,
20583 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20584 ) -> Subscription {
20585 let id = self.next_editor_action_id.post_inc();
20586 self.editor_actions
20587 .borrow_mut()
20588 .insert(id, Box::new(listener));
20589
20590 let editor_actions = self.editor_actions.clone();
20591 Subscription::new(move || {
20592 editor_actions.borrow_mut().remove(&id);
20593 })
20594 }
20595
20596 pub fn register_action<A: Action>(
20597 &mut self,
20598 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20599 ) -> Subscription {
20600 let id = self.next_editor_action_id.post_inc();
20601 let listener = Arc::new(listener);
20602 self.editor_actions.borrow_mut().insert(
20603 id,
20604 Box::new(move |_, window, _| {
20605 let listener = listener.clone();
20606 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20607 let action = action.downcast_ref().unwrap();
20608 if phase == DispatchPhase::Bubble {
20609 listener(action, window, cx)
20610 }
20611 })
20612 }),
20613 );
20614
20615 let editor_actions = self.editor_actions.clone();
20616 Subscription::new(move || {
20617 editor_actions.borrow_mut().remove(&id);
20618 })
20619 }
20620
20621 pub fn file_header_size(&self) -> u32 {
20622 FILE_HEADER_HEIGHT
20623 }
20624
20625 pub fn restore(
20626 &mut self,
20627 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20628 window: &mut Window,
20629 cx: &mut Context<Self>,
20630 ) {
20631 let workspace = self.workspace();
20632 let project = self.project.as_ref();
20633 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20634 let mut tasks = Vec::new();
20635 for (buffer_id, changes) in revert_changes {
20636 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20637 buffer.update(cx, |buffer, cx| {
20638 buffer.edit(
20639 changes
20640 .into_iter()
20641 .map(|(range, text)| (range, text.to_string())),
20642 None,
20643 cx,
20644 );
20645 });
20646
20647 if let Some(project) =
20648 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20649 {
20650 project.update(cx, |project, cx| {
20651 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20652 })
20653 }
20654 }
20655 }
20656 tasks
20657 });
20658 cx.spawn_in(window, async move |_, cx| {
20659 for (buffer, task) in save_tasks {
20660 let result = task.await;
20661 if result.is_err() {
20662 let Some(path) = buffer
20663 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20664 .ok()
20665 else {
20666 continue;
20667 };
20668 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20669 let Some(task) = cx
20670 .update_window_entity(&workspace, |workspace, window, cx| {
20671 workspace
20672 .open_path_preview(path, None, false, false, false, window, cx)
20673 })
20674 .ok()
20675 else {
20676 continue;
20677 };
20678 task.await.log_err();
20679 }
20680 }
20681 }
20682 })
20683 .detach();
20684 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20685 selections.refresh()
20686 });
20687 }
20688
20689 pub fn to_pixel_point(
20690 &self,
20691 source: multi_buffer::Anchor,
20692 editor_snapshot: &EditorSnapshot,
20693 window: &mut Window,
20694 ) -> Option<gpui::Point<Pixels>> {
20695 let source_point = source.to_display_point(editor_snapshot);
20696 self.display_to_pixel_point(source_point, editor_snapshot, window)
20697 }
20698
20699 pub fn display_to_pixel_point(
20700 &self,
20701 source: DisplayPoint,
20702 editor_snapshot: &EditorSnapshot,
20703 window: &mut Window,
20704 ) -> Option<gpui::Point<Pixels>> {
20705 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20706 let text_layout_details = self.text_layout_details(window);
20707 let scroll_top = text_layout_details
20708 .scroll_anchor
20709 .scroll_position(editor_snapshot)
20710 .y;
20711
20712 if source.row().as_f32() < scroll_top.floor() {
20713 return None;
20714 }
20715 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20716 let source_y = line_height * (source.row().as_f32() - scroll_top);
20717 Some(gpui::Point::new(source_x, source_y))
20718 }
20719
20720 pub fn has_visible_completions_menu(&self) -> bool {
20721 !self.edit_prediction_preview_is_active()
20722 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20723 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20724 })
20725 }
20726
20727 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20728 if self.mode.is_minimap() {
20729 return;
20730 }
20731 self.addons
20732 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20733 }
20734
20735 pub fn unregister_addon<T: Addon>(&mut self) {
20736 self.addons.remove(&std::any::TypeId::of::<T>());
20737 }
20738
20739 pub fn addon<T: Addon>(&self) -> Option<&T> {
20740 let type_id = std::any::TypeId::of::<T>();
20741 self.addons
20742 .get(&type_id)
20743 .and_then(|item| item.to_any().downcast_ref::<T>())
20744 }
20745
20746 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20747 let type_id = std::any::TypeId::of::<T>();
20748 self.addons
20749 .get_mut(&type_id)
20750 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20751 }
20752
20753 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
20754 let text_layout_details = self.text_layout_details(window);
20755 let style = &text_layout_details.editor_style;
20756 let font_id = window.text_system().resolve_font(&style.text.font());
20757 let font_size = style.text.font_size.to_pixels(window.rem_size());
20758 let line_height = style.text.line_height_in_pixels(window.rem_size());
20759 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20760 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
20761
20762 CharacterDimensions {
20763 em_width,
20764 em_advance,
20765 line_height,
20766 }
20767 }
20768
20769 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20770 self.load_diff_task.clone()
20771 }
20772
20773 fn read_metadata_from_db(
20774 &mut self,
20775 item_id: u64,
20776 workspace_id: WorkspaceId,
20777 window: &mut Window,
20778 cx: &mut Context<Editor>,
20779 ) {
20780 if self.is_singleton(cx)
20781 && !self.mode.is_minimap()
20782 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20783 {
20784 let buffer_snapshot = OnceCell::new();
20785
20786 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20787 if !folds.is_empty() {
20788 let snapshot =
20789 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20790 self.fold_ranges(
20791 folds
20792 .into_iter()
20793 .map(|(start, end)| {
20794 snapshot.clip_offset(start, Bias::Left)
20795 ..snapshot.clip_offset(end, Bias::Right)
20796 })
20797 .collect(),
20798 false,
20799 window,
20800 cx,
20801 );
20802 }
20803 }
20804
20805 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20806 if !selections.is_empty() {
20807 let snapshot =
20808 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20809 // skip adding the initial selection to selection history
20810 self.selection_history.mode = SelectionHistoryMode::Skipping;
20811 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20812 s.select_ranges(selections.into_iter().map(|(start, end)| {
20813 snapshot.clip_offset(start, Bias::Left)
20814 ..snapshot.clip_offset(end, Bias::Right)
20815 }));
20816 });
20817 self.selection_history.mode = SelectionHistoryMode::Normal;
20818 }
20819 };
20820 }
20821
20822 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20823 }
20824
20825 fn update_lsp_data(
20826 &mut self,
20827 ignore_cache: bool,
20828 for_buffer: Option<BufferId>,
20829 window: &mut Window,
20830 cx: &mut Context<'_, Self>,
20831 ) {
20832 self.pull_diagnostics(for_buffer, window, cx);
20833 self.refresh_colors(ignore_cache, for_buffer, window, cx);
20834 }
20835}
20836
20837fn vim_enabled(cx: &App) -> bool {
20838 cx.global::<SettingsStore>()
20839 .raw_user_settings()
20840 .get("vim_mode")
20841 == Some(&serde_json::Value::Bool(true))
20842}
20843
20844fn process_completion_for_edit(
20845 completion: &Completion,
20846 intent: CompletionIntent,
20847 buffer: &Entity<Buffer>,
20848 cursor_position: &text::Anchor,
20849 cx: &mut Context<Editor>,
20850) -> CompletionEdit {
20851 let buffer = buffer.read(cx);
20852 let buffer_snapshot = buffer.snapshot();
20853 let (snippet, new_text) = if completion.is_snippet() {
20854 // Workaround for typescript language server issues so that methods don't expand within
20855 // strings and functions with type expressions. The previous point is used because the query
20856 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
20857 let mut snippet_source = completion.new_text.clone();
20858 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
20859 previous_point.column = previous_point.column.saturating_sub(1);
20860 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
20861 if scope.prefers_label_for_snippet_in_completion() {
20862 if let Some(label) = completion.label() {
20863 if matches!(
20864 completion.kind(),
20865 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20866 ) {
20867 snippet_source = label;
20868 }
20869 }
20870 }
20871 }
20872 match Snippet::parse(&snippet_source).log_err() {
20873 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20874 None => (None, completion.new_text.clone()),
20875 }
20876 } else {
20877 (None, completion.new_text.clone())
20878 };
20879
20880 let mut range_to_replace = {
20881 let replace_range = &completion.replace_range;
20882 if let CompletionSource::Lsp {
20883 insert_range: Some(insert_range),
20884 ..
20885 } = &completion.source
20886 {
20887 debug_assert_eq!(
20888 insert_range.start, replace_range.start,
20889 "insert_range and replace_range should start at the same position"
20890 );
20891 debug_assert!(
20892 insert_range
20893 .start
20894 .cmp(&cursor_position, &buffer_snapshot)
20895 .is_le(),
20896 "insert_range should start before or at cursor position"
20897 );
20898 debug_assert!(
20899 replace_range
20900 .start
20901 .cmp(&cursor_position, &buffer_snapshot)
20902 .is_le(),
20903 "replace_range should start before or at cursor position"
20904 );
20905 debug_assert!(
20906 insert_range
20907 .end
20908 .cmp(&cursor_position, &buffer_snapshot)
20909 .is_le(),
20910 "insert_range should end before or at cursor position"
20911 );
20912
20913 let should_replace = match intent {
20914 CompletionIntent::CompleteWithInsert => false,
20915 CompletionIntent::CompleteWithReplace => true,
20916 CompletionIntent::Complete | CompletionIntent::Compose => {
20917 let insert_mode =
20918 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
20919 .completions
20920 .lsp_insert_mode;
20921 match insert_mode {
20922 LspInsertMode::Insert => false,
20923 LspInsertMode::Replace => true,
20924 LspInsertMode::ReplaceSubsequence => {
20925 let mut text_to_replace = buffer.chars_for_range(
20926 buffer.anchor_before(replace_range.start)
20927 ..buffer.anchor_after(replace_range.end),
20928 );
20929 let mut current_needle = text_to_replace.next();
20930 for haystack_ch in completion.label.text.chars() {
20931 if let Some(needle_ch) = current_needle {
20932 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
20933 current_needle = text_to_replace.next();
20934 }
20935 }
20936 }
20937 current_needle.is_none()
20938 }
20939 LspInsertMode::ReplaceSuffix => {
20940 if replace_range
20941 .end
20942 .cmp(&cursor_position, &buffer_snapshot)
20943 .is_gt()
20944 {
20945 let range_after_cursor = *cursor_position..replace_range.end;
20946 let text_after_cursor = buffer
20947 .text_for_range(
20948 buffer.anchor_before(range_after_cursor.start)
20949 ..buffer.anchor_after(range_after_cursor.end),
20950 )
20951 .collect::<String>()
20952 .to_ascii_lowercase();
20953 completion
20954 .label
20955 .text
20956 .to_ascii_lowercase()
20957 .ends_with(&text_after_cursor)
20958 } else {
20959 true
20960 }
20961 }
20962 }
20963 }
20964 };
20965
20966 if should_replace {
20967 replace_range.clone()
20968 } else {
20969 insert_range.clone()
20970 }
20971 } else {
20972 replace_range.clone()
20973 }
20974 };
20975
20976 if range_to_replace
20977 .end
20978 .cmp(&cursor_position, &buffer_snapshot)
20979 .is_lt()
20980 {
20981 range_to_replace.end = *cursor_position;
20982 }
20983
20984 CompletionEdit {
20985 new_text,
20986 replace_range: range_to_replace.to_offset(&buffer),
20987 snippet,
20988 }
20989}
20990
20991struct CompletionEdit {
20992 new_text: String,
20993 replace_range: Range<usize>,
20994 snippet: Option<Snippet>,
20995}
20996
20997fn insert_extra_newline_brackets(
20998 buffer: &MultiBufferSnapshot,
20999 range: Range<usize>,
21000 language: &language::LanguageScope,
21001) -> bool {
21002 let leading_whitespace_len = buffer
21003 .reversed_chars_at(range.start)
21004 .take_while(|c| c.is_whitespace() && *c != '\n')
21005 .map(|c| c.len_utf8())
21006 .sum::<usize>();
21007 let trailing_whitespace_len = buffer
21008 .chars_at(range.end)
21009 .take_while(|c| c.is_whitespace() && *c != '\n')
21010 .map(|c| c.len_utf8())
21011 .sum::<usize>();
21012 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21013
21014 language.brackets().any(|(pair, enabled)| {
21015 let pair_start = pair.start.trim_end();
21016 let pair_end = pair.end.trim_start();
21017
21018 enabled
21019 && pair.newline
21020 && buffer.contains_str_at(range.end, pair_end)
21021 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21022 })
21023}
21024
21025fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21026 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21027 [(buffer, range, _)] => (*buffer, range.clone()),
21028 _ => return false,
21029 };
21030 let pair = {
21031 let mut result: Option<BracketMatch> = None;
21032
21033 for pair in buffer
21034 .all_bracket_ranges(range.clone())
21035 .filter(move |pair| {
21036 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21037 })
21038 {
21039 let len = pair.close_range.end - pair.open_range.start;
21040
21041 if let Some(existing) = &result {
21042 let existing_len = existing.close_range.end - existing.open_range.start;
21043 if len > existing_len {
21044 continue;
21045 }
21046 }
21047
21048 result = Some(pair);
21049 }
21050
21051 result
21052 };
21053 let Some(pair) = pair else {
21054 return false;
21055 };
21056 pair.newline_only
21057 && buffer
21058 .chars_for_range(pair.open_range.end..range.start)
21059 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21060 .all(|c| c.is_whitespace() && c != '\n')
21061}
21062
21063fn update_uncommitted_diff_for_buffer(
21064 editor: Entity<Editor>,
21065 project: &Entity<Project>,
21066 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21067 buffer: Entity<MultiBuffer>,
21068 cx: &mut App,
21069) -> Task<()> {
21070 let mut tasks = Vec::new();
21071 project.update(cx, |project, cx| {
21072 for buffer in buffers {
21073 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21074 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21075 }
21076 }
21077 });
21078 cx.spawn(async move |cx| {
21079 let diffs = future::join_all(tasks).await;
21080 if editor
21081 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21082 .unwrap_or(false)
21083 {
21084 return;
21085 }
21086
21087 buffer
21088 .update(cx, |buffer, cx| {
21089 for diff in diffs.into_iter().flatten() {
21090 buffer.add_diff(diff, cx);
21091 }
21092 })
21093 .ok();
21094 })
21095}
21096
21097fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21098 let tab_size = tab_size.get() as usize;
21099 let mut width = offset;
21100
21101 for ch in text.chars() {
21102 width += if ch == '\t' {
21103 tab_size - (width % tab_size)
21104 } else {
21105 1
21106 };
21107 }
21108
21109 width - offset
21110}
21111
21112#[cfg(test)]
21113mod tests {
21114 use super::*;
21115
21116 #[test]
21117 fn test_string_size_with_expanded_tabs() {
21118 let nz = |val| NonZeroU32::new(val).unwrap();
21119 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21120 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21121 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21122 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21123 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21124 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21125 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21126 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21127 }
21128}
21129
21130/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21131struct WordBreakingTokenizer<'a> {
21132 input: &'a str,
21133}
21134
21135impl<'a> WordBreakingTokenizer<'a> {
21136 fn new(input: &'a str) -> Self {
21137 Self { input }
21138 }
21139}
21140
21141fn is_char_ideographic(ch: char) -> bool {
21142 use unicode_script::Script::*;
21143 use unicode_script::UnicodeScript;
21144 matches!(ch.script(), Han | Tangut | Yi)
21145}
21146
21147fn is_grapheme_ideographic(text: &str) -> bool {
21148 text.chars().any(is_char_ideographic)
21149}
21150
21151fn is_grapheme_whitespace(text: &str) -> bool {
21152 text.chars().any(|x| x.is_whitespace())
21153}
21154
21155fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21156 text.chars().next().map_or(false, |ch| {
21157 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
21158 })
21159}
21160
21161#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21162enum WordBreakToken<'a> {
21163 Word { token: &'a str, grapheme_len: usize },
21164 InlineWhitespace { token: &'a str, grapheme_len: usize },
21165 Newline,
21166}
21167
21168impl<'a> Iterator for WordBreakingTokenizer<'a> {
21169 /// Yields a span, the count of graphemes in the token, and whether it was
21170 /// whitespace. Note that it also breaks at word boundaries.
21171 type Item = WordBreakToken<'a>;
21172
21173 fn next(&mut self) -> Option<Self::Item> {
21174 use unicode_segmentation::UnicodeSegmentation;
21175 if self.input.is_empty() {
21176 return None;
21177 }
21178
21179 let mut iter = self.input.graphemes(true).peekable();
21180 let mut offset = 0;
21181 let mut grapheme_len = 0;
21182 if let Some(first_grapheme) = iter.next() {
21183 let is_newline = first_grapheme == "\n";
21184 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21185 offset += first_grapheme.len();
21186 grapheme_len += 1;
21187 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21188 if let Some(grapheme) = iter.peek().copied() {
21189 if should_stay_with_preceding_ideograph(grapheme) {
21190 offset += grapheme.len();
21191 grapheme_len += 1;
21192 }
21193 }
21194 } else {
21195 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21196 let mut next_word_bound = words.peek().copied();
21197 if next_word_bound.map_or(false, |(i, _)| i == 0) {
21198 next_word_bound = words.next();
21199 }
21200 while let Some(grapheme) = iter.peek().copied() {
21201 if next_word_bound.map_or(false, |(i, _)| i == offset) {
21202 break;
21203 };
21204 if is_grapheme_whitespace(grapheme) != is_whitespace
21205 || (grapheme == "\n") != is_newline
21206 {
21207 break;
21208 };
21209 offset += grapheme.len();
21210 grapheme_len += 1;
21211 iter.next();
21212 }
21213 }
21214 let token = &self.input[..offset];
21215 self.input = &self.input[offset..];
21216 if token == "\n" {
21217 Some(WordBreakToken::Newline)
21218 } else if is_whitespace {
21219 Some(WordBreakToken::InlineWhitespace {
21220 token,
21221 grapheme_len,
21222 })
21223 } else {
21224 Some(WordBreakToken::Word {
21225 token,
21226 grapheme_len,
21227 })
21228 }
21229 } else {
21230 None
21231 }
21232 }
21233}
21234
21235#[test]
21236fn test_word_breaking_tokenizer() {
21237 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21238 ("", &[]),
21239 (" ", &[whitespace(" ", 2)]),
21240 ("Ʒ", &[word("Ʒ", 1)]),
21241 ("Ǽ", &[word("Ǽ", 1)]),
21242 ("⋑", &[word("⋑", 1)]),
21243 ("⋑⋑", &[word("⋑⋑", 2)]),
21244 (
21245 "原理,进而",
21246 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21247 ),
21248 (
21249 "hello world",
21250 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21251 ),
21252 (
21253 "hello, world",
21254 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21255 ),
21256 (
21257 " hello world",
21258 &[
21259 whitespace(" ", 2),
21260 word("hello", 5),
21261 whitespace(" ", 1),
21262 word("world", 5),
21263 ],
21264 ),
21265 (
21266 "这是什么 \n 钢笔",
21267 &[
21268 word("这", 1),
21269 word("是", 1),
21270 word("什", 1),
21271 word("么", 1),
21272 whitespace(" ", 1),
21273 newline(),
21274 whitespace(" ", 1),
21275 word("钢", 1),
21276 word("笔", 1),
21277 ],
21278 ),
21279 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21280 ];
21281
21282 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21283 WordBreakToken::Word {
21284 token,
21285 grapheme_len,
21286 }
21287 }
21288
21289 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21290 WordBreakToken::InlineWhitespace {
21291 token,
21292 grapheme_len,
21293 }
21294 }
21295
21296 fn newline() -> WordBreakToken<'static> {
21297 WordBreakToken::Newline
21298 }
21299
21300 for (input, result) in tests {
21301 assert_eq!(
21302 WordBreakingTokenizer::new(input)
21303 .collect::<Vec<_>>()
21304 .as_slice(),
21305 *result,
21306 );
21307 }
21308}
21309
21310fn wrap_with_prefix(
21311 first_line_prefix: String,
21312 subsequent_lines_prefix: String,
21313 unwrapped_text: String,
21314 wrap_column: usize,
21315 tab_size: NonZeroU32,
21316 preserve_existing_whitespace: bool,
21317) -> String {
21318 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21319 let subsequent_lines_prefix_len =
21320 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21321 let mut wrapped_text = String::new();
21322 let mut current_line = first_line_prefix.clone();
21323 let mut is_first_line = true;
21324
21325 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21326 let mut current_line_len = first_line_prefix_len;
21327 let mut in_whitespace = false;
21328 for token in tokenizer {
21329 let have_preceding_whitespace = in_whitespace;
21330 match token {
21331 WordBreakToken::Word {
21332 token,
21333 grapheme_len,
21334 } => {
21335 in_whitespace = false;
21336 let current_prefix_len = if is_first_line {
21337 first_line_prefix_len
21338 } else {
21339 subsequent_lines_prefix_len
21340 };
21341 if current_line_len + grapheme_len > wrap_column
21342 && current_line_len != current_prefix_len
21343 {
21344 wrapped_text.push_str(current_line.trim_end());
21345 wrapped_text.push('\n');
21346 is_first_line = false;
21347 current_line = subsequent_lines_prefix.clone();
21348 current_line_len = subsequent_lines_prefix_len;
21349 }
21350 current_line.push_str(token);
21351 current_line_len += grapheme_len;
21352 }
21353 WordBreakToken::InlineWhitespace {
21354 mut token,
21355 mut grapheme_len,
21356 } => {
21357 in_whitespace = true;
21358 if have_preceding_whitespace && !preserve_existing_whitespace {
21359 continue;
21360 }
21361 if !preserve_existing_whitespace {
21362 token = " ";
21363 grapheme_len = 1;
21364 }
21365 let current_prefix_len = if is_first_line {
21366 first_line_prefix_len
21367 } else {
21368 subsequent_lines_prefix_len
21369 };
21370 if current_line_len + grapheme_len > wrap_column {
21371 wrapped_text.push_str(current_line.trim_end());
21372 wrapped_text.push('\n');
21373 is_first_line = false;
21374 current_line = subsequent_lines_prefix.clone();
21375 current_line_len = subsequent_lines_prefix_len;
21376 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21377 current_line.push_str(token);
21378 current_line_len += grapheme_len;
21379 }
21380 }
21381 WordBreakToken::Newline => {
21382 in_whitespace = true;
21383 let current_prefix_len = if is_first_line {
21384 first_line_prefix_len
21385 } else {
21386 subsequent_lines_prefix_len
21387 };
21388 if preserve_existing_whitespace {
21389 wrapped_text.push_str(current_line.trim_end());
21390 wrapped_text.push('\n');
21391 is_first_line = false;
21392 current_line = subsequent_lines_prefix.clone();
21393 current_line_len = subsequent_lines_prefix_len;
21394 } else if have_preceding_whitespace {
21395 continue;
21396 } else if current_line_len + 1 > wrap_column
21397 && current_line_len != current_prefix_len
21398 {
21399 wrapped_text.push_str(current_line.trim_end());
21400 wrapped_text.push('\n');
21401 is_first_line = false;
21402 current_line = subsequent_lines_prefix.clone();
21403 current_line_len = subsequent_lines_prefix_len;
21404 } else if current_line_len != current_prefix_len {
21405 current_line.push(' ');
21406 current_line_len += 1;
21407 }
21408 }
21409 }
21410 }
21411
21412 if !current_line.is_empty() {
21413 wrapped_text.push_str(¤t_line);
21414 }
21415 wrapped_text
21416}
21417
21418#[test]
21419fn test_wrap_with_prefix() {
21420 assert_eq!(
21421 wrap_with_prefix(
21422 "# ".to_string(),
21423 "# ".to_string(),
21424 "abcdefg".to_string(),
21425 4,
21426 NonZeroU32::new(4).unwrap(),
21427 false,
21428 ),
21429 "# abcdefg"
21430 );
21431 assert_eq!(
21432 wrap_with_prefix(
21433 "".to_string(),
21434 "".to_string(),
21435 "\thello world".to_string(),
21436 8,
21437 NonZeroU32::new(4).unwrap(),
21438 false,
21439 ),
21440 "hello\nworld"
21441 );
21442 assert_eq!(
21443 wrap_with_prefix(
21444 "// ".to_string(),
21445 "// ".to_string(),
21446 "xx \nyy zz aa bb cc".to_string(),
21447 12,
21448 NonZeroU32::new(4).unwrap(),
21449 false,
21450 ),
21451 "// xx yy zz\n// aa bb cc"
21452 );
21453 assert_eq!(
21454 wrap_with_prefix(
21455 String::new(),
21456 String::new(),
21457 "这是什么 \n 钢笔".to_string(),
21458 3,
21459 NonZeroU32::new(4).unwrap(),
21460 false,
21461 ),
21462 "这是什\n么 钢\n笔"
21463 );
21464}
21465
21466pub trait CollaborationHub {
21467 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21468 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21469 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21470}
21471
21472impl CollaborationHub for Entity<Project> {
21473 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21474 self.read(cx).collaborators()
21475 }
21476
21477 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21478 self.read(cx).user_store().read(cx).participant_indices()
21479 }
21480
21481 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21482 let this = self.read(cx);
21483 let user_ids = this.collaborators().values().map(|c| c.user_id);
21484 this.user_store().read(cx).participant_names(user_ids, cx)
21485 }
21486}
21487
21488pub trait SemanticsProvider {
21489 fn hover(
21490 &self,
21491 buffer: &Entity<Buffer>,
21492 position: text::Anchor,
21493 cx: &mut App,
21494 ) -> Option<Task<Vec<project::Hover>>>;
21495
21496 fn inline_values(
21497 &self,
21498 buffer_handle: Entity<Buffer>,
21499 range: Range<text::Anchor>,
21500 cx: &mut App,
21501 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21502
21503 fn inlay_hints(
21504 &self,
21505 buffer_handle: Entity<Buffer>,
21506 range: Range<text::Anchor>,
21507 cx: &mut App,
21508 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21509
21510 fn resolve_inlay_hint(
21511 &self,
21512 hint: InlayHint,
21513 buffer_handle: Entity<Buffer>,
21514 server_id: LanguageServerId,
21515 cx: &mut App,
21516 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21517
21518 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21519
21520 fn document_highlights(
21521 &self,
21522 buffer: &Entity<Buffer>,
21523 position: text::Anchor,
21524 cx: &mut App,
21525 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21526
21527 fn definitions(
21528 &self,
21529 buffer: &Entity<Buffer>,
21530 position: text::Anchor,
21531 kind: GotoDefinitionKind,
21532 cx: &mut App,
21533 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21534
21535 fn range_for_rename(
21536 &self,
21537 buffer: &Entity<Buffer>,
21538 position: text::Anchor,
21539 cx: &mut App,
21540 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21541
21542 fn perform_rename(
21543 &self,
21544 buffer: &Entity<Buffer>,
21545 position: text::Anchor,
21546 new_name: String,
21547 cx: &mut App,
21548 ) -> Option<Task<Result<ProjectTransaction>>>;
21549}
21550
21551pub trait CompletionProvider {
21552 fn completions(
21553 &self,
21554 excerpt_id: ExcerptId,
21555 buffer: &Entity<Buffer>,
21556 buffer_position: text::Anchor,
21557 trigger: CompletionContext,
21558 window: &mut Window,
21559 cx: &mut Context<Editor>,
21560 ) -> Task<Result<Vec<CompletionResponse>>>;
21561
21562 fn resolve_completions(
21563 &self,
21564 _buffer: Entity<Buffer>,
21565 _completion_indices: Vec<usize>,
21566 _completions: Rc<RefCell<Box<[Completion]>>>,
21567 _cx: &mut Context<Editor>,
21568 ) -> Task<Result<bool>> {
21569 Task::ready(Ok(false))
21570 }
21571
21572 fn apply_additional_edits_for_completion(
21573 &self,
21574 _buffer: Entity<Buffer>,
21575 _completions: Rc<RefCell<Box<[Completion]>>>,
21576 _completion_index: usize,
21577 _push_to_history: bool,
21578 _cx: &mut Context<Editor>,
21579 ) -> Task<Result<Option<language::Transaction>>> {
21580 Task::ready(Ok(None))
21581 }
21582
21583 fn is_completion_trigger(
21584 &self,
21585 buffer: &Entity<Buffer>,
21586 position: language::Anchor,
21587 text: &str,
21588 trigger_in_words: bool,
21589 menu_is_open: bool,
21590 cx: &mut Context<Editor>,
21591 ) -> bool;
21592
21593 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21594
21595 fn sort_completions(&self) -> bool {
21596 true
21597 }
21598
21599 fn filter_completions(&self) -> bool {
21600 true
21601 }
21602}
21603
21604pub trait CodeActionProvider {
21605 fn id(&self) -> Arc<str>;
21606
21607 fn code_actions(
21608 &self,
21609 buffer: &Entity<Buffer>,
21610 range: Range<text::Anchor>,
21611 window: &mut Window,
21612 cx: &mut App,
21613 ) -> Task<Result<Vec<CodeAction>>>;
21614
21615 fn apply_code_action(
21616 &self,
21617 buffer_handle: Entity<Buffer>,
21618 action: CodeAction,
21619 excerpt_id: ExcerptId,
21620 push_to_history: bool,
21621 window: &mut Window,
21622 cx: &mut App,
21623 ) -> Task<Result<ProjectTransaction>>;
21624}
21625
21626impl CodeActionProvider for Entity<Project> {
21627 fn id(&self) -> Arc<str> {
21628 "project".into()
21629 }
21630
21631 fn code_actions(
21632 &self,
21633 buffer: &Entity<Buffer>,
21634 range: Range<text::Anchor>,
21635 _window: &mut Window,
21636 cx: &mut App,
21637 ) -> Task<Result<Vec<CodeAction>>> {
21638 self.update(cx, |project, cx| {
21639 let code_lens = project.code_lens(buffer, range.clone(), cx);
21640 let code_actions = project.code_actions(buffer, range, None, cx);
21641 cx.background_spawn(async move {
21642 let (code_lens, code_actions) = join(code_lens, code_actions).await;
21643 Ok(code_lens
21644 .context("code lens fetch")?
21645 .into_iter()
21646 .chain(code_actions.context("code action fetch")?)
21647 .collect())
21648 })
21649 })
21650 }
21651
21652 fn apply_code_action(
21653 &self,
21654 buffer_handle: Entity<Buffer>,
21655 action: CodeAction,
21656 _excerpt_id: ExcerptId,
21657 push_to_history: bool,
21658 _window: &mut Window,
21659 cx: &mut App,
21660 ) -> Task<Result<ProjectTransaction>> {
21661 self.update(cx, |project, cx| {
21662 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21663 })
21664 }
21665}
21666
21667fn snippet_completions(
21668 project: &Project,
21669 buffer: &Entity<Buffer>,
21670 buffer_position: text::Anchor,
21671 cx: &mut App,
21672) -> Task<Result<CompletionResponse>> {
21673 let languages = buffer.read(cx).languages_at(buffer_position);
21674 let snippet_store = project.snippets().read(cx);
21675
21676 let scopes: Vec<_> = languages
21677 .iter()
21678 .filter_map(|language| {
21679 let language_name = language.lsp_id();
21680 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21681
21682 if snippets.is_empty() {
21683 None
21684 } else {
21685 Some((language.default_scope(), snippets))
21686 }
21687 })
21688 .collect();
21689
21690 if scopes.is_empty() {
21691 return Task::ready(Ok(CompletionResponse {
21692 completions: vec![],
21693 is_incomplete: false,
21694 }));
21695 }
21696
21697 let snapshot = buffer.read(cx).text_snapshot();
21698 let chars: String = snapshot
21699 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
21700 .collect();
21701 let executor = cx.background_executor().clone();
21702
21703 cx.background_spawn(async move {
21704 let mut is_incomplete = false;
21705 let mut completions: Vec<Completion> = Vec::new();
21706 for (scope, snippets) in scopes.into_iter() {
21707 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
21708 let mut last_word = chars
21709 .chars()
21710 .take_while(|c| classifier.is_word(*c))
21711 .collect::<String>();
21712 last_word = last_word.chars().rev().collect();
21713
21714 if last_word.is_empty() {
21715 return Ok(CompletionResponse {
21716 completions: vec![],
21717 is_incomplete: true,
21718 });
21719 }
21720
21721 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
21722 let to_lsp = |point: &text::Anchor| {
21723 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
21724 point_to_lsp(end)
21725 };
21726 let lsp_end = to_lsp(&buffer_position);
21727
21728 let candidates = snippets
21729 .iter()
21730 .enumerate()
21731 .flat_map(|(ix, snippet)| {
21732 snippet
21733 .prefix
21734 .iter()
21735 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21736 })
21737 .collect::<Vec<StringMatchCandidate>>();
21738
21739 const MAX_RESULTS: usize = 100;
21740 let mut matches = fuzzy::match_strings(
21741 &candidates,
21742 &last_word,
21743 last_word.chars().any(|c| c.is_uppercase()),
21744 true,
21745 MAX_RESULTS,
21746 &Default::default(),
21747 executor.clone(),
21748 )
21749 .await;
21750
21751 if matches.len() >= MAX_RESULTS {
21752 is_incomplete = true;
21753 }
21754
21755 // Remove all candidates where the query's start does not match the start of any word in the candidate
21756 if let Some(query_start) = last_word.chars().next() {
21757 matches.retain(|string_match| {
21758 split_words(&string_match.string).any(|word| {
21759 // Check that the first codepoint of the word as lowercase matches the first
21760 // codepoint of the query as lowercase
21761 word.chars()
21762 .flat_map(|codepoint| codepoint.to_lowercase())
21763 .zip(query_start.to_lowercase())
21764 .all(|(word_cp, query_cp)| word_cp == query_cp)
21765 })
21766 });
21767 }
21768
21769 let matched_strings = matches
21770 .into_iter()
21771 .map(|m| m.string)
21772 .collect::<HashSet<_>>();
21773
21774 completions.extend(snippets.iter().filter_map(|snippet| {
21775 let matching_prefix = snippet
21776 .prefix
21777 .iter()
21778 .find(|prefix| matched_strings.contains(*prefix))?;
21779 let start = as_offset - last_word.len();
21780 let start = snapshot.anchor_before(start);
21781 let range = start..buffer_position;
21782 let lsp_start = to_lsp(&start);
21783 let lsp_range = lsp::Range {
21784 start: lsp_start,
21785 end: lsp_end,
21786 };
21787 Some(Completion {
21788 replace_range: range,
21789 new_text: snippet.body.clone(),
21790 source: CompletionSource::Lsp {
21791 insert_range: None,
21792 server_id: LanguageServerId(usize::MAX),
21793 resolved: true,
21794 lsp_completion: Box::new(lsp::CompletionItem {
21795 label: snippet.prefix.first().unwrap().clone(),
21796 kind: Some(CompletionItemKind::SNIPPET),
21797 label_details: snippet.description.as_ref().map(|description| {
21798 lsp::CompletionItemLabelDetails {
21799 detail: Some(description.clone()),
21800 description: None,
21801 }
21802 }),
21803 insert_text_format: Some(InsertTextFormat::SNIPPET),
21804 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21805 lsp::InsertReplaceEdit {
21806 new_text: snippet.body.clone(),
21807 insert: lsp_range,
21808 replace: lsp_range,
21809 },
21810 )),
21811 filter_text: Some(snippet.body.clone()),
21812 sort_text: Some(char::MAX.to_string()),
21813 ..lsp::CompletionItem::default()
21814 }),
21815 lsp_defaults: None,
21816 },
21817 label: CodeLabel {
21818 text: matching_prefix.clone(),
21819 runs: Vec::new(),
21820 filter_range: 0..matching_prefix.len(),
21821 },
21822 icon_path: None,
21823 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
21824 single_line: snippet.name.clone().into(),
21825 plain_text: snippet
21826 .description
21827 .clone()
21828 .map(|description| description.into()),
21829 }),
21830 insert_text_mode: None,
21831 confirm: None,
21832 })
21833 }))
21834 }
21835
21836 Ok(CompletionResponse {
21837 completions,
21838 is_incomplete,
21839 })
21840 })
21841}
21842
21843impl CompletionProvider for Entity<Project> {
21844 fn completions(
21845 &self,
21846 _excerpt_id: ExcerptId,
21847 buffer: &Entity<Buffer>,
21848 buffer_position: text::Anchor,
21849 options: CompletionContext,
21850 _window: &mut Window,
21851 cx: &mut Context<Editor>,
21852 ) -> Task<Result<Vec<CompletionResponse>>> {
21853 self.update(cx, |project, cx| {
21854 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21855 let project_completions = project.completions(buffer, buffer_position, options, cx);
21856 cx.background_spawn(async move {
21857 let mut responses = project_completions.await?;
21858 let snippets = snippets.await?;
21859 if !snippets.completions.is_empty() {
21860 responses.push(snippets);
21861 }
21862 Ok(responses)
21863 })
21864 })
21865 }
21866
21867 fn resolve_completions(
21868 &self,
21869 buffer: Entity<Buffer>,
21870 completion_indices: Vec<usize>,
21871 completions: Rc<RefCell<Box<[Completion]>>>,
21872 cx: &mut Context<Editor>,
21873 ) -> Task<Result<bool>> {
21874 self.update(cx, |project, cx| {
21875 project.lsp_store().update(cx, |lsp_store, cx| {
21876 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21877 })
21878 })
21879 }
21880
21881 fn apply_additional_edits_for_completion(
21882 &self,
21883 buffer: Entity<Buffer>,
21884 completions: Rc<RefCell<Box<[Completion]>>>,
21885 completion_index: usize,
21886 push_to_history: bool,
21887 cx: &mut Context<Editor>,
21888 ) -> Task<Result<Option<language::Transaction>>> {
21889 self.update(cx, |project, cx| {
21890 project.lsp_store().update(cx, |lsp_store, cx| {
21891 lsp_store.apply_additional_edits_for_completion(
21892 buffer,
21893 completions,
21894 completion_index,
21895 push_to_history,
21896 cx,
21897 )
21898 })
21899 })
21900 }
21901
21902 fn is_completion_trigger(
21903 &self,
21904 buffer: &Entity<Buffer>,
21905 position: language::Anchor,
21906 text: &str,
21907 trigger_in_words: bool,
21908 menu_is_open: bool,
21909 cx: &mut Context<Editor>,
21910 ) -> bool {
21911 let mut chars = text.chars();
21912 let char = if let Some(char) = chars.next() {
21913 char
21914 } else {
21915 return false;
21916 };
21917 if chars.next().is_some() {
21918 return false;
21919 }
21920
21921 let buffer = buffer.read(cx);
21922 let snapshot = buffer.snapshot();
21923 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
21924 return false;
21925 }
21926 let classifier = snapshot.char_classifier_at(position).for_completion(true);
21927 if trigger_in_words && classifier.is_word(char) {
21928 return true;
21929 }
21930
21931 buffer.completion_triggers().contains(text)
21932 }
21933}
21934
21935impl SemanticsProvider for Entity<Project> {
21936 fn hover(
21937 &self,
21938 buffer: &Entity<Buffer>,
21939 position: text::Anchor,
21940 cx: &mut App,
21941 ) -> Option<Task<Vec<project::Hover>>> {
21942 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
21943 }
21944
21945 fn document_highlights(
21946 &self,
21947 buffer: &Entity<Buffer>,
21948 position: text::Anchor,
21949 cx: &mut App,
21950 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
21951 Some(self.update(cx, |project, cx| {
21952 project.document_highlights(buffer, position, cx)
21953 }))
21954 }
21955
21956 fn definitions(
21957 &self,
21958 buffer: &Entity<Buffer>,
21959 position: text::Anchor,
21960 kind: GotoDefinitionKind,
21961 cx: &mut App,
21962 ) -> Option<Task<Result<Vec<LocationLink>>>> {
21963 Some(self.update(cx, |project, cx| match kind {
21964 GotoDefinitionKind::Symbol => project.definitions(&buffer, position, cx),
21965 GotoDefinitionKind::Declaration => project.declarations(&buffer, position, cx),
21966 GotoDefinitionKind::Type => project.type_definitions(&buffer, position, cx),
21967 GotoDefinitionKind::Implementation => project.implementations(&buffer, position, cx),
21968 }))
21969 }
21970
21971 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
21972 // TODO: make this work for remote projects
21973 self.update(cx, |project, cx| {
21974 if project
21975 .active_debug_session(cx)
21976 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
21977 {
21978 return true;
21979 }
21980
21981 buffer.update(cx, |buffer, cx| {
21982 project.any_language_server_supports_inlay_hints(buffer, cx)
21983 })
21984 })
21985 }
21986
21987 fn inline_values(
21988 &self,
21989 buffer_handle: Entity<Buffer>,
21990 range: Range<text::Anchor>,
21991 cx: &mut App,
21992 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21993 self.update(cx, |project, cx| {
21994 let (session, active_stack_frame) = project.active_debug_session(cx)?;
21995
21996 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
21997 })
21998 }
21999
22000 fn inlay_hints(
22001 &self,
22002 buffer_handle: Entity<Buffer>,
22003 range: Range<text::Anchor>,
22004 cx: &mut App,
22005 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22006 Some(self.update(cx, |project, cx| {
22007 project.inlay_hints(buffer_handle, range, cx)
22008 }))
22009 }
22010
22011 fn resolve_inlay_hint(
22012 &self,
22013 hint: InlayHint,
22014 buffer_handle: Entity<Buffer>,
22015 server_id: LanguageServerId,
22016 cx: &mut App,
22017 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22018 Some(self.update(cx, |project, cx| {
22019 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22020 }))
22021 }
22022
22023 fn range_for_rename(
22024 &self,
22025 buffer: &Entity<Buffer>,
22026 position: text::Anchor,
22027 cx: &mut App,
22028 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22029 Some(self.update(cx, |project, cx| {
22030 let buffer = buffer.clone();
22031 let task = project.prepare_rename(buffer.clone(), position, cx);
22032 cx.spawn(async move |_, cx| {
22033 Ok(match task.await? {
22034 PrepareRenameResponse::Success(range) => Some(range),
22035 PrepareRenameResponse::InvalidPosition => None,
22036 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22037 // Fallback on using TreeSitter info to determine identifier range
22038 buffer.read_with(cx, |buffer, _| {
22039 let snapshot = buffer.snapshot();
22040 let (range, kind) = snapshot.surrounding_word(position);
22041 if kind != Some(CharKind::Word) {
22042 return None;
22043 }
22044 Some(
22045 snapshot.anchor_before(range.start)
22046 ..snapshot.anchor_after(range.end),
22047 )
22048 })?
22049 }
22050 })
22051 })
22052 }))
22053 }
22054
22055 fn perform_rename(
22056 &self,
22057 buffer: &Entity<Buffer>,
22058 position: text::Anchor,
22059 new_name: String,
22060 cx: &mut App,
22061 ) -> Option<Task<Result<ProjectTransaction>>> {
22062 Some(self.update(cx, |project, cx| {
22063 project.perform_rename(buffer.clone(), position, new_name, cx)
22064 }))
22065 }
22066}
22067
22068fn inlay_hint_settings(
22069 location: Anchor,
22070 snapshot: &MultiBufferSnapshot,
22071 cx: &mut Context<Editor>,
22072) -> InlayHintSettings {
22073 let file = snapshot.file_at(location);
22074 let language = snapshot.language_at(location).map(|l| l.name());
22075 language_settings(language, file, cx).inlay_hints
22076}
22077
22078fn consume_contiguous_rows(
22079 contiguous_row_selections: &mut Vec<Selection<Point>>,
22080 selection: &Selection<Point>,
22081 display_map: &DisplaySnapshot,
22082 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22083) -> (MultiBufferRow, MultiBufferRow) {
22084 contiguous_row_selections.push(selection.clone());
22085 let start_row = MultiBufferRow(selection.start.row);
22086 let mut end_row = ending_row(selection, display_map);
22087
22088 while let Some(next_selection) = selections.peek() {
22089 if next_selection.start.row <= end_row.0 {
22090 end_row = ending_row(next_selection, display_map);
22091 contiguous_row_selections.push(selections.next().unwrap().clone());
22092 } else {
22093 break;
22094 }
22095 }
22096 (start_row, end_row)
22097}
22098
22099fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22100 if next_selection.end.column > 0 || next_selection.is_empty() {
22101 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22102 } else {
22103 MultiBufferRow(next_selection.end.row)
22104 }
22105}
22106
22107impl EditorSnapshot {
22108 pub fn remote_selections_in_range<'a>(
22109 &'a self,
22110 range: &'a Range<Anchor>,
22111 collaboration_hub: &dyn CollaborationHub,
22112 cx: &'a App,
22113 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22114 let participant_names = collaboration_hub.user_names(cx);
22115 let participant_indices = collaboration_hub.user_participant_indices(cx);
22116 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22117 let collaborators_by_replica_id = collaborators_by_peer_id
22118 .values()
22119 .map(|collaborator| (collaborator.replica_id, collaborator))
22120 .collect::<HashMap<_, _>>();
22121 self.buffer_snapshot
22122 .selections_in_range(range, false)
22123 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22124 if replica_id == AGENT_REPLICA_ID {
22125 Some(RemoteSelection {
22126 replica_id,
22127 selection,
22128 cursor_shape,
22129 line_mode,
22130 collaborator_id: CollaboratorId::Agent,
22131 user_name: Some("Agent".into()),
22132 color: cx.theme().players().agent(),
22133 })
22134 } else {
22135 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22136 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22137 let user_name = participant_names.get(&collaborator.user_id).cloned();
22138 Some(RemoteSelection {
22139 replica_id,
22140 selection,
22141 cursor_shape,
22142 line_mode,
22143 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22144 user_name,
22145 color: if let Some(index) = participant_index {
22146 cx.theme().players().color_for_participant(index.0)
22147 } else {
22148 cx.theme().players().absent()
22149 },
22150 })
22151 }
22152 })
22153 }
22154
22155 pub fn hunks_for_ranges(
22156 &self,
22157 ranges: impl IntoIterator<Item = Range<Point>>,
22158 ) -> Vec<MultiBufferDiffHunk> {
22159 let mut hunks = Vec::new();
22160 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22161 HashMap::default();
22162 for query_range in ranges {
22163 let query_rows =
22164 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22165 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22166 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22167 ) {
22168 // Include deleted hunks that are adjacent to the query range, because
22169 // otherwise they would be missed.
22170 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22171 if hunk.status().is_deleted() {
22172 intersects_range |= hunk.row_range.start == query_rows.end;
22173 intersects_range |= hunk.row_range.end == query_rows.start;
22174 }
22175 if intersects_range {
22176 if !processed_buffer_rows
22177 .entry(hunk.buffer_id)
22178 .or_default()
22179 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22180 {
22181 continue;
22182 }
22183 hunks.push(hunk);
22184 }
22185 }
22186 }
22187
22188 hunks
22189 }
22190
22191 fn display_diff_hunks_for_rows<'a>(
22192 &'a self,
22193 display_rows: Range<DisplayRow>,
22194 folded_buffers: &'a HashSet<BufferId>,
22195 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22196 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22197 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22198
22199 self.buffer_snapshot
22200 .diff_hunks_in_range(buffer_start..buffer_end)
22201 .filter_map(|hunk| {
22202 if folded_buffers.contains(&hunk.buffer_id) {
22203 return None;
22204 }
22205
22206 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22207 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22208
22209 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22210 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22211
22212 let display_hunk = if hunk_display_start.column() != 0 {
22213 DisplayDiffHunk::Folded {
22214 display_row: hunk_display_start.row(),
22215 }
22216 } else {
22217 let mut end_row = hunk_display_end.row();
22218 if hunk_display_end.column() > 0 {
22219 end_row.0 += 1;
22220 }
22221 let is_created_file = hunk.is_created_file();
22222 DisplayDiffHunk::Unfolded {
22223 status: hunk.status(),
22224 diff_base_byte_range: hunk.diff_base_byte_range,
22225 display_row_range: hunk_display_start.row()..end_row,
22226 multi_buffer_range: Anchor::range_in_buffer(
22227 hunk.excerpt_id,
22228 hunk.buffer_id,
22229 hunk.buffer_range,
22230 ),
22231 is_created_file,
22232 }
22233 };
22234
22235 Some(display_hunk)
22236 })
22237 }
22238
22239 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22240 self.display_snapshot.buffer_snapshot.language_at(position)
22241 }
22242
22243 pub fn is_focused(&self) -> bool {
22244 self.is_focused
22245 }
22246
22247 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22248 self.placeholder_text.as_ref()
22249 }
22250
22251 pub fn scroll_position(&self) -> gpui::Point<f32> {
22252 self.scroll_anchor.scroll_position(&self.display_snapshot)
22253 }
22254
22255 fn gutter_dimensions(
22256 &self,
22257 font_id: FontId,
22258 font_size: Pixels,
22259 max_line_number_width: Pixels,
22260 cx: &App,
22261 ) -> Option<GutterDimensions> {
22262 if !self.show_gutter {
22263 return None;
22264 }
22265
22266 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22267 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22268
22269 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22270 matches!(
22271 ProjectSettings::get_global(cx).git.git_gutter,
22272 Some(GitGutterSetting::TrackedFiles)
22273 )
22274 });
22275 let gutter_settings = EditorSettings::get_global(cx).gutter;
22276 let show_line_numbers = self
22277 .show_line_numbers
22278 .unwrap_or(gutter_settings.line_numbers);
22279 let line_gutter_width = if show_line_numbers {
22280 // Avoid flicker-like gutter resizes when the line number gains another digit by
22281 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22282 let min_width_for_number_on_gutter =
22283 ch_advance * gutter_settings.min_line_number_digits as f32;
22284 max_line_number_width.max(min_width_for_number_on_gutter)
22285 } else {
22286 0.0.into()
22287 };
22288
22289 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22290 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22291
22292 let git_blame_entries_width =
22293 self.git_blame_gutter_max_author_length
22294 .map(|max_author_length| {
22295 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22296 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22297
22298 /// The number of characters to dedicate to gaps and margins.
22299 const SPACING_WIDTH: usize = 4;
22300
22301 let max_char_count = max_author_length.min(renderer.max_author_length())
22302 + ::git::SHORT_SHA_LENGTH
22303 + MAX_RELATIVE_TIMESTAMP.len()
22304 + SPACING_WIDTH;
22305
22306 ch_advance * max_char_count
22307 });
22308
22309 let is_singleton = self.buffer_snapshot.is_singleton();
22310
22311 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22312 left_padding += if !is_singleton {
22313 ch_width * 4.0
22314 } else if show_runnables || show_breakpoints {
22315 ch_width * 3.0
22316 } else if show_git_gutter && show_line_numbers {
22317 ch_width * 2.0
22318 } else if show_git_gutter || show_line_numbers {
22319 ch_width
22320 } else {
22321 px(0.)
22322 };
22323
22324 let shows_folds = is_singleton && gutter_settings.folds;
22325
22326 let right_padding = if shows_folds && show_line_numbers {
22327 ch_width * 4.0
22328 } else if shows_folds || (!is_singleton && show_line_numbers) {
22329 ch_width * 3.0
22330 } else if show_line_numbers {
22331 ch_width
22332 } else {
22333 px(0.)
22334 };
22335
22336 Some(GutterDimensions {
22337 left_padding,
22338 right_padding,
22339 width: line_gutter_width + left_padding + right_padding,
22340 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22341 git_blame_entries_width,
22342 })
22343 }
22344
22345 pub fn render_crease_toggle(
22346 &self,
22347 buffer_row: MultiBufferRow,
22348 row_contains_cursor: bool,
22349 editor: Entity<Editor>,
22350 window: &mut Window,
22351 cx: &mut App,
22352 ) -> Option<AnyElement> {
22353 let folded = self.is_line_folded(buffer_row);
22354 let mut is_foldable = false;
22355
22356 if let Some(crease) = self
22357 .crease_snapshot
22358 .query_row(buffer_row, &self.buffer_snapshot)
22359 {
22360 is_foldable = true;
22361 match crease {
22362 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22363 if let Some(render_toggle) = render_toggle {
22364 let toggle_callback =
22365 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22366 if folded {
22367 editor.update(cx, |editor, cx| {
22368 editor.fold_at(buffer_row, window, cx)
22369 });
22370 } else {
22371 editor.update(cx, |editor, cx| {
22372 editor.unfold_at(buffer_row, window, cx)
22373 });
22374 }
22375 });
22376 return Some((render_toggle)(
22377 buffer_row,
22378 folded,
22379 toggle_callback,
22380 window,
22381 cx,
22382 ));
22383 }
22384 }
22385 }
22386 }
22387
22388 is_foldable |= self.starts_indent(buffer_row);
22389
22390 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22391 Some(
22392 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22393 .toggle_state(folded)
22394 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22395 if folded {
22396 this.unfold_at(buffer_row, window, cx);
22397 } else {
22398 this.fold_at(buffer_row, window, cx);
22399 }
22400 }))
22401 .into_any_element(),
22402 )
22403 } else {
22404 None
22405 }
22406 }
22407
22408 pub fn render_crease_trailer(
22409 &self,
22410 buffer_row: MultiBufferRow,
22411 window: &mut Window,
22412 cx: &mut App,
22413 ) -> Option<AnyElement> {
22414 let folded = self.is_line_folded(buffer_row);
22415 if let Crease::Inline { render_trailer, .. } = self
22416 .crease_snapshot
22417 .query_row(buffer_row, &self.buffer_snapshot)?
22418 {
22419 let render_trailer = render_trailer.as_ref()?;
22420 Some(render_trailer(buffer_row, folded, window, cx))
22421 } else {
22422 None
22423 }
22424 }
22425}
22426
22427impl Deref for EditorSnapshot {
22428 type Target = DisplaySnapshot;
22429
22430 fn deref(&self) -> &Self::Target {
22431 &self.display_snapshot
22432 }
22433}
22434
22435#[derive(Clone, Debug, PartialEq, Eq)]
22436pub enum EditorEvent {
22437 InputIgnored {
22438 text: Arc<str>,
22439 },
22440 InputHandled {
22441 utf16_range_to_replace: Option<Range<isize>>,
22442 text: Arc<str>,
22443 },
22444 ExcerptsAdded {
22445 buffer: Entity<Buffer>,
22446 predecessor: ExcerptId,
22447 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22448 },
22449 ExcerptsRemoved {
22450 ids: Vec<ExcerptId>,
22451 removed_buffer_ids: Vec<BufferId>,
22452 },
22453 BufferFoldToggled {
22454 ids: Vec<ExcerptId>,
22455 folded: bool,
22456 },
22457 ExcerptsEdited {
22458 ids: Vec<ExcerptId>,
22459 },
22460 ExcerptsExpanded {
22461 ids: Vec<ExcerptId>,
22462 },
22463 BufferEdited,
22464 Edited {
22465 transaction_id: clock::Lamport,
22466 },
22467 Reparsed(BufferId),
22468 Focused,
22469 FocusedIn,
22470 Blurred,
22471 DirtyChanged,
22472 Saved,
22473 TitleChanged,
22474 DiffBaseChanged,
22475 SelectionsChanged {
22476 local: bool,
22477 },
22478 ScrollPositionChanged {
22479 local: bool,
22480 autoscroll: bool,
22481 },
22482 Closed,
22483 TransactionUndone {
22484 transaction_id: clock::Lamport,
22485 },
22486 TransactionBegun {
22487 transaction_id: clock::Lamport,
22488 },
22489 Reloaded,
22490 CursorShapeChanged,
22491 PushedToNavHistory {
22492 anchor: Anchor,
22493 is_deactivate: bool,
22494 },
22495}
22496
22497impl EventEmitter<EditorEvent> for Editor {}
22498
22499impl Focusable for Editor {
22500 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22501 self.focus_handle.clone()
22502 }
22503}
22504
22505impl Render for Editor {
22506 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22507 let settings = ThemeSettings::get_global(cx);
22508
22509 let mut text_style = match self.mode {
22510 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22511 color: cx.theme().colors().editor_foreground,
22512 font_family: settings.ui_font.family.clone(),
22513 font_features: settings.ui_font.features.clone(),
22514 font_fallbacks: settings.ui_font.fallbacks.clone(),
22515 font_size: rems(0.875).into(),
22516 font_weight: settings.ui_font.weight,
22517 line_height: relative(settings.buffer_line_height.value()),
22518 ..Default::default()
22519 },
22520 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22521 color: cx.theme().colors().editor_foreground,
22522 font_family: settings.buffer_font.family.clone(),
22523 font_features: settings.buffer_font.features.clone(),
22524 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22525 font_size: settings.buffer_font_size(cx).into(),
22526 font_weight: settings.buffer_font.weight,
22527 line_height: relative(settings.buffer_line_height.value()),
22528 ..Default::default()
22529 },
22530 };
22531 if let Some(text_style_refinement) = &self.text_style_refinement {
22532 text_style.refine(text_style_refinement)
22533 }
22534
22535 let background = match self.mode {
22536 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22537 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22538 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22539 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22540 };
22541
22542 EditorElement::new(
22543 &cx.entity(),
22544 EditorStyle {
22545 background,
22546 border: cx.theme().colors().border,
22547 local_player: cx.theme().players().local(),
22548 text: text_style,
22549 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22550 syntax: cx.theme().syntax().clone(),
22551 status: cx.theme().status().clone(),
22552 inlay_hints_style: make_inlay_hints_style(cx),
22553 inline_completion_styles: make_suggestion_styles(cx),
22554 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22555 show_underlines: self.diagnostics_enabled(),
22556 },
22557 )
22558 }
22559}
22560
22561impl EntityInputHandler for Editor {
22562 fn text_for_range(
22563 &mut self,
22564 range_utf16: Range<usize>,
22565 adjusted_range: &mut Option<Range<usize>>,
22566 _: &mut Window,
22567 cx: &mut Context<Self>,
22568 ) -> Option<String> {
22569 let snapshot = self.buffer.read(cx).read(cx);
22570 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22571 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22572 if (start.0..end.0) != range_utf16 {
22573 adjusted_range.replace(start.0..end.0);
22574 }
22575 Some(snapshot.text_for_range(start..end).collect())
22576 }
22577
22578 fn selected_text_range(
22579 &mut self,
22580 ignore_disabled_input: bool,
22581 _: &mut Window,
22582 cx: &mut Context<Self>,
22583 ) -> Option<UTF16Selection> {
22584 // Prevent the IME menu from appearing when holding down an alphabetic key
22585 // while input is disabled.
22586 if !ignore_disabled_input && !self.input_enabled {
22587 return None;
22588 }
22589
22590 let selection = self.selections.newest::<OffsetUtf16>(cx);
22591 let range = selection.range();
22592
22593 Some(UTF16Selection {
22594 range: range.start.0..range.end.0,
22595 reversed: selection.reversed,
22596 })
22597 }
22598
22599 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22600 let snapshot = self.buffer.read(cx).read(cx);
22601 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22602 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22603 }
22604
22605 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22606 self.clear_highlights::<InputComposition>(cx);
22607 self.ime_transaction.take();
22608 }
22609
22610 fn replace_text_in_range(
22611 &mut self,
22612 range_utf16: Option<Range<usize>>,
22613 text: &str,
22614 window: &mut Window,
22615 cx: &mut Context<Self>,
22616 ) {
22617 if !self.input_enabled {
22618 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22619 return;
22620 }
22621
22622 self.transact(window, cx, |this, window, cx| {
22623 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22624 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22625 Some(this.selection_replacement_ranges(range_utf16, cx))
22626 } else {
22627 this.marked_text_ranges(cx)
22628 };
22629
22630 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22631 let newest_selection_id = this.selections.newest_anchor().id;
22632 this.selections
22633 .all::<OffsetUtf16>(cx)
22634 .iter()
22635 .zip(ranges_to_replace.iter())
22636 .find_map(|(selection, range)| {
22637 if selection.id == newest_selection_id {
22638 Some(
22639 (range.start.0 as isize - selection.head().0 as isize)
22640 ..(range.end.0 as isize - selection.head().0 as isize),
22641 )
22642 } else {
22643 None
22644 }
22645 })
22646 });
22647
22648 cx.emit(EditorEvent::InputHandled {
22649 utf16_range_to_replace: range_to_replace,
22650 text: text.into(),
22651 });
22652
22653 if let Some(new_selected_ranges) = new_selected_ranges {
22654 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22655 selections.select_ranges(new_selected_ranges)
22656 });
22657 this.backspace(&Default::default(), window, cx);
22658 }
22659
22660 this.handle_input(text, window, cx);
22661 });
22662
22663 if let Some(transaction) = self.ime_transaction {
22664 self.buffer.update(cx, |buffer, cx| {
22665 buffer.group_until_transaction(transaction, cx);
22666 });
22667 }
22668
22669 self.unmark_text(window, cx);
22670 }
22671
22672 fn replace_and_mark_text_in_range(
22673 &mut self,
22674 range_utf16: Option<Range<usize>>,
22675 text: &str,
22676 new_selected_range_utf16: Option<Range<usize>>,
22677 window: &mut Window,
22678 cx: &mut Context<Self>,
22679 ) {
22680 if !self.input_enabled {
22681 return;
22682 }
22683
22684 let transaction = self.transact(window, cx, |this, window, cx| {
22685 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22686 let snapshot = this.buffer.read(cx).read(cx);
22687 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22688 for marked_range in &mut marked_ranges {
22689 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22690 marked_range.start.0 += relative_range_utf16.start;
22691 marked_range.start =
22692 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22693 marked_range.end =
22694 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22695 }
22696 }
22697 Some(marked_ranges)
22698 } else if let Some(range_utf16) = range_utf16 {
22699 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22700 Some(this.selection_replacement_ranges(range_utf16, cx))
22701 } else {
22702 None
22703 };
22704
22705 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22706 let newest_selection_id = this.selections.newest_anchor().id;
22707 this.selections
22708 .all::<OffsetUtf16>(cx)
22709 .iter()
22710 .zip(ranges_to_replace.iter())
22711 .find_map(|(selection, range)| {
22712 if selection.id == newest_selection_id {
22713 Some(
22714 (range.start.0 as isize - selection.head().0 as isize)
22715 ..(range.end.0 as isize - selection.head().0 as isize),
22716 )
22717 } else {
22718 None
22719 }
22720 })
22721 });
22722
22723 cx.emit(EditorEvent::InputHandled {
22724 utf16_range_to_replace: range_to_replace,
22725 text: text.into(),
22726 });
22727
22728 if let Some(ranges) = ranges_to_replace {
22729 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22730 s.select_ranges(ranges)
22731 });
22732 }
22733
22734 let marked_ranges = {
22735 let snapshot = this.buffer.read(cx).read(cx);
22736 this.selections
22737 .disjoint_anchors()
22738 .iter()
22739 .map(|selection| {
22740 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22741 })
22742 .collect::<Vec<_>>()
22743 };
22744
22745 if text.is_empty() {
22746 this.unmark_text(window, cx);
22747 } else {
22748 this.highlight_text::<InputComposition>(
22749 marked_ranges.clone(),
22750 HighlightStyle {
22751 underline: Some(UnderlineStyle {
22752 thickness: px(1.),
22753 color: None,
22754 wavy: false,
22755 }),
22756 ..Default::default()
22757 },
22758 cx,
22759 );
22760 }
22761
22762 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22763 let use_autoclose = this.use_autoclose;
22764 let use_auto_surround = this.use_auto_surround;
22765 this.set_use_autoclose(false);
22766 this.set_use_auto_surround(false);
22767 this.handle_input(text, window, cx);
22768 this.set_use_autoclose(use_autoclose);
22769 this.set_use_auto_surround(use_auto_surround);
22770
22771 if let Some(new_selected_range) = new_selected_range_utf16 {
22772 let snapshot = this.buffer.read(cx).read(cx);
22773 let new_selected_ranges = marked_ranges
22774 .into_iter()
22775 .map(|marked_range| {
22776 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22777 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22778 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22779 snapshot.clip_offset_utf16(new_start, Bias::Left)
22780 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22781 })
22782 .collect::<Vec<_>>();
22783
22784 drop(snapshot);
22785 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22786 selections.select_ranges(new_selected_ranges)
22787 });
22788 }
22789 });
22790
22791 self.ime_transaction = self.ime_transaction.or(transaction);
22792 if let Some(transaction) = self.ime_transaction {
22793 self.buffer.update(cx, |buffer, cx| {
22794 buffer.group_until_transaction(transaction, cx);
22795 });
22796 }
22797
22798 if self.text_highlights::<InputComposition>(cx).is_none() {
22799 self.ime_transaction.take();
22800 }
22801 }
22802
22803 fn bounds_for_range(
22804 &mut self,
22805 range_utf16: Range<usize>,
22806 element_bounds: gpui::Bounds<Pixels>,
22807 window: &mut Window,
22808 cx: &mut Context<Self>,
22809 ) -> Option<gpui::Bounds<Pixels>> {
22810 let text_layout_details = self.text_layout_details(window);
22811 let CharacterDimensions {
22812 em_width,
22813 em_advance,
22814 line_height,
22815 } = self.character_dimensions(window);
22816
22817 let snapshot = self.snapshot(window, cx);
22818 let scroll_position = snapshot.scroll_position();
22819 let scroll_left = scroll_position.x * em_advance;
22820
22821 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22822 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22823 + self.gutter_dimensions.full_width();
22824 let y = line_height * (start.row().as_f32() - scroll_position.y);
22825
22826 Some(Bounds {
22827 origin: element_bounds.origin + point(x, y),
22828 size: size(em_width, line_height),
22829 })
22830 }
22831
22832 fn character_index_for_point(
22833 &mut self,
22834 point: gpui::Point<Pixels>,
22835 _window: &mut Window,
22836 _cx: &mut Context<Self>,
22837 ) -> Option<usize> {
22838 let position_map = self.last_position_map.as_ref()?;
22839 if !position_map.text_hitbox.contains(&point) {
22840 return None;
22841 }
22842 let display_point = position_map.point_for_position(point).previous_valid;
22843 let anchor = position_map
22844 .snapshot
22845 .display_point_to_anchor(display_point, Bias::Left);
22846 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22847 Some(utf16_offset.0)
22848 }
22849}
22850
22851trait SelectionExt {
22852 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22853 fn spanned_rows(
22854 &self,
22855 include_end_if_at_line_start: bool,
22856 map: &DisplaySnapshot,
22857 ) -> Range<MultiBufferRow>;
22858}
22859
22860impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22861 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22862 let start = self
22863 .start
22864 .to_point(&map.buffer_snapshot)
22865 .to_display_point(map);
22866 let end = self
22867 .end
22868 .to_point(&map.buffer_snapshot)
22869 .to_display_point(map);
22870 if self.reversed {
22871 end..start
22872 } else {
22873 start..end
22874 }
22875 }
22876
22877 fn spanned_rows(
22878 &self,
22879 include_end_if_at_line_start: bool,
22880 map: &DisplaySnapshot,
22881 ) -> Range<MultiBufferRow> {
22882 let start = self.start.to_point(&map.buffer_snapshot);
22883 let mut end = self.end.to_point(&map.buffer_snapshot);
22884 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22885 end.row -= 1;
22886 }
22887
22888 let buffer_start = map.prev_line_boundary(start).0;
22889 let buffer_end = map.next_line_boundary(end).0;
22890 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22891 }
22892}
22893
22894impl<T: InvalidationRegion> InvalidationStack<T> {
22895 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22896 where
22897 S: Clone + ToOffset,
22898 {
22899 while let Some(region) = self.last() {
22900 let all_selections_inside_invalidation_ranges =
22901 if selections.len() == region.ranges().len() {
22902 selections
22903 .iter()
22904 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
22905 .all(|(selection, invalidation_range)| {
22906 let head = selection.head().to_offset(buffer);
22907 invalidation_range.start <= head && invalidation_range.end >= head
22908 })
22909 } else {
22910 false
22911 };
22912
22913 if all_selections_inside_invalidation_ranges {
22914 break;
22915 } else {
22916 self.pop();
22917 }
22918 }
22919 }
22920}
22921
22922impl<T> Default for InvalidationStack<T> {
22923 fn default() -> Self {
22924 Self(Default::default())
22925 }
22926}
22927
22928impl<T> Deref for InvalidationStack<T> {
22929 type Target = Vec<T>;
22930
22931 fn deref(&self) -> &Self::Target {
22932 &self.0
22933 }
22934}
22935
22936impl<T> DerefMut for InvalidationStack<T> {
22937 fn deref_mut(&mut self) -> &mut Self::Target {
22938 &mut self.0
22939 }
22940}
22941
22942impl InvalidationRegion for SnippetState {
22943 fn ranges(&self) -> &[Range<Anchor>] {
22944 &self.ranges[self.active_index]
22945 }
22946}
22947
22948fn inline_completion_edit_text(
22949 current_snapshot: &BufferSnapshot,
22950 edits: &[(Range<Anchor>, String)],
22951 edit_preview: &EditPreview,
22952 include_deletions: bool,
22953 cx: &App,
22954) -> HighlightedText {
22955 let edits = edits
22956 .iter()
22957 .map(|(anchor, text)| {
22958 (
22959 anchor.start.text_anchor..anchor.end.text_anchor,
22960 text.clone(),
22961 )
22962 })
22963 .collect::<Vec<_>>();
22964
22965 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
22966}
22967
22968pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
22969 match severity {
22970 lsp::DiagnosticSeverity::ERROR => colors.error,
22971 lsp::DiagnosticSeverity::WARNING => colors.warning,
22972 lsp::DiagnosticSeverity::INFORMATION => colors.info,
22973 lsp::DiagnosticSeverity::HINT => colors.info,
22974 _ => colors.ignored,
22975 }
22976}
22977
22978pub fn styled_runs_for_code_label<'a>(
22979 label: &'a CodeLabel,
22980 syntax_theme: &'a theme::SyntaxTheme,
22981) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
22982 let fade_out = HighlightStyle {
22983 fade_out: Some(0.35),
22984 ..Default::default()
22985 };
22986
22987 let mut prev_end = label.filter_range.end;
22988 label
22989 .runs
22990 .iter()
22991 .enumerate()
22992 .flat_map(move |(ix, (range, highlight_id))| {
22993 let style = if let Some(style) = highlight_id.style(syntax_theme) {
22994 style
22995 } else {
22996 return Default::default();
22997 };
22998 let mut muted_style = style;
22999 muted_style.highlight(fade_out);
23000
23001 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23002 if range.start >= label.filter_range.end {
23003 if range.start > prev_end {
23004 runs.push((prev_end..range.start, fade_out));
23005 }
23006 runs.push((range.clone(), muted_style));
23007 } else if range.end <= label.filter_range.end {
23008 runs.push((range.clone(), style));
23009 } else {
23010 runs.push((range.start..label.filter_range.end, style));
23011 runs.push((label.filter_range.end..range.end, muted_style));
23012 }
23013 prev_end = cmp::max(prev_end, range.end);
23014
23015 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23016 runs.push((prev_end..label.text.len(), fade_out));
23017 }
23018
23019 runs
23020 })
23021}
23022
23023pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23024 let mut prev_index = 0;
23025 let mut prev_codepoint: Option<char> = None;
23026 text.char_indices()
23027 .chain([(text.len(), '\0')])
23028 .filter_map(move |(index, codepoint)| {
23029 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23030 let is_boundary = index == text.len()
23031 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23032 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23033 if is_boundary {
23034 let chunk = &text[prev_index..index];
23035 prev_index = index;
23036 Some(chunk)
23037 } else {
23038 None
23039 }
23040 })
23041}
23042
23043pub trait RangeToAnchorExt: Sized {
23044 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23045
23046 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23047 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23048 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23049 }
23050}
23051
23052impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23053 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23054 let start_offset = self.start.to_offset(snapshot);
23055 let end_offset = self.end.to_offset(snapshot);
23056 if start_offset == end_offset {
23057 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23058 } else {
23059 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23060 }
23061 }
23062}
23063
23064pub trait RowExt {
23065 fn as_f32(&self) -> f32;
23066
23067 fn next_row(&self) -> Self;
23068
23069 fn previous_row(&self) -> Self;
23070
23071 fn minus(&self, other: Self) -> u32;
23072}
23073
23074impl RowExt for DisplayRow {
23075 fn as_f32(&self) -> f32 {
23076 self.0 as f32
23077 }
23078
23079 fn next_row(&self) -> Self {
23080 Self(self.0 + 1)
23081 }
23082
23083 fn previous_row(&self) -> Self {
23084 Self(self.0.saturating_sub(1))
23085 }
23086
23087 fn minus(&self, other: Self) -> u32 {
23088 self.0 - other.0
23089 }
23090}
23091
23092impl RowExt for MultiBufferRow {
23093 fn as_f32(&self) -> f32 {
23094 self.0 as f32
23095 }
23096
23097 fn next_row(&self) -> Self {
23098 Self(self.0 + 1)
23099 }
23100
23101 fn previous_row(&self) -> Self {
23102 Self(self.0.saturating_sub(1))
23103 }
23104
23105 fn minus(&self, other: Self) -> u32 {
23106 self.0 - other.0
23107 }
23108}
23109
23110trait RowRangeExt {
23111 type Row;
23112
23113 fn len(&self) -> usize;
23114
23115 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23116}
23117
23118impl RowRangeExt for Range<MultiBufferRow> {
23119 type Row = MultiBufferRow;
23120
23121 fn len(&self) -> usize {
23122 (self.end.0 - self.start.0) as usize
23123 }
23124
23125 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23126 (self.start.0..self.end.0).map(MultiBufferRow)
23127 }
23128}
23129
23130impl RowRangeExt for Range<DisplayRow> {
23131 type Row = DisplayRow;
23132
23133 fn len(&self) -> usize {
23134 (self.end.0 - self.start.0) as usize
23135 }
23136
23137 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23138 (self.start.0..self.end.0).map(DisplayRow)
23139 }
23140}
23141
23142/// If select range has more than one line, we
23143/// just point the cursor to range.start.
23144fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23145 if range.start.row == range.end.row {
23146 range
23147 } else {
23148 range.start..range.start
23149 }
23150}
23151pub struct KillRing(ClipboardItem);
23152impl Global for KillRing {}
23153
23154const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23155
23156enum BreakpointPromptEditAction {
23157 Log,
23158 Condition,
23159 HitCondition,
23160}
23161
23162struct BreakpointPromptEditor {
23163 pub(crate) prompt: Entity<Editor>,
23164 editor: WeakEntity<Editor>,
23165 breakpoint_anchor: Anchor,
23166 breakpoint: Breakpoint,
23167 edit_action: BreakpointPromptEditAction,
23168 block_ids: HashSet<CustomBlockId>,
23169 editor_margins: Arc<Mutex<EditorMargins>>,
23170 _subscriptions: Vec<Subscription>,
23171}
23172
23173impl BreakpointPromptEditor {
23174 const MAX_LINES: u8 = 4;
23175
23176 fn new(
23177 editor: WeakEntity<Editor>,
23178 breakpoint_anchor: Anchor,
23179 breakpoint: Breakpoint,
23180 edit_action: BreakpointPromptEditAction,
23181 window: &mut Window,
23182 cx: &mut Context<Self>,
23183 ) -> Self {
23184 let base_text = match edit_action {
23185 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23186 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23187 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23188 }
23189 .map(|msg| msg.to_string())
23190 .unwrap_or_default();
23191
23192 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23193 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23194
23195 let prompt = cx.new(|cx| {
23196 let mut prompt = Editor::new(
23197 EditorMode::AutoHeight {
23198 min_lines: 1,
23199 max_lines: Some(Self::MAX_LINES as usize),
23200 },
23201 buffer,
23202 None,
23203 window,
23204 cx,
23205 );
23206 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23207 prompt.set_show_cursor_when_unfocused(false, cx);
23208 prompt.set_placeholder_text(
23209 match edit_action {
23210 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23211 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23212 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23213 },
23214 cx,
23215 );
23216
23217 prompt
23218 });
23219
23220 Self {
23221 prompt,
23222 editor,
23223 breakpoint_anchor,
23224 breakpoint,
23225 edit_action,
23226 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23227 block_ids: Default::default(),
23228 _subscriptions: vec![],
23229 }
23230 }
23231
23232 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23233 self.block_ids.extend(block_ids)
23234 }
23235
23236 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23237 if let Some(editor) = self.editor.upgrade() {
23238 let message = self
23239 .prompt
23240 .read(cx)
23241 .buffer
23242 .read(cx)
23243 .as_singleton()
23244 .expect("A multi buffer in breakpoint prompt isn't possible")
23245 .read(cx)
23246 .as_rope()
23247 .to_string();
23248
23249 editor.update(cx, |editor, cx| {
23250 editor.edit_breakpoint_at_anchor(
23251 self.breakpoint_anchor,
23252 self.breakpoint.clone(),
23253 match self.edit_action {
23254 BreakpointPromptEditAction::Log => {
23255 BreakpointEditAction::EditLogMessage(message.into())
23256 }
23257 BreakpointPromptEditAction::Condition => {
23258 BreakpointEditAction::EditCondition(message.into())
23259 }
23260 BreakpointPromptEditAction::HitCondition => {
23261 BreakpointEditAction::EditHitCondition(message.into())
23262 }
23263 },
23264 cx,
23265 );
23266
23267 editor.remove_blocks(self.block_ids.clone(), None, cx);
23268 cx.focus_self(window);
23269 });
23270 }
23271 }
23272
23273 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23274 self.editor
23275 .update(cx, |editor, cx| {
23276 editor.remove_blocks(self.block_ids.clone(), None, cx);
23277 window.focus(&editor.focus_handle);
23278 })
23279 .log_err();
23280 }
23281
23282 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23283 let settings = ThemeSettings::get_global(cx);
23284 let text_style = TextStyle {
23285 color: if self.prompt.read(cx).read_only(cx) {
23286 cx.theme().colors().text_disabled
23287 } else {
23288 cx.theme().colors().text
23289 },
23290 font_family: settings.buffer_font.family.clone(),
23291 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23292 font_size: settings.buffer_font_size(cx).into(),
23293 font_weight: settings.buffer_font.weight,
23294 line_height: relative(settings.buffer_line_height.value()),
23295 ..Default::default()
23296 };
23297 EditorElement::new(
23298 &self.prompt,
23299 EditorStyle {
23300 background: cx.theme().colors().editor_background,
23301 local_player: cx.theme().players().local(),
23302 text: text_style,
23303 ..Default::default()
23304 },
23305 )
23306 }
23307}
23308
23309impl Render for BreakpointPromptEditor {
23310 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23311 let editor_margins = *self.editor_margins.lock();
23312 let gutter_dimensions = editor_margins.gutter;
23313 h_flex()
23314 .key_context("Editor")
23315 .bg(cx.theme().colors().editor_background)
23316 .border_y_1()
23317 .border_color(cx.theme().status().info_border)
23318 .size_full()
23319 .py(window.line_height() / 2.5)
23320 .on_action(cx.listener(Self::confirm))
23321 .on_action(cx.listener(Self::cancel))
23322 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23323 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23324 }
23325}
23326
23327impl Focusable for BreakpointPromptEditor {
23328 fn focus_handle(&self, cx: &App) -> FocusHandle {
23329 self.prompt.focus_handle(cx)
23330 }
23331}
23332
23333fn all_edits_insertions_or_deletions(
23334 edits: &Vec<(Range<Anchor>, String)>,
23335 snapshot: &MultiBufferSnapshot,
23336) -> bool {
23337 let mut all_insertions = true;
23338 let mut all_deletions = true;
23339
23340 for (range, new_text) in edits.iter() {
23341 let range_is_empty = range.to_offset(&snapshot).is_empty();
23342 let text_is_empty = new_text.is_empty();
23343
23344 if range_is_empty != text_is_empty {
23345 if range_is_empty {
23346 all_deletions = false;
23347 } else {
23348 all_insertions = false;
23349 }
23350 } else {
23351 return false;
23352 }
23353
23354 if !all_insertions && !all_deletions {
23355 return false;
23356 }
23357 }
23358 all_insertions || all_deletions
23359}
23360
23361struct MissingEditPredictionKeybindingTooltip;
23362
23363impl Render for MissingEditPredictionKeybindingTooltip {
23364 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23365 ui::tooltip_container(window, cx, |container, _, cx| {
23366 container
23367 .flex_shrink_0()
23368 .max_w_80()
23369 .min_h(rems_from_px(124.))
23370 .justify_between()
23371 .child(
23372 v_flex()
23373 .flex_1()
23374 .text_ui_sm(cx)
23375 .child(Label::new("Conflict with Accept Keybinding"))
23376 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23377 )
23378 .child(
23379 h_flex()
23380 .pb_1()
23381 .gap_1()
23382 .items_end()
23383 .w_full()
23384 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23385 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23386 }))
23387 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23388 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23389 })),
23390 )
23391 })
23392 }
23393}
23394
23395#[derive(Debug, Clone, Copy, PartialEq)]
23396pub struct LineHighlight {
23397 pub background: Background,
23398 pub border: Option<gpui::Hsla>,
23399 pub include_gutter: bool,
23400 pub type_id: Option<TypeId>,
23401}
23402
23403struct LineManipulationResult {
23404 pub new_text: String,
23405 pub line_count_before: usize,
23406 pub line_count_after: usize,
23407}
23408
23409fn render_diff_hunk_controls(
23410 row: u32,
23411 status: &DiffHunkStatus,
23412 hunk_range: Range<Anchor>,
23413 is_created_file: bool,
23414 line_height: Pixels,
23415 editor: &Entity<Editor>,
23416 _window: &mut Window,
23417 cx: &mut App,
23418) -> AnyElement {
23419 h_flex()
23420 .h(line_height)
23421 .mr_1()
23422 .gap_1()
23423 .px_0p5()
23424 .pb_1()
23425 .border_x_1()
23426 .border_b_1()
23427 .border_color(cx.theme().colors().border_variant)
23428 .rounded_b_lg()
23429 .bg(cx.theme().colors().editor_background)
23430 .gap_1()
23431 .block_mouse_except_scroll()
23432 .shadow_md()
23433 .child(if status.has_secondary_hunk() {
23434 Button::new(("stage", row as u64), "Stage")
23435 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23436 .tooltip({
23437 let focus_handle = editor.focus_handle(cx);
23438 move |window, cx| {
23439 Tooltip::for_action_in(
23440 "Stage Hunk",
23441 &::git::ToggleStaged,
23442 &focus_handle,
23443 window,
23444 cx,
23445 )
23446 }
23447 })
23448 .on_click({
23449 let editor = editor.clone();
23450 move |_event, _window, cx| {
23451 editor.update(cx, |editor, cx| {
23452 editor.stage_or_unstage_diff_hunks(
23453 true,
23454 vec![hunk_range.start..hunk_range.start],
23455 cx,
23456 );
23457 });
23458 }
23459 })
23460 } else {
23461 Button::new(("unstage", row as u64), "Unstage")
23462 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23463 .tooltip({
23464 let focus_handle = editor.focus_handle(cx);
23465 move |window, cx| {
23466 Tooltip::for_action_in(
23467 "Unstage Hunk",
23468 &::git::ToggleStaged,
23469 &focus_handle,
23470 window,
23471 cx,
23472 )
23473 }
23474 })
23475 .on_click({
23476 let editor = editor.clone();
23477 move |_event, _window, cx| {
23478 editor.update(cx, |editor, cx| {
23479 editor.stage_or_unstage_diff_hunks(
23480 false,
23481 vec![hunk_range.start..hunk_range.start],
23482 cx,
23483 );
23484 });
23485 }
23486 })
23487 })
23488 .child(
23489 Button::new(("restore", row as u64), "Restore")
23490 .tooltip({
23491 let focus_handle = editor.focus_handle(cx);
23492 move |window, cx| {
23493 Tooltip::for_action_in(
23494 "Restore Hunk",
23495 &::git::Restore,
23496 &focus_handle,
23497 window,
23498 cx,
23499 )
23500 }
23501 })
23502 .on_click({
23503 let editor = editor.clone();
23504 move |_event, window, cx| {
23505 editor.update(cx, |editor, cx| {
23506 let snapshot = editor.snapshot(window, cx);
23507 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23508 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23509 });
23510 }
23511 })
23512 .disabled(is_created_file),
23513 )
23514 .when(
23515 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23516 |el| {
23517 el.child(
23518 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23519 .shape(IconButtonShape::Square)
23520 .icon_size(IconSize::Small)
23521 // .disabled(!has_multiple_hunks)
23522 .tooltip({
23523 let focus_handle = editor.focus_handle(cx);
23524 move |window, cx| {
23525 Tooltip::for_action_in(
23526 "Next Hunk",
23527 &GoToHunk,
23528 &focus_handle,
23529 window,
23530 cx,
23531 )
23532 }
23533 })
23534 .on_click({
23535 let editor = editor.clone();
23536 move |_event, window, cx| {
23537 editor.update(cx, |editor, cx| {
23538 let snapshot = editor.snapshot(window, cx);
23539 let position =
23540 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23541 editor.go_to_hunk_before_or_after_position(
23542 &snapshot,
23543 position,
23544 Direction::Next,
23545 window,
23546 cx,
23547 );
23548 editor.expand_selected_diff_hunks(cx);
23549 });
23550 }
23551 }),
23552 )
23553 .child(
23554 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23555 .shape(IconButtonShape::Square)
23556 .icon_size(IconSize::Small)
23557 // .disabled(!has_multiple_hunks)
23558 .tooltip({
23559 let focus_handle = editor.focus_handle(cx);
23560 move |window, cx| {
23561 Tooltip::for_action_in(
23562 "Previous Hunk",
23563 &GoToPreviousHunk,
23564 &focus_handle,
23565 window,
23566 cx,
23567 )
23568 }
23569 })
23570 .on_click({
23571 let editor = editor.clone();
23572 move |_event, window, cx| {
23573 editor.update(cx, |editor, cx| {
23574 let snapshot = editor.snapshot(window, cx);
23575 let point =
23576 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23577 editor.go_to_hunk_before_or_after_position(
23578 &snapshot,
23579 point,
23580 Direction::Prev,
23581 window,
23582 cx,
23583 );
23584 editor.expand_selected_diff_hunks(cx);
23585 });
23586 }
23587 }),
23588 )
23589 },
23590 )
23591 .into_any_element()
23592}