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 auto_width: bool,
487 },
488 AutoHeight {
489 min_lines: usize,
490 max_lines: Option<usize>,
491 },
492 Full {
493 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
494 scale_ui_elements_with_buffer_font_size: bool,
495 /// When set to `true`, the editor will render a background for the active line.
496 show_active_line_background: bool,
497 /// When set to `true`, the editor's height will be determined by its content.
498 sized_by_content: bool,
499 },
500 Minimap {
501 parent: WeakEntity<Editor>,
502 },
503}
504
505impl EditorMode {
506 pub fn full() -> Self {
507 Self::Full {
508 scale_ui_elements_with_buffer_font_size: true,
509 show_active_line_background: true,
510 sized_by_content: false,
511 }
512 }
513
514 #[inline]
515 pub fn is_full(&self) -> bool {
516 matches!(self, Self::Full { .. })
517 }
518
519 #[inline]
520 pub fn is_single_line(&self) -> bool {
521 matches!(self, Self::SingleLine { .. })
522 }
523
524 #[inline]
525 fn is_minimap(&self) -> bool {
526 matches!(self, Self::Minimap { .. })
527 }
528}
529
530#[derive(Copy, Clone, Debug)]
531pub enum SoftWrap {
532 /// Prefer not to wrap at all.
533 ///
534 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
535 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
536 GitDiff,
537 /// Prefer a single line generally, unless an overly long line is encountered.
538 None,
539 /// Soft wrap lines that exceed the editor width.
540 EditorWidth,
541 /// Soft wrap lines at the preferred line length.
542 Column(u32),
543 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
544 Bounded(u32),
545}
546
547#[derive(Clone)]
548pub struct EditorStyle {
549 pub background: Hsla,
550 pub local_player: PlayerColor,
551 pub text: TextStyle,
552 pub scrollbar_width: Pixels,
553 pub syntax: Arc<SyntaxTheme>,
554 pub status: StatusColors,
555 pub inlay_hints_style: HighlightStyle,
556 pub inline_completion_styles: InlineCompletionStyles,
557 pub unnecessary_code_fade: f32,
558 pub show_underlines: bool,
559}
560
561impl Default for EditorStyle {
562 fn default() -> Self {
563 Self {
564 background: 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
866/// A set of caret positions, registered when the editor was edited.
867pub struct ChangeList {
868 changes: Vec<Vec<Anchor>>,
869 /// Currently "selected" change.
870 position: Option<usize>,
871}
872
873impl ChangeList {
874 pub fn new() -> Self {
875 Self {
876 changes: Vec::new(),
877 position: None,
878 }
879 }
880
881 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
882 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
883 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
884 if self.changes.is_empty() {
885 return None;
886 }
887
888 let prev = self.position.unwrap_or(self.changes.len());
889 let next = if direction == Direction::Prev {
890 prev.saturating_sub(count)
891 } else {
892 (prev + count).min(self.changes.len() - 1)
893 };
894 self.position = Some(next);
895 self.changes.get(next).map(|anchors| anchors.as_slice())
896 }
897
898 /// Adds a new change to the list, resetting the change list position.
899 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
900 self.position.take();
901 if pop_state {
902 self.changes.pop();
903 }
904 self.changes.push(new_positions.clone());
905 }
906
907 pub fn last(&self) -> Option<&[Anchor]> {
908 self.changes.last().map(|anchors| anchors.as_slice())
909 }
910}
911
912#[derive(Clone)]
913struct InlineBlamePopoverState {
914 scroll_handle: ScrollHandle,
915 commit_message: Option<ParsedCommitMessage>,
916 markdown: Entity<Markdown>,
917}
918
919struct InlineBlamePopover {
920 position: gpui::Point<Pixels>,
921 hide_task: Option<Task<()>>,
922 popover_bounds: Option<Bounds<Pixels>>,
923 popover_state: InlineBlamePopoverState,
924}
925
926enum SelectionDragState {
927 /// State when no drag related activity is detected.
928 None,
929 /// State when the mouse is down on a selection that is about to be dragged.
930 ReadyToDrag {
931 selection: Selection<Anchor>,
932 click_position: gpui::Point<Pixels>,
933 mouse_down_time: Instant,
934 },
935 /// State when the mouse is dragging the selection in the editor.
936 Dragging {
937 selection: Selection<Anchor>,
938 drop_cursor: Selection<Anchor>,
939 hide_drop_cursor: bool,
940 },
941}
942
943enum ColumnarSelectionState {
944 FromMouse {
945 selection_tail: Anchor,
946 display_point: Option<DisplayPoint>,
947 },
948 FromSelection {
949 selection_tail: Anchor,
950 },
951}
952
953/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
954/// a breakpoint on them.
955#[derive(Clone, Copy, Debug, PartialEq, Eq)]
956struct PhantomBreakpointIndicator {
957 display_row: DisplayRow,
958 /// There's a small debounce between hovering over the line and showing the indicator.
959 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
960 is_active: bool,
961 collides_with_existing_breakpoint: bool,
962}
963
964/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
965///
966/// See the [module level documentation](self) for more information.
967pub struct Editor {
968 focus_handle: FocusHandle,
969 last_focused_descendant: Option<WeakFocusHandle>,
970 /// The text buffer being edited
971 buffer: Entity<MultiBuffer>,
972 /// Map of how text in the buffer should be displayed.
973 /// Handles soft wraps, folds, fake inlay text insertions, etc.
974 pub display_map: Entity<DisplayMap>,
975 pub selections: SelectionsCollection,
976 pub scroll_manager: ScrollManager,
977 /// When inline assist editors are linked, they all render cursors because
978 /// typing enters text into each of them, even the ones that aren't focused.
979 pub(crate) show_cursor_when_unfocused: bool,
980 columnar_selection_state: Option<ColumnarSelectionState>,
981 add_selections_state: Option<AddSelectionsState>,
982 select_next_state: Option<SelectNextState>,
983 select_prev_state: Option<SelectNextState>,
984 selection_history: SelectionHistory,
985 defer_selection_effects: bool,
986 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
987 autoclose_regions: Vec<AutocloseRegion>,
988 snippet_stack: InvalidationStack<SnippetState>,
989 select_syntax_node_history: SelectSyntaxNodeHistory,
990 ime_transaction: Option<TransactionId>,
991 pub diagnostics_max_severity: DiagnosticSeverity,
992 active_diagnostics: ActiveDiagnostic,
993 show_inline_diagnostics: bool,
994 inline_diagnostics_update: Task<()>,
995 inline_diagnostics_enabled: bool,
996 diagnostics_enabled: bool,
997 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
998 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
999 hard_wrap: Option<usize>,
1000
1001 // TODO: make this a access method
1002 pub project: Option<Entity<Project>>,
1003 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1004 completion_provider: Option<Rc<dyn CompletionProvider>>,
1005 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1006 blink_manager: Entity<BlinkManager>,
1007 show_cursor_names: bool,
1008 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1009 pub show_local_selections: bool,
1010 mode: EditorMode,
1011 show_breadcrumbs: bool,
1012 show_gutter: bool,
1013 show_scrollbars: ScrollbarAxes,
1014 minimap_visibility: MinimapVisibility,
1015 offset_content: bool,
1016 disable_expand_excerpt_buttons: bool,
1017 show_line_numbers: Option<bool>,
1018 use_relative_line_numbers: Option<bool>,
1019 show_git_diff_gutter: Option<bool>,
1020 show_code_actions: Option<bool>,
1021 show_runnables: Option<bool>,
1022 show_breakpoints: Option<bool>,
1023 show_wrap_guides: Option<bool>,
1024 show_indent_guides: Option<bool>,
1025 placeholder_text: Option<Arc<str>>,
1026 highlight_order: usize,
1027 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1028 background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
1029 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1030 scrollbar_marker_state: ScrollbarMarkerState,
1031 active_indent_guides_state: ActiveIndentGuidesState,
1032 nav_history: Option<ItemNavHistory>,
1033 context_menu: RefCell<Option<CodeContextMenu>>,
1034 context_menu_options: Option<ContextMenuOptions>,
1035 mouse_context_menu: Option<MouseContextMenu>,
1036 completion_tasks: Vec<(CompletionId, Task<()>)>,
1037 inline_blame_popover: Option<InlineBlamePopover>,
1038 inline_blame_popover_show_task: Option<Task<()>>,
1039 signature_help_state: SignatureHelpState,
1040 auto_signature_help: Option<bool>,
1041 find_all_references_task_sources: Vec<Anchor>,
1042 next_completion_id: CompletionId,
1043 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1044 code_actions_task: Option<Task<Result<()>>>,
1045 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1046 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1047 document_highlights_task: Option<Task<()>>,
1048 linked_editing_range_task: Option<Task<Option<()>>>,
1049 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1050 pending_rename: Option<RenameState>,
1051 searchable: bool,
1052 cursor_shape: CursorShape,
1053 current_line_highlight: Option<CurrentLineHighlight>,
1054 collapse_matches: bool,
1055 autoindent_mode: Option<AutoindentMode>,
1056 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1057 input_enabled: bool,
1058 use_modal_editing: bool,
1059 read_only: bool,
1060 leader_id: Option<CollaboratorId>,
1061 remote_id: Option<ViewId>,
1062 pub hover_state: HoverState,
1063 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1064 gutter_hovered: bool,
1065 hovered_link_state: Option<HoveredLinkState>,
1066 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1067 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1068 active_inline_completion: Option<InlineCompletionState>,
1069 /// Used to prevent flickering as the user types while the menu is open
1070 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1071 edit_prediction_settings: EditPredictionSettings,
1072 inline_completions_hidden_for_vim_mode: bool,
1073 show_inline_completions_override: Option<bool>,
1074 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1075 edit_prediction_preview: EditPredictionPreview,
1076 edit_prediction_indent_conflict: bool,
1077 edit_prediction_requires_modifier_in_indent_conflict: bool,
1078 inlay_hint_cache: InlayHintCache,
1079 next_inlay_id: usize,
1080 _subscriptions: Vec<Subscription>,
1081 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1082 gutter_dimensions: GutterDimensions,
1083 style: Option<EditorStyle>,
1084 text_style_refinement: Option<TextStyleRefinement>,
1085 next_editor_action_id: EditorActionId,
1086 editor_actions: Rc<
1087 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1088 >,
1089 use_autoclose: bool,
1090 use_auto_surround: bool,
1091 auto_replace_emoji_shortcode: bool,
1092 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1093 show_git_blame_gutter: bool,
1094 show_git_blame_inline: bool,
1095 show_git_blame_inline_delay_task: Option<Task<()>>,
1096 git_blame_inline_enabled: bool,
1097 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1098 serialize_dirty_buffers: bool,
1099 show_selection_menu: Option<bool>,
1100 blame: Option<Entity<GitBlame>>,
1101 blame_subscription: Option<Subscription>,
1102 custom_context_menu: Option<
1103 Box<
1104 dyn 'static
1105 + Fn(
1106 &mut Self,
1107 DisplayPoint,
1108 &mut Window,
1109 &mut Context<Self>,
1110 ) -> Option<Entity<ui::ContextMenu>>,
1111 >,
1112 >,
1113 last_bounds: Option<Bounds<Pixels>>,
1114 last_position_map: Option<Rc<PositionMap>>,
1115 expect_bounds_change: Option<Bounds<Pixels>>,
1116 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1117 tasks_update_task: Option<Task<()>>,
1118 breakpoint_store: Option<Entity<BreakpointStore>>,
1119 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1120 hovered_diff_hunk_row: Option<DisplayRow>,
1121 pull_diagnostics_task: Task<()>,
1122 in_project_search: bool,
1123 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1124 breadcrumb_header: Option<String>,
1125 focused_block: Option<FocusedBlock>,
1126 next_scroll_position: NextScrollCursorCenterTopBottom,
1127 addons: HashMap<TypeId, Box<dyn Addon>>,
1128 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1129 load_diff_task: Option<Shared<Task<()>>>,
1130 /// Whether we are temporarily displaying a diff other than git's
1131 temporary_diff_override: bool,
1132 selection_mark_mode: bool,
1133 toggle_fold_multiple_buffers: Task<()>,
1134 _scroll_cursor_center_top_bottom_task: Task<()>,
1135 serialize_selections: Task<()>,
1136 serialize_folds: Task<()>,
1137 mouse_cursor_hidden: bool,
1138 minimap: Option<Entity<Self>>,
1139 hide_mouse_mode: HideMouseMode,
1140 pub change_list: ChangeList,
1141 inline_value_cache: InlineValueCache,
1142 selection_drag_state: SelectionDragState,
1143 drag_and_drop_selection_enabled: bool,
1144 next_color_inlay_id: usize,
1145 colors: Option<LspColorData>,
1146}
1147
1148#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1149enum NextScrollCursorCenterTopBottom {
1150 #[default]
1151 Center,
1152 Top,
1153 Bottom,
1154}
1155
1156impl NextScrollCursorCenterTopBottom {
1157 fn next(&self) -> Self {
1158 match self {
1159 Self::Center => Self::Top,
1160 Self::Top => Self::Bottom,
1161 Self::Bottom => Self::Center,
1162 }
1163 }
1164}
1165
1166#[derive(Clone)]
1167pub struct EditorSnapshot {
1168 pub mode: EditorMode,
1169 show_gutter: bool,
1170 show_line_numbers: Option<bool>,
1171 show_git_diff_gutter: Option<bool>,
1172 show_code_actions: Option<bool>,
1173 show_runnables: Option<bool>,
1174 show_breakpoints: Option<bool>,
1175 git_blame_gutter_max_author_length: Option<usize>,
1176 pub display_snapshot: DisplaySnapshot,
1177 pub placeholder_text: Option<Arc<str>>,
1178 is_focused: bool,
1179 scroll_anchor: ScrollAnchor,
1180 ongoing_scroll: OngoingScroll,
1181 current_line_highlight: CurrentLineHighlight,
1182 gutter_hovered: bool,
1183}
1184
1185#[derive(Default, Debug, Clone, Copy)]
1186pub struct GutterDimensions {
1187 pub left_padding: Pixels,
1188 pub right_padding: Pixels,
1189 pub width: Pixels,
1190 pub margin: Pixels,
1191 pub git_blame_entries_width: Option<Pixels>,
1192}
1193
1194impl GutterDimensions {
1195 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1196 Self {
1197 margin: Self::default_gutter_margin(font_id, font_size, cx),
1198 ..Default::default()
1199 }
1200 }
1201
1202 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1203 -cx.text_system().descent(font_id, font_size)
1204 }
1205 /// The full width of the space taken up by the gutter.
1206 pub fn full_width(&self) -> Pixels {
1207 self.margin + self.width
1208 }
1209
1210 /// The width of the space reserved for the fold indicators,
1211 /// use alongside 'justify_end' and `gutter_width` to
1212 /// right align content with the line numbers
1213 pub fn fold_area_width(&self) -> Pixels {
1214 self.margin + self.right_padding
1215 }
1216}
1217
1218#[derive(Debug)]
1219pub struct RemoteSelection {
1220 pub replica_id: ReplicaId,
1221 pub selection: Selection<Anchor>,
1222 pub cursor_shape: CursorShape,
1223 pub collaborator_id: CollaboratorId,
1224 pub line_mode: bool,
1225 pub user_name: Option<SharedString>,
1226 pub color: PlayerColor,
1227}
1228
1229#[derive(Clone, Debug)]
1230struct SelectionHistoryEntry {
1231 selections: Arc<[Selection<Anchor>]>,
1232 select_next_state: Option<SelectNextState>,
1233 select_prev_state: Option<SelectNextState>,
1234 add_selections_state: Option<AddSelectionsState>,
1235}
1236
1237#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1238enum SelectionHistoryMode {
1239 Normal,
1240 Undoing,
1241 Redoing,
1242 Skipping,
1243}
1244
1245#[derive(Clone, PartialEq, Eq, Hash)]
1246struct HoveredCursor {
1247 replica_id: u16,
1248 selection_id: usize,
1249}
1250
1251impl Default for SelectionHistoryMode {
1252 fn default() -> Self {
1253 Self::Normal
1254 }
1255}
1256
1257#[derive(Debug)]
1258pub struct SelectionEffects {
1259 nav_history: bool,
1260 completions: bool,
1261 scroll: Option<Autoscroll>,
1262}
1263
1264impl Default for SelectionEffects {
1265 fn default() -> Self {
1266 Self {
1267 nav_history: true,
1268 completions: true,
1269 scroll: Some(Autoscroll::fit()),
1270 }
1271 }
1272}
1273impl SelectionEffects {
1274 pub fn scroll(scroll: Autoscroll) -> Self {
1275 Self {
1276 scroll: Some(scroll),
1277 ..Default::default()
1278 }
1279 }
1280
1281 pub fn no_scroll() -> Self {
1282 Self {
1283 scroll: None,
1284 ..Default::default()
1285 }
1286 }
1287
1288 pub fn completions(self, completions: bool) -> Self {
1289 Self {
1290 completions,
1291 ..self
1292 }
1293 }
1294
1295 pub fn nav_history(self, nav_history: bool) -> Self {
1296 Self {
1297 nav_history,
1298 ..self
1299 }
1300 }
1301}
1302
1303struct DeferredSelectionEffectsState {
1304 changed: bool,
1305 effects: SelectionEffects,
1306 old_cursor_position: Anchor,
1307 history_entry: SelectionHistoryEntry,
1308}
1309
1310#[derive(Default)]
1311struct SelectionHistory {
1312 #[allow(clippy::type_complexity)]
1313 selections_by_transaction:
1314 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1315 mode: SelectionHistoryMode,
1316 undo_stack: VecDeque<SelectionHistoryEntry>,
1317 redo_stack: VecDeque<SelectionHistoryEntry>,
1318}
1319
1320impl SelectionHistory {
1321 #[track_caller]
1322 fn insert_transaction(
1323 &mut self,
1324 transaction_id: TransactionId,
1325 selections: Arc<[Selection<Anchor>]>,
1326 ) {
1327 if selections.is_empty() {
1328 log::error!(
1329 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1330 std::panic::Location::caller()
1331 );
1332 return;
1333 }
1334 self.selections_by_transaction
1335 .insert(transaction_id, (selections, None));
1336 }
1337
1338 #[allow(clippy::type_complexity)]
1339 fn transaction(
1340 &self,
1341 transaction_id: TransactionId,
1342 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1343 self.selections_by_transaction.get(&transaction_id)
1344 }
1345
1346 #[allow(clippy::type_complexity)]
1347 fn transaction_mut(
1348 &mut self,
1349 transaction_id: TransactionId,
1350 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1351 self.selections_by_transaction.get_mut(&transaction_id)
1352 }
1353
1354 fn push(&mut self, entry: SelectionHistoryEntry) {
1355 if !entry.selections.is_empty() {
1356 match self.mode {
1357 SelectionHistoryMode::Normal => {
1358 self.push_undo(entry);
1359 self.redo_stack.clear();
1360 }
1361 SelectionHistoryMode::Undoing => self.push_redo(entry),
1362 SelectionHistoryMode::Redoing => self.push_undo(entry),
1363 SelectionHistoryMode::Skipping => {}
1364 }
1365 }
1366 }
1367
1368 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1369 if self
1370 .undo_stack
1371 .back()
1372 .map_or(true, |e| e.selections != entry.selections)
1373 {
1374 self.undo_stack.push_back(entry);
1375 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1376 self.undo_stack.pop_front();
1377 }
1378 }
1379 }
1380
1381 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1382 if self
1383 .redo_stack
1384 .back()
1385 .map_or(true, |e| e.selections != entry.selections)
1386 {
1387 self.redo_stack.push_back(entry);
1388 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1389 self.redo_stack.pop_front();
1390 }
1391 }
1392 }
1393}
1394
1395#[derive(Clone, Copy)]
1396pub struct RowHighlightOptions {
1397 pub autoscroll: bool,
1398 pub include_gutter: bool,
1399}
1400
1401impl Default for RowHighlightOptions {
1402 fn default() -> Self {
1403 Self {
1404 autoscroll: Default::default(),
1405 include_gutter: true,
1406 }
1407 }
1408}
1409
1410struct RowHighlight {
1411 index: usize,
1412 range: Range<Anchor>,
1413 color: Hsla,
1414 options: RowHighlightOptions,
1415 type_id: TypeId,
1416}
1417
1418#[derive(Clone, Debug)]
1419struct AddSelectionsState {
1420 groups: Vec<AddSelectionsGroup>,
1421}
1422
1423#[derive(Clone, Debug)]
1424struct AddSelectionsGroup {
1425 above: bool,
1426 stack: Vec<usize>,
1427}
1428
1429#[derive(Clone)]
1430struct SelectNextState {
1431 query: AhoCorasick,
1432 wordwise: bool,
1433 done: bool,
1434}
1435
1436impl std::fmt::Debug for SelectNextState {
1437 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1438 f.debug_struct(std::any::type_name::<Self>())
1439 .field("wordwise", &self.wordwise)
1440 .field("done", &self.done)
1441 .finish()
1442 }
1443}
1444
1445#[derive(Debug)]
1446struct AutocloseRegion {
1447 selection_id: usize,
1448 range: Range<Anchor>,
1449 pair: BracketPair,
1450}
1451
1452#[derive(Debug)]
1453struct SnippetState {
1454 ranges: Vec<Vec<Range<Anchor>>>,
1455 active_index: usize,
1456 choices: Vec<Option<Vec<String>>>,
1457}
1458
1459#[doc(hidden)]
1460pub struct RenameState {
1461 pub range: Range<Anchor>,
1462 pub old_name: Arc<str>,
1463 pub editor: Entity<Editor>,
1464 block_id: CustomBlockId,
1465}
1466
1467struct InvalidationStack<T>(Vec<T>);
1468
1469struct RegisteredInlineCompletionProvider {
1470 provider: Arc<dyn InlineCompletionProviderHandle>,
1471 _subscription: Subscription,
1472}
1473
1474#[derive(Debug, PartialEq, Eq)]
1475pub struct ActiveDiagnosticGroup {
1476 pub active_range: Range<Anchor>,
1477 pub active_message: String,
1478 pub group_id: usize,
1479 pub blocks: HashSet<CustomBlockId>,
1480}
1481
1482#[derive(Debug, PartialEq, Eq)]
1483
1484pub(crate) enum ActiveDiagnostic {
1485 None,
1486 All,
1487 Group(ActiveDiagnosticGroup),
1488}
1489
1490#[derive(Serialize, Deserialize, Clone, Debug)]
1491pub struct ClipboardSelection {
1492 /// The number of bytes in this selection.
1493 pub len: usize,
1494 /// Whether this was a full-line selection.
1495 pub is_entire_line: bool,
1496 /// The indentation of the first line when this content was originally copied.
1497 pub first_line_indent: u32,
1498}
1499
1500// selections, scroll behavior, was newest selection reversed
1501type SelectSyntaxNodeHistoryState = (
1502 Box<[Selection<usize>]>,
1503 SelectSyntaxNodeScrollBehavior,
1504 bool,
1505);
1506
1507#[derive(Default)]
1508struct SelectSyntaxNodeHistory {
1509 stack: Vec<SelectSyntaxNodeHistoryState>,
1510 // disable temporarily to allow changing selections without losing the stack
1511 pub disable_clearing: bool,
1512}
1513
1514impl SelectSyntaxNodeHistory {
1515 pub fn try_clear(&mut self) {
1516 if !self.disable_clearing {
1517 self.stack.clear();
1518 }
1519 }
1520
1521 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1522 self.stack.push(selection);
1523 }
1524
1525 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1526 self.stack.pop()
1527 }
1528}
1529
1530enum SelectSyntaxNodeScrollBehavior {
1531 CursorTop,
1532 FitSelection,
1533 CursorBottom,
1534}
1535
1536#[derive(Debug)]
1537pub(crate) struct NavigationData {
1538 cursor_anchor: Anchor,
1539 cursor_position: Point,
1540 scroll_anchor: ScrollAnchor,
1541 scroll_top_row: u32,
1542}
1543
1544#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1545pub enum GotoDefinitionKind {
1546 Symbol,
1547 Declaration,
1548 Type,
1549 Implementation,
1550}
1551
1552#[derive(Debug, Clone)]
1553enum InlayHintRefreshReason {
1554 ModifiersChanged(bool),
1555 Toggle(bool),
1556 SettingsChange(InlayHintSettings),
1557 NewLinesShown,
1558 BufferEdited(HashSet<Arc<Language>>),
1559 RefreshRequested,
1560 ExcerptsRemoved(Vec<ExcerptId>),
1561}
1562
1563impl InlayHintRefreshReason {
1564 fn description(&self) -> &'static str {
1565 match self {
1566 Self::ModifiersChanged(_) => "modifiers changed",
1567 Self::Toggle(_) => "toggle",
1568 Self::SettingsChange(_) => "settings change",
1569 Self::NewLinesShown => "new lines shown",
1570 Self::BufferEdited(_) => "buffer edited",
1571 Self::RefreshRequested => "refresh requested",
1572 Self::ExcerptsRemoved(_) => "excerpts removed",
1573 }
1574 }
1575}
1576
1577pub enum FormatTarget {
1578 Buffers(HashSet<Entity<Buffer>>),
1579 Ranges(Vec<Range<MultiBufferPoint>>),
1580}
1581
1582pub(crate) struct FocusedBlock {
1583 id: BlockId,
1584 focus_handle: WeakFocusHandle,
1585}
1586
1587#[derive(Clone)]
1588enum JumpData {
1589 MultiBufferRow {
1590 row: MultiBufferRow,
1591 line_offset_from_top: u32,
1592 },
1593 MultiBufferPoint {
1594 excerpt_id: ExcerptId,
1595 position: Point,
1596 anchor: text::Anchor,
1597 line_offset_from_top: u32,
1598 },
1599}
1600
1601pub enum MultibufferSelectionMode {
1602 First,
1603 All,
1604}
1605
1606#[derive(Clone, Copy, Debug, Default)]
1607pub struct RewrapOptions {
1608 pub override_language_settings: bool,
1609 pub preserve_existing_whitespace: bool,
1610}
1611
1612impl Editor {
1613 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1614 let buffer = cx.new(|cx| Buffer::local("", cx));
1615 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1616 Self::new(
1617 EditorMode::SingleLine { auto_width: false },
1618 buffer,
1619 None,
1620 window,
1621 cx,
1622 )
1623 }
1624
1625 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1626 let buffer = cx.new(|cx| Buffer::local("", cx));
1627 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1628 Self::new(EditorMode::full(), buffer, None, window, cx)
1629 }
1630
1631 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1632 let buffer = cx.new(|cx| Buffer::local("", cx));
1633 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1634 Self::new(
1635 EditorMode::SingleLine { auto_width: true },
1636 buffer,
1637 None,
1638 window,
1639 cx,
1640 )
1641 }
1642
1643 pub fn auto_height(
1644 min_lines: usize,
1645 max_lines: usize,
1646 window: &mut Window,
1647 cx: &mut Context<Self>,
1648 ) -> Self {
1649 let buffer = cx.new(|cx| Buffer::local("", cx));
1650 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1651 Self::new(
1652 EditorMode::AutoHeight {
1653 min_lines,
1654 max_lines: Some(max_lines),
1655 },
1656 buffer,
1657 None,
1658 window,
1659 cx,
1660 )
1661 }
1662
1663 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1664 /// The editor grows as tall as needed to fit its content.
1665 pub fn auto_height_unbounded(
1666 min_lines: usize,
1667 window: &mut Window,
1668 cx: &mut Context<Self>,
1669 ) -> Self {
1670 let buffer = cx.new(|cx| Buffer::local("", cx));
1671 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1672 Self::new(
1673 EditorMode::AutoHeight {
1674 min_lines,
1675 max_lines: None,
1676 },
1677 buffer,
1678 None,
1679 window,
1680 cx,
1681 )
1682 }
1683
1684 pub fn for_buffer(
1685 buffer: Entity<Buffer>,
1686 project: Option<Entity<Project>>,
1687 window: &mut Window,
1688 cx: &mut Context<Self>,
1689 ) -> Self {
1690 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1691 Self::new(EditorMode::full(), buffer, project, window, cx)
1692 }
1693
1694 pub fn for_multibuffer(
1695 buffer: Entity<MultiBuffer>,
1696 project: Option<Entity<Project>>,
1697 window: &mut Window,
1698 cx: &mut Context<Self>,
1699 ) -> Self {
1700 Self::new(EditorMode::full(), buffer, project, window, cx)
1701 }
1702
1703 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1704 let mut clone = Self::new(
1705 self.mode.clone(),
1706 self.buffer.clone(),
1707 self.project.clone(),
1708 window,
1709 cx,
1710 );
1711 self.display_map.update(cx, |display_map, cx| {
1712 let snapshot = display_map.snapshot(cx);
1713 clone.display_map.update(cx, |display_map, cx| {
1714 display_map.set_state(&snapshot, cx);
1715 });
1716 });
1717 clone.folds_did_change(cx);
1718 clone.selections.clone_state(&self.selections);
1719 clone.scroll_manager.clone_state(&self.scroll_manager);
1720 clone.searchable = self.searchable;
1721 clone.read_only = self.read_only;
1722 clone
1723 }
1724
1725 pub fn new(
1726 mode: EditorMode,
1727 buffer: Entity<MultiBuffer>,
1728 project: Option<Entity<Project>>,
1729 window: &mut Window,
1730 cx: &mut Context<Self>,
1731 ) -> Self {
1732 Editor::new_internal(mode, buffer, project, None, window, cx)
1733 }
1734
1735 fn new_internal(
1736 mode: EditorMode,
1737 buffer: Entity<MultiBuffer>,
1738 project: Option<Entity<Project>>,
1739 display_map: Option<Entity<DisplayMap>>,
1740 window: &mut Window,
1741 cx: &mut Context<Self>,
1742 ) -> Self {
1743 debug_assert!(
1744 display_map.is_none() || mode.is_minimap(),
1745 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1746 );
1747
1748 let full_mode = mode.is_full();
1749 let diagnostics_max_severity = if full_mode {
1750 EditorSettings::get_global(cx)
1751 .diagnostics_max_severity
1752 .unwrap_or(DiagnosticSeverity::Hint)
1753 } else {
1754 DiagnosticSeverity::Off
1755 };
1756 let style = window.text_style();
1757 let font_size = style.font_size.to_pixels(window.rem_size());
1758 let editor = cx.entity().downgrade();
1759 let fold_placeholder = FoldPlaceholder {
1760 constrain_width: true,
1761 render: Arc::new(move |fold_id, fold_range, cx| {
1762 let editor = editor.clone();
1763 div()
1764 .id(fold_id)
1765 .bg(cx.theme().colors().ghost_element_background)
1766 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1767 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1768 .rounded_xs()
1769 .size_full()
1770 .cursor_pointer()
1771 .child("⋯")
1772 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1773 .on_click(move |_, _window, cx| {
1774 editor
1775 .update(cx, |editor, cx| {
1776 editor.unfold_ranges(
1777 &[fold_range.start..fold_range.end],
1778 true,
1779 false,
1780 cx,
1781 );
1782 cx.stop_propagation();
1783 })
1784 .ok();
1785 })
1786 .into_any()
1787 }),
1788 merge_adjacent: true,
1789 ..FoldPlaceholder::default()
1790 };
1791 let display_map = display_map.unwrap_or_else(|| {
1792 cx.new(|cx| {
1793 DisplayMap::new(
1794 buffer.clone(),
1795 style.font(),
1796 font_size,
1797 None,
1798 FILE_HEADER_HEIGHT,
1799 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1800 fold_placeholder,
1801 diagnostics_max_severity,
1802 cx,
1803 )
1804 })
1805 });
1806
1807 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1808
1809 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1810
1811 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1812 .then(|| language_settings::SoftWrap::None);
1813
1814 let mut project_subscriptions = Vec::new();
1815 if mode.is_full() {
1816 if let Some(project) = project.as_ref() {
1817 project_subscriptions.push(cx.subscribe_in(
1818 project,
1819 window,
1820 |editor, _, event, window, cx| match event {
1821 project::Event::RefreshCodeLens => {
1822 // we always query lens with actions, without storing them, always refreshing them
1823 }
1824 project::Event::RefreshInlayHints => {
1825 editor
1826 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1827 }
1828 project::Event::LanguageServerAdded(server_id, ..)
1829 | project::Event::LanguageServerRemoved(server_id) => {
1830 if editor.tasks_update_task.is_none() {
1831 editor.tasks_update_task =
1832 Some(editor.refresh_runnables(window, cx));
1833 }
1834 editor.update_lsp_data(Some(*server_id), None, window, cx);
1835 }
1836 project::Event::SnippetEdit(id, snippet_edits) => {
1837 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1838 let focus_handle = editor.focus_handle(cx);
1839 if focus_handle.is_focused(window) {
1840 let snapshot = buffer.read(cx).snapshot();
1841 for (range, snippet) in snippet_edits {
1842 let editor_range =
1843 language::range_from_lsp(*range).to_offset(&snapshot);
1844 editor
1845 .insert_snippet(
1846 &[editor_range],
1847 snippet.clone(),
1848 window,
1849 cx,
1850 )
1851 .ok();
1852 }
1853 }
1854 }
1855 }
1856 _ => {}
1857 },
1858 ));
1859 if let Some(task_inventory) = project
1860 .read(cx)
1861 .task_store()
1862 .read(cx)
1863 .task_inventory()
1864 .cloned()
1865 {
1866 project_subscriptions.push(cx.observe_in(
1867 &task_inventory,
1868 window,
1869 |editor, _, window, cx| {
1870 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1871 },
1872 ));
1873 };
1874
1875 project_subscriptions.push(cx.subscribe_in(
1876 &project.read(cx).breakpoint_store(),
1877 window,
1878 |editor, _, event, window, cx| match event {
1879 BreakpointStoreEvent::ClearDebugLines => {
1880 editor.clear_row_highlights::<ActiveDebugLine>();
1881 editor.refresh_inline_values(cx);
1882 }
1883 BreakpointStoreEvent::SetDebugLine => {
1884 if editor.go_to_active_debug_line(window, cx) {
1885 cx.stop_propagation();
1886 }
1887
1888 editor.refresh_inline_values(cx);
1889 }
1890 _ => {}
1891 },
1892 ));
1893 let git_store = project.read(cx).git_store().clone();
1894 let project = project.clone();
1895 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1896 match event {
1897 GitStoreEvent::RepositoryUpdated(
1898 _,
1899 RepositoryEvent::Updated {
1900 new_instance: true, ..
1901 },
1902 _,
1903 ) => {
1904 this.load_diff_task = Some(
1905 update_uncommitted_diff_for_buffer(
1906 cx.entity(),
1907 &project,
1908 this.buffer.read(cx).all_buffers(),
1909 this.buffer.clone(),
1910 cx,
1911 )
1912 .shared(),
1913 );
1914 }
1915 _ => {}
1916 }
1917 }));
1918 }
1919 }
1920
1921 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1922
1923 let inlay_hint_settings =
1924 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1925 let focus_handle = cx.focus_handle();
1926 cx.on_focus(&focus_handle, window, Self::handle_focus)
1927 .detach();
1928 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1929 .detach();
1930 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1931 .detach();
1932 cx.on_blur(&focus_handle, window, Self::handle_blur)
1933 .detach();
1934 cx.observe_pending_input(window, Self::observe_pending_input)
1935 .detach();
1936
1937 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1938 Some(false)
1939 } else {
1940 None
1941 };
1942
1943 let breakpoint_store = match (&mode, project.as_ref()) {
1944 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1945 _ => None,
1946 };
1947
1948 let mut code_action_providers = Vec::new();
1949 let mut load_uncommitted_diff = None;
1950 if let Some(project) = project.clone() {
1951 load_uncommitted_diff = Some(
1952 update_uncommitted_diff_for_buffer(
1953 cx.entity(),
1954 &project,
1955 buffer.read(cx).all_buffers(),
1956 buffer.clone(),
1957 cx,
1958 )
1959 .shared(),
1960 );
1961 code_action_providers.push(Rc::new(project) as Rc<_>);
1962 }
1963
1964 let mut editor = Self {
1965 focus_handle,
1966 show_cursor_when_unfocused: false,
1967 last_focused_descendant: None,
1968 buffer: buffer.clone(),
1969 display_map: display_map.clone(),
1970 selections,
1971 scroll_manager: ScrollManager::new(cx),
1972 columnar_selection_state: None,
1973 add_selections_state: None,
1974 select_next_state: None,
1975 select_prev_state: None,
1976 selection_history: SelectionHistory::default(),
1977 defer_selection_effects: false,
1978 deferred_selection_effects_state: None,
1979 autoclose_regions: Vec::new(),
1980 snippet_stack: InvalidationStack::default(),
1981 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1982 ime_transaction: None,
1983 active_diagnostics: ActiveDiagnostic::None,
1984 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1985 inline_diagnostics_update: Task::ready(()),
1986 inline_diagnostics: Vec::new(),
1987 soft_wrap_mode_override,
1988 diagnostics_max_severity,
1989 hard_wrap: None,
1990 completion_provider: project.clone().map(|project| Rc::new(project) as _),
1991 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1992 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1993 project,
1994 blink_manager: blink_manager.clone(),
1995 show_local_selections: true,
1996 show_scrollbars: ScrollbarAxes {
1997 horizontal: full_mode,
1998 vertical: full_mode,
1999 },
2000 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2001 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
2002 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2003 show_gutter: mode.is_full(),
2004 show_line_numbers: None,
2005 use_relative_line_numbers: None,
2006 disable_expand_excerpt_buttons: false,
2007 show_git_diff_gutter: None,
2008 show_code_actions: None,
2009 show_runnables: None,
2010 show_breakpoints: None,
2011 show_wrap_guides: None,
2012 show_indent_guides,
2013 placeholder_text: None,
2014 highlight_order: 0,
2015 highlighted_rows: HashMap::default(),
2016 background_highlights: TreeMap::default(),
2017 gutter_highlights: TreeMap::default(),
2018 scrollbar_marker_state: ScrollbarMarkerState::default(),
2019 active_indent_guides_state: ActiveIndentGuidesState::default(),
2020 nav_history: None,
2021 context_menu: RefCell::new(None),
2022 context_menu_options: None,
2023 mouse_context_menu: None,
2024 completion_tasks: Vec::new(),
2025 inline_blame_popover: None,
2026 inline_blame_popover_show_task: None,
2027 signature_help_state: SignatureHelpState::default(),
2028 auto_signature_help: None,
2029 find_all_references_task_sources: Vec::new(),
2030 next_completion_id: 0,
2031 next_inlay_id: 0,
2032 code_action_providers,
2033 available_code_actions: None,
2034 code_actions_task: None,
2035 quick_selection_highlight_task: None,
2036 debounced_selection_highlight_task: None,
2037 document_highlights_task: None,
2038 linked_editing_range_task: None,
2039 pending_rename: None,
2040 searchable: true,
2041 cursor_shape: EditorSettings::get_global(cx)
2042 .cursor_shape
2043 .unwrap_or_default(),
2044 current_line_highlight: None,
2045 autoindent_mode: Some(AutoindentMode::EachLine),
2046 collapse_matches: false,
2047 workspace: None,
2048 input_enabled: true,
2049 use_modal_editing: mode.is_full(),
2050 read_only: mode.is_minimap(),
2051 use_autoclose: true,
2052 use_auto_surround: true,
2053 auto_replace_emoji_shortcode: false,
2054 jsx_tag_auto_close_enabled_in_any_buffer: false,
2055 leader_id: None,
2056 remote_id: None,
2057 hover_state: HoverState::default(),
2058 pending_mouse_down: None,
2059 hovered_link_state: None,
2060 edit_prediction_provider: None,
2061 active_inline_completion: None,
2062 stale_inline_completion_in_menu: None,
2063 edit_prediction_preview: EditPredictionPreview::Inactive {
2064 released_too_fast: false,
2065 },
2066 inline_diagnostics_enabled: mode.is_full(),
2067 diagnostics_enabled: mode.is_full(),
2068 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2069 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2070
2071 gutter_hovered: false,
2072 pixel_position_of_newest_cursor: None,
2073 last_bounds: None,
2074 last_position_map: None,
2075 expect_bounds_change: None,
2076 gutter_dimensions: GutterDimensions::default(),
2077 style: None,
2078 show_cursor_names: false,
2079 hovered_cursors: HashMap::default(),
2080 next_editor_action_id: EditorActionId::default(),
2081 editor_actions: Rc::default(),
2082 inline_completions_hidden_for_vim_mode: false,
2083 show_inline_completions_override: None,
2084 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
2085 edit_prediction_settings: EditPredictionSettings::Disabled,
2086 edit_prediction_indent_conflict: false,
2087 edit_prediction_requires_modifier_in_indent_conflict: true,
2088 custom_context_menu: None,
2089 show_git_blame_gutter: false,
2090 show_git_blame_inline: false,
2091 show_selection_menu: None,
2092 show_git_blame_inline_delay_task: None,
2093 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2094 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2095 serialize_dirty_buffers: !mode.is_minimap()
2096 && ProjectSettings::get_global(cx)
2097 .session
2098 .restore_unsaved_buffers,
2099 blame: None,
2100 blame_subscription: None,
2101 tasks: BTreeMap::default(),
2102
2103 breakpoint_store,
2104 gutter_breakpoint_indicator: (None, None),
2105 hovered_diff_hunk_row: None,
2106 _subscriptions: vec![
2107 cx.observe(&buffer, Self::on_buffer_changed),
2108 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2109 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2110 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2111 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2112 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2113 cx.observe_window_activation(window, |editor, window, cx| {
2114 let active = window.is_window_active();
2115 editor.blink_manager.update(cx, |blink_manager, cx| {
2116 if active {
2117 blink_manager.enable(cx);
2118 } else {
2119 blink_manager.disable(cx);
2120 }
2121 });
2122 if active {
2123 editor.show_mouse_cursor(cx);
2124 }
2125 }),
2126 ],
2127 tasks_update_task: None,
2128 pull_diagnostics_task: Task::ready(()),
2129 colors: None,
2130 next_color_inlay_id: 0,
2131 linked_edit_ranges: Default::default(),
2132 in_project_search: false,
2133 previous_search_ranges: None,
2134 breadcrumb_header: None,
2135 focused_block: None,
2136 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2137 addons: HashMap::default(),
2138 registered_buffers: HashMap::default(),
2139 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2140 selection_mark_mode: false,
2141 toggle_fold_multiple_buffers: Task::ready(()),
2142 serialize_selections: Task::ready(()),
2143 serialize_folds: Task::ready(()),
2144 text_style_refinement: None,
2145 load_diff_task: load_uncommitted_diff,
2146 temporary_diff_override: false,
2147 mouse_cursor_hidden: false,
2148 minimap: None,
2149 hide_mouse_mode: EditorSettings::get_global(cx)
2150 .hide_mouse
2151 .unwrap_or_default(),
2152 change_list: ChangeList::new(),
2153 mode,
2154 selection_drag_state: SelectionDragState::None,
2155 drag_and_drop_selection_enabled: EditorSettings::get_global(cx).drag_and_drop_selection,
2156 };
2157 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2158 editor
2159 ._subscriptions
2160 .push(cx.observe(breakpoints, |_, _, cx| {
2161 cx.notify();
2162 }));
2163 }
2164 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2165 editor._subscriptions.extend(project_subscriptions);
2166
2167 editor._subscriptions.push(cx.subscribe_in(
2168 &cx.entity(),
2169 window,
2170 |editor, _, e: &EditorEvent, window, cx| match e {
2171 EditorEvent::ScrollPositionChanged { local, .. } => {
2172 if *local {
2173 let new_anchor = editor.scroll_manager.anchor();
2174 let snapshot = editor.snapshot(window, cx);
2175 editor.update_restoration_data(cx, move |data| {
2176 data.scroll_position = (
2177 new_anchor.top_row(&snapshot.buffer_snapshot),
2178 new_anchor.offset,
2179 );
2180 });
2181 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2182 editor.inline_blame_popover.take();
2183 }
2184 }
2185 EditorEvent::Edited { .. } => {
2186 if !vim_enabled(cx) {
2187 let (map, selections) = editor.selections.all_adjusted_display(cx);
2188 let pop_state = editor
2189 .change_list
2190 .last()
2191 .map(|previous| {
2192 previous.len() == selections.len()
2193 && previous.iter().enumerate().all(|(ix, p)| {
2194 p.to_display_point(&map).row()
2195 == selections[ix].head().row()
2196 })
2197 })
2198 .unwrap_or(false);
2199 let new_positions = selections
2200 .into_iter()
2201 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2202 .collect();
2203 editor
2204 .change_list
2205 .push_to_change_list(pop_state, new_positions);
2206 }
2207 }
2208 _ => (),
2209 },
2210 ));
2211
2212 if let Some(dap_store) = editor
2213 .project
2214 .as_ref()
2215 .map(|project| project.read(cx).dap_store())
2216 {
2217 let weak_editor = cx.weak_entity();
2218
2219 editor
2220 ._subscriptions
2221 .push(
2222 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2223 let session_entity = cx.entity();
2224 weak_editor
2225 .update(cx, |editor, cx| {
2226 editor._subscriptions.push(
2227 cx.subscribe(&session_entity, Self::on_debug_session_event),
2228 );
2229 })
2230 .ok();
2231 }),
2232 );
2233
2234 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2235 editor
2236 ._subscriptions
2237 .push(cx.subscribe(&session, Self::on_debug_session_event));
2238 }
2239 }
2240
2241 // skip adding the initial selection to selection history
2242 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2243 editor.end_selection(window, cx);
2244 editor.selection_history.mode = SelectionHistoryMode::Normal;
2245
2246 editor.scroll_manager.show_scrollbars(window, cx);
2247 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2248
2249 if full_mode {
2250 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2251 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2252
2253 if editor.git_blame_inline_enabled {
2254 editor.start_git_blame_inline(false, window, cx);
2255 }
2256
2257 editor.go_to_active_debug_line(window, cx);
2258
2259 if let Some(buffer) = buffer.read(cx).as_singleton() {
2260 if let Some(project) = editor.project.as_ref() {
2261 let handle = project.update(cx, |project, cx| {
2262 project.register_buffer_with_language_servers(&buffer, cx)
2263 });
2264 editor
2265 .registered_buffers
2266 .insert(buffer.read(cx).remote_id(), handle);
2267 }
2268 }
2269
2270 editor.minimap =
2271 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2272 editor.colors = Some(LspColorData::new(cx));
2273 editor.update_lsp_data(None, None, window, cx);
2274 }
2275
2276 editor.report_editor_event("Editor Opened", None, cx);
2277 editor
2278 }
2279
2280 pub fn deploy_mouse_context_menu(
2281 &mut self,
2282 position: gpui::Point<Pixels>,
2283 context_menu: Entity<ContextMenu>,
2284 window: &mut Window,
2285 cx: &mut Context<Self>,
2286 ) {
2287 self.mouse_context_menu = Some(MouseContextMenu::new(
2288 self,
2289 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2290 context_menu,
2291 window,
2292 cx,
2293 ));
2294 }
2295
2296 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2297 self.mouse_context_menu
2298 .as_ref()
2299 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2300 }
2301
2302 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2303 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2304 }
2305
2306 fn key_context_internal(
2307 &self,
2308 has_active_edit_prediction: bool,
2309 window: &Window,
2310 cx: &App,
2311 ) -> KeyContext {
2312 let mut key_context = KeyContext::new_with_defaults();
2313 key_context.add("Editor");
2314 let mode = match self.mode {
2315 EditorMode::SingleLine { .. } => "single_line",
2316 EditorMode::AutoHeight { .. } => "auto_height",
2317 EditorMode::Minimap { .. } => "minimap",
2318 EditorMode::Full { .. } => "full",
2319 };
2320
2321 if EditorSettings::jupyter_enabled(cx) {
2322 key_context.add("jupyter");
2323 }
2324
2325 key_context.set("mode", mode);
2326 if self.pending_rename.is_some() {
2327 key_context.add("renaming");
2328 }
2329
2330 match self.context_menu.borrow().as_ref() {
2331 Some(CodeContextMenu::Completions(_)) => {
2332 key_context.add("menu");
2333 key_context.add("showing_completions");
2334 }
2335 Some(CodeContextMenu::CodeActions(_)) => {
2336 key_context.add("menu");
2337 key_context.add("showing_code_actions")
2338 }
2339 None => {}
2340 }
2341
2342 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2343 if !self.focus_handle(cx).contains_focused(window, cx)
2344 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2345 {
2346 for addon in self.addons.values() {
2347 addon.extend_key_context(&mut key_context, cx)
2348 }
2349 }
2350
2351 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2352 if let Some(extension) = singleton_buffer
2353 .read(cx)
2354 .file()
2355 .and_then(|file| file.path().extension()?.to_str())
2356 {
2357 key_context.set("extension", extension.to_string());
2358 }
2359 } else {
2360 key_context.add("multibuffer");
2361 }
2362
2363 if has_active_edit_prediction {
2364 if self.edit_prediction_in_conflict() {
2365 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2366 } else {
2367 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2368 key_context.add("copilot_suggestion");
2369 }
2370 }
2371
2372 if self.selection_mark_mode {
2373 key_context.add("selection_mode");
2374 }
2375
2376 key_context
2377 }
2378
2379 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2380 if self.mouse_cursor_hidden {
2381 self.mouse_cursor_hidden = false;
2382 cx.notify();
2383 }
2384 }
2385
2386 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2387 let hide_mouse_cursor = match origin {
2388 HideMouseCursorOrigin::TypingAction => {
2389 matches!(
2390 self.hide_mouse_mode,
2391 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2392 )
2393 }
2394 HideMouseCursorOrigin::MovementAction => {
2395 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2396 }
2397 };
2398 if self.mouse_cursor_hidden != hide_mouse_cursor {
2399 self.mouse_cursor_hidden = hide_mouse_cursor;
2400 cx.notify();
2401 }
2402 }
2403
2404 pub fn edit_prediction_in_conflict(&self) -> bool {
2405 if !self.show_edit_predictions_in_menu() {
2406 return false;
2407 }
2408
2409 let showing_completions = self
2410 .context_menu
2411 .borrow()
2412 .as_ref()
2413 .map_or(false, |context| {
2414 matches!(context, CodeContextMenu::Completions(_))
2415 });
2416
2417 showing_completions
2418 || self.edit_prediction_requires_modifier()
2419 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2420 // bindings to insert tab characters.
2421 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2422 }
2423
2424 pub fn accept_edit_prediction_keybind(
2425 &self,
2426 accept_partial: bool,
2427 window: &Window,
2428 cx: &App,
2429 ) -> AcceptEditPredictionBinding {
2430 let key_context = self.key_context_internal(true, window, cx);
2431 let in_conflict = self.edit_prediction_in_conflict();
2432
2433 let bindings = if accept_partial {
2434 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2435 } else {
2436 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2437 };
2438
2439 // TODO: if the binding contains multiple keystrokes, display all of them, not
2440 // just the first one.
2441 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2442 !in_conflict
2443 || binding
2444 .keystrokes()
2445 .first()
2446 .map_or(false, |keystroke| keystroke.modifiers.modified())
2447 }))
2448 }
2449
2450 pub fn new_file(
2451 workspace: &mut Workspace,
2452 _: &workspace::NewFile,
2453 window: &mut Window,
2454 cx: &mut Context<Workspace>,
2455 ) {
2456 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2457 "Failed to create buffer",
2458 window,
2459 cx,
2460 |e, _, _| match e.error_code() {
2461 ErrorCode::RemoteUpgradeRequired => Some(format!(
2462 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2463 e.error_tag("required").unwrap_or("the latest version")
2464 )),
2465 _ => None,
2466 },
2467 );
2468 }
2469
2470 pub fn new_in_workspace(
2471 workspace: &mut Workspace,
2472 window: &mut Window,
2473 cx: &mut Context<Workspace>,
2474 ) -> Task<Result<Entity<Editor>>> {
2475 let project = workspace.project().clone();
2476 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2477
2478 cx.spawn_in(window, async move |workspace, cx| {
2479 let buffer = create.await?;
2480 workspace.update_in(cx, |workspace, window, cx| {
2481 let editor =
2482 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2483 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2484 editor
2485 })
2486 })
2487 }
2488
2489 fn new_file_vertical(
2490 workspace: &mut Workspace,
2491 _: &workspace::NewFileSplitVertical,
2492 window: &mut Window,
2493 cx: &mut Context<Workspace>,
2494 ) {
2495 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2496 }
2497
2498 fn new_file_horizontal(
2499 workspace: &mut Workspace,
2500 _: &workspace::NewFileSplitHorizontal,
2501 window: &mut Window,
2502 cx: &mut Context<Workspace>,
2503 ) {
2504 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2505 }
2506
2507 fn new_file_in_direction(
2508 workspace: &mut Workspace,
2509 direction: SplitDirection,
2510 window: &mut Window,
2511 cx: &mut Context<Workspace>,
2512 ) {
2513 let project = workspace.project().clone();
2514 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2515
2516 cx.spawn_in(window, async move |workspace, cx| {
2517 let buffer = create.await?;
2518 workspace.update_in(cx, move |workspace, window, cx| {
2519 workspace.split_item(
2520 direction,
2521 Box::new(
2522 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2523 ),
2524 window,
2525 cx,
2526 )
2527 })?;
2528 anyhow::Ok(())
2529 })
2530 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2531 match e.error_code() {
2532 ErrorCode::RemoteUpgradeRequired => Some(format!(
2533 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2534 e.error_tag("required").unwrap_or("the latest version")
2535 )),
2536 _ => None,
2537 }
2538 });
2539 }
2540
2541 pub fn leader_id(&self) -> Option<CollaboratorId> {
2542 self.leader_id
2543 }
2544
2545 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2546 &self.buffer
2547 }
2548
2549 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2550 self.workspace.as_ref()?.0.upgrade()
2551 }
2552
2553 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2554 self.buffer().read(cx).title(cx)
2555 }
2556
2557 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2558 let git_blame_gutter_max_author_length = self
2559 .render_git_blame_gutter(cx)
2560 .then(|| {
2561 if let Some(blame) = self.blame.as_ref() {
2562 let max_author_length =
2563 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2564 Some(max_author_length)
2565 } else {
2566 None
2567 }
2568 })
2569 .flatten();
2570
2571 EditorSnapshot {
2572 mode: self.mode.clone(),
2573 show_gutter: self.show_gutter,
2574 show_line_numbers: self.show_line_numbers,
2575 show_git_diff_gutter: self.show_git_diff_gutter,
2576 show_code_actions: self.show_code_actions,
2577 show_runnables: self.show_runnables,
2578 show_breakpoints: self.show_breakpoints,
2579 git_blame_gutter_max_author_length,
2580 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2581 scroll_anchor: self.scroll_manager.anchor(),
2582 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2583 placeholder_text: self.placeholder_text.clone(),
2584 is_focused: self.focus_handle.is_focused(window),
2585 current_line_highlight: self
2586 .current_line_highlight
2587 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2588 gutter_hovered: self.gutter_hovered,
2589 }
2590 }
2591
2592 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2593 self.buffer.read(cx).language_at(point, cx)
2594 }
2595
2596 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2597 self.buffer.read(cx).read(cx).file_at(point).cloned()
2598 }
2599
2600 pub fn active_excerpt(
2601 &self,
2602 cx: &App,
2603 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2604 self.buffer
2605 .read(cx)
2606 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2607 }
2608
2609 pub fn mode(&self) -> &EditorMode {
2610 &self.mode
2611 }
2612
2613 pub fn set_mode(&mut self, mode: EditorMode) {
2614 self.mode = mode;
2615 }
2616
2617 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2618 self.collaboration_hub.as_deref()
2619 }
2620
2621 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2622 self.collaboration_hub = Some(hub);
2623 }
2624
2625 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2626 self.in_project_search = in_project_search;
2627 }
2628
2629 pub fn set_custom_context_menu(
2630 &mut self,
2631 f: impl 'static
2632 + Fn(
2633 &mut Self,
2634 DisplayPoint,
2635 &mut Window,
2636 &mut Context<Self>,
2637 ) -> Option<Entity<ui::ContextMenu>>,
2638 ) {
2639 self.custom_context_menu = Some(Box::new(f))
2640 }
2641
2642 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2643 self.completion_provider = provider;
2644 }
2645
2646 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2647 self.semantics_provider.clone()
2648 }
2649
2650 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2651 self.semantics_provider = provider;
2652 }
2653
2654 pub fn set_edit_prediction_provider<T>(
2655 &mut self,
2656 provider: Option<Entity<T>>,
2657 window: &mut Window,
2658 cx: &mut Context<Self>,
2659 ) where
2660 T: EditPredictionProvider,
2661 {
2662 self.edit_prediction_provider =
2663 provider.map(|provider| RegisteredInlineCompletionProvider {
2664 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2665 if this.focus_handle.is_focused(window) {
2666 this.update_visible_inline_completion(window, cx);
2667 }
2668 }),
2669 provider: Arc::new(provider),
2670 });
2671 self.update_edit_prediction_settings(cx);
2672 self.refresh_inline_completion(false, false, window, cx);
2673 }
2674
2675 pub fn placeholder_text(&self) -> Option<&str> {
2676 self.placeholder_text.as_deref()
2677 }
2678
2679 pub fn set_placeholder_text(
2680 &mut self,
2681 placeholder_text: impl Into<Arc<str>>,
2682 cx: &mut Context<Self>,
2683 ) {
2684 let placeholder_text = Some(placeholder_text.into());
2685 if self.placeholder_text != placeholder_text {
2686 self.placeholder_text = placeholder_text;
2687 cx.notify();
2688 }
2689 }
2690
2691 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2692 self.cursor_shape = cursor_shape;
2693
2694 // Disrupt blink for immediate user feedback that the cursor shape has changed
2695 self.blink_manager.update(cx, BlinkManager::show_cursor);
2696
2697 cx.notify();
2698 }
2699
2700 pub fn set_current_line_highlight(
2701 &mut self,
2702 current_line_highlight: Option<CurrentLineHighlight>,
2703 ) {
2704 self.current_line_highlight = current_line_highlight;
2705 }
2706
2707 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2708 self.collapse_matches = collapse_matches;
2709 }
2710
2711 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2712 let buffers = self.buffer.read(cx).all_buffers();
2713 let Some(project) = self.project.as_ref() else {
2714 return;
2715 };
2716 project.update(cx, |project, cx| {
2717 for buffer in buffers {
2718 self.registered_buffers
2719 .entry(buffer.read(cx).remote_id())
2720 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2721 }
2722 })
2723 }
2724
2725 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2726 if self.collapse_matches {
2727 return range.start..range.start;
2728 }
2729 range.clone()
2730 }
2731
2732 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2733 if self.display_map.read(cx).clip_at_line_ends != clip {
2734 self.display_map
2735 .update(cx, |map, _| map.clip_at_line_ends = clip);
2736 }
2737 }
2738
2739 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2740 self.input_enabled = input_enabled;
2741 }
2742
2743 pub fn set_inline_completions_hidden_for_vim_mode(
2744 &mut self,
2745 hidden: bool,
2746 window: &mut Window,
2747 cx: &mut Context<Self>,
2748 ) {
2749 if hidden != self.inline_completions_hidden_for_vim_mode {
2750 self.inline_completions_hidden_for_vim_mode = hidden;
2751 if hidden {
2752 self.update_visible_inline_completion(window, cx);
2753 } else {
2754 self.refresh_inline_completion(true, false, window, cx);
2755 }
2756 }
2757 }
2758
2759 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2760 self.menu_inline_completions_policy = value;
2761 }
2762
2763 pub fn set_autoindent(&mut self, autoindent: bool) {
2764 if autoindent {
2765 self.autoindent_mode = Some(AutoindentMode::EachLine);
2766 } else {
2767 self.autoindent_mode = None;
2768 }
2769 }
2770
2771 pub fn read_only(&self, cx: &App) -> bool {
2772 self.read_only || self.buffer.read(cx).read_only()
2773 }
2774
2775 pub fn set_read_only(&mut self, read_only: bool) {
2776 self.read_only = read_only;
2777 }
2778
2779 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2780 self.use_autoclose = autoclose;
2781 }
2782
2783 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2784 self.use_auto_surround = auto_surround;
2785 }
2786
2787 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2788 self.auto_replace_emoji_shortcode = auto_replace;
2789 }
2790
2791 pub fn toggle_edit_predictions(
2792 &mut self,
2793 _: &ToggleEditPrediction,
2794 window: &mut Window,
2795 cx: &mut Context<Self>,
2796 ) {
2797 if self.show_inline_completions_override.is_some() {
2798 self.set_show_edit_predictions(None, window, cx);
2799 } else {
2800 let show_edit_predictions = !self.edit_predictions_enabled();
2801 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2802 }
2803 }
2804
2805 pub fn set_show_edit_predictions(
2806 &mut self,
2807 show_edit_predictions: Option<bool>,
2808 window: &mut Window,
2809 cx: &mut Context<Self>,
2810 ) {
2811 self.show_inline_completions_override = show_edit_predictions;
2812 self.update_edit_prediction_settings(cx);
2813
2814 if let Some(false) = show_edit_predictions {
2815 self.discard_inline_completion(false, cx);
2816 } else {
2817 self.refresh_inline_completion(false, true, window, cx);
2818 }
2819 }
2820
2821 fn inline_completions_disabled_in_scope(
2822 &self,
2823 buffer: &Entity<Buffer>,
2824 buffer_position: language::Anchor,
2825 cx: &App,
2826 ) -> bool {
2827 let snapshot = buffer.read(cx).snapshot();
2828 let settings = snapshot.settings_at(buffer_position, cx);
2829
2830 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2831 return false;
2832 };
2833
2834 scope.override_name().map_or(false, |scope_name| {
2835 settings
2836 .edit_predictions_disabled_in
2837 .iter()
2838 .any(|s| s == scope_name)
2839 })
2840 }
2841
2842 pub fn set_use_modal_editing(&mut self, to: bool) {
2843 self.use_modal_editing = to;
2844 }
2845
2846 pub fn use_modal_editing(&self) -> bool {
2847 self.use_modal_editing
2848 }
2849
2850 fn selections_did_change(
2851 &mut self,
2852 local: bool,
2853 old_cursor_position: &Anchor,
2854 effects: SelectionEffects,
2855 window: &mut Window,
2856 cx: &mut Context<Self>,
2857 ) {
2858 window.invalidate_character_coordinates();
2859
2860 // Copy selections to primary selection buffer
2861 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2862 if local {
2863 let selections = self.selections.all::<usize>(cx);
2864 let buffer_handle = self.buffer.read(cx).read(cx);
2865
2866 let mut text = String::new();
2867 for (index, selection) in selections.iter().enumerate() {
2868 let text_for_selection = buffer_handle
2869 .text_for_range(selection.start..selection.end)
2870 .collect::<String>();
2871
2872 text.push_str(&text_for_selection);
2873 if index != selections.len() - 1 {
2874 text.push('\n');
2875 }
2876 }
2877
2878 if !text.is_empty() {
2879 cx.write_to_primary(ClipboardItem::new_string(text));
2880 }
2881 }
2882
2883 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2884 self.buffer.update(cx, |buffer, cx| {
2885 buffer.set_active_selections(
2886 &self.selections.disjoint_anchors(),
2887 self.selections.line_mode,
2888 self.cursor_shape,
2889 cx,
2890 )
2891 });
2892 }
2893 let display_map = self
2894 .display_map
2895 .update(cx, |display_map, cx| display_map.snapshot(cx));
2896 let buffer = &display_map.buffer_snapshot;
2897 if self.selections.count() == 1 {
2898 self.add_selections_state = None;
2899 }
2900 self.select_next_state = None;
2901 self.select_prev_state = None;
2902 self.select_syntax_node_history.try_clear();
2903 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2904 self.snippet_stack
2905 .invalidate(&self.selections.disjoint_anchors(), buffer);
2906 self.take_rename(false, window, cx);
2907
2908 let newest_selection = self.selections.newest_anchor();
2909 let new_cursor_position = newest_selection.head();
2910 let selection_start = newest_selection.start;
2911
2912 if effects.nav_history {
2913 self.push_to_nav_history(
2914 *old_cursor_position,
2915 Some(new_cursor_position.to_point(buffer)),
2916 false,
2917 cx,
2918 );
2919 }
2920
2921 if local {
2922 if let Some(buffer_id) = new_cursor_position.buffer_id {
2923 if !self.registered_buffers.contains_key(&buffer_id) {
2924 if let Some(project) = self.project.as_ref() {
2925 project.update(cx, |project, cx| {
2926 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2927 return;
2928 };
2929 self.registered_buffers.insert(
2930 buffer_id,
2931 project.register_buffer_with_language_servers(&buffer, cx),
2932 );
2933 })
2934 }
2935 }
2936 }
2937
2938 let mut context_menu = self.context_menu.borrow_mut();
2939 let completion_menu = match context_menu.as_ref() {
2940 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2941 Some(CodeContextMenu::CodeActions(_)) => {
2942 *context_menu = None;
2943 None
2944 }
2945 None => None,
2946 };
2947 let completion_position = completion_menu.map(|menu| menu.initial_position);
2948 drop(context_menu);
2949
2950 if effects.completions {
2951 if let Some(completion_position) = completion_position {
2952 let start_offset = selection_start.to_offset(buffer);
2953 let position_matches = start_offset == completion_position.to_offset(buffer);
2954 let continue_showing = if position_matches {
2955 if self.snippet_stack.is_empty() {
2956 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
2957 } else {
2958 // Snippet choices can be shown even when the cursor is in whitespace.
2959 // Dismissing the menu with actions like backspace is handled by
2960 // invalidation regions.
2961 true
2962 }
2963 } else {
2964 false
2965 };
2966
2967 if continue_showing {
2968 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2969 } else {
2970 self.hide_context_menu(window, cx);
2971 }
2972 }
2973 }
2974
2975 hide_hover(self, cx);
2976
2977 if old_cursor_position.to_display_point(&display_map).row()
2978 != new_cursor_position.to_display_point(&display_map).row()
2979 {
2980 self.available_code_actions.take();
2981 }
2982 self.refresh_code_actions(window, cx);
2983 self.refresh_document_highlights(cx);
2984 self.refresh_selected_text_highlights(false, window, cx);
2985 refresh_matching_bracket_highlights(self, window, cx);
2986 self.update_visible_inline_completion(window, cx);
2987 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2988 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2989 self.inline_blame_popover.take();
2990 if self.git_blame_inline_enabled {
2991 self.start_inline_blame_timer(window, cx);
2992 }
2993 }
2994
2995 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2996 cx.emit(EditorEvent::SelectionsChanged { local });
2997
2998 let selections = &self.selections.disjoint;
2999 if selections.len() == 1 {
3000 cx.emit(SearchEvent::ActiveMatchChanged)
3001 }
3002 if local {
3003 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3004 let inmemory_selections = selections
3005 .iter()
3006 .map(|s| {
3007 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3008 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3009 })
3010 .collect();
3011 self.update_restoration_data(cx, |data| {
3012 data.selections = inmemory_selections;
3013 });
3014
3015 if WorkspaceSettings::get(None, cx).restore_on_startup
3016 != RestoreOnStartupBehavior::None
3017 {
3018 if let Some(workspace_id) =
3019 self.workspace.as_ref().and_then(|workspace| workspace.1)
3020 {
3021 let snapshot = self.buffer().read(cx).snapshot(cx);
3022 let selections = selections.clone();
3023 let background_executor = cx.background_executor().clone();
3024 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3025 self.serialize_selections = cx.background_spawn(async move {
3026 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3027 let db_selections = selections
3028 .iter()
3029 .map(|selection| {
3030 (
3031 selection.start.to_offset(&snapshot),
3032 selection.end.to_offset(&snapshot),
3033 )
3034 })
3035 .collect();
3036
3037 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3038 .await
3039 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3040 .log_err();
3041 });
3042 }
3043 }
3044 }
3045 }
3046
3047 cx.notify();
3048 }
3049
3050 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3051 use text::ToOffset as _;
3052 use text::ToPoint as _;
3053
3054 if self.mode.is_minimap()
3055 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3056 {
3057 return;
3058 }
3059
3060 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3061 return;
3062 };
3063
3064 let snapshot = singleton.read(cx).snapshot();
3065 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3066 let display_snapshot = display_map.snapshot(cx);
3067
3068 display_snapshot
3069 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3070 .map(|fold| {
3071 fold.range.start.text_anchor.to_point(&snapshot)
3072 ..fold.range.end.text_anchor.to_point(&snapshot)
3073 })
3074 .collect()
3075 });
3076 self.update_restoration_data(cx, |data| {
3077 data.folds = inmemory_folds;
3078 });
3079
3080 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3081 return;
3082 };
3083 let background_executor = cx.background_executor().clone();
3084 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3085 let db_folds = self.display_map.update(cx, |display_map, cx| {
3086 display_map
3087 .snapshot(cx)
3088 .folds_in_range(0..snapshot.len())
3089 .map(|fold| {
3090 (
3091 fold.range.start.text_anchor.to_offset(&snapshot),
3092 fold.range.end.text_anchor.to_offset(&snapshot),
3093 )
3094 })
3095 .collect()
3096 });
3097 self.serialize_folds = cx.background_spawn(async move {
3098 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3099 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3100 .await
3101 .with_context(|| {
3102 format!(
3103 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3104 )
3105 })
3106 .log_err();
3107 });
3108 }
3109
3110 pub fn sync_selections(
3111 &mut self,
3112 other: Entity<Editor>,
3113 cx: &mut Context<Self>,
3114 ) -> gpui::Subscription {
3115 let other_selections = other.read(cx).selections.disjoint.to_vec();
3116 self.selections.change_with(cx, |selections| {
3117 selections.select_anchors(other_selections);
3118 });
3119
3120 let other_subscription =
3121 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3122 EditorEvent::SelectionsChanged { local: true } => {
3123 let other_selections = other.read(cx).selections.disjoint.to_vec();
3124 if other_selections.is_empty() {
3125 return;
3126 }
3127 this.selections.change_with(cx, |selections| {
3128 selections.select_anchors(other_selections);
3129 });
3130 }
3131 _ => {}
3132 });
3133
3134 let this_subscription =
3135 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3136 EditorEvent::SelectionsChanged { local: true } => {
3137 let these_selections = this.selections.disjoint.to_vec();
3138 if these_selections.is_empty() {
3139 return;
3140 }
3141 other.update(cx, |other_editor, cx| {
3142 other_editor.selections.change_with(cx, |selections| {
3143 selections.select_anchors(these_selections);
3144 })
3145 });
3146 }
3147 _ => {}
3148 });
3149
3150 Subscription::join(other_subscription, this_subscription)
3151 }
3152
3153 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3154 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3155 /// effects of selection change occur at the end of the transaction.
3156 pub fn change_selections<R>(
3157 &mut self,
3158 effects: impl Into<SelectionEffects>,
3159 window: &mut Window,
3160 cx: &mut Context<Self>,
3161 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3162 ) -> R {
3163 let effects = effects.into();
3164 if let Some(state) = &mut self.deferred_selection_effects_state {
3165 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3166 state.effects.completions = effects.completions;
3167 state.effects.nav_history |= effects.nav_history;
3168 let (changed, result) = self.selections.change_with(cx, change);
3169 state.changed |= changed;
3170 return result;
3171 }
3172 let mut state = DeferredSelectionEffectsState {
3173 changed: false,
3174 effects,
3175 old_cursor_position: self.selections.newest_anchor().head(),
3176 history_entry: SelectionHistoryEntry {
3177 selections: self.selections.disjoint_anchors(),
3178 select_next_state: self.select_next_state.clone(),
3179 select_prev_state: self.select_prev_state.clone(),
3180 add_selections_state: self.add_selections_state.clone(),
3181 },
3182 };
3183 let (changed, result) = self.selections.change_with(cx, change);
3184 state.changed = state.changed || changed;
3185 if self.defer_selection_effects {
3186 self.deferred_selection_effects_state = Some(state);
3187 } else {
3188 self.apply_selection_effects(state, window, cx);
3189 }
3190 result
3191 }
3192
3193 /// Defers the effects of selection change, so that the effects of multiple calls to
3194 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3195 /// to selection history and the state of popovers based on selection position aren't
3196 /// erroneously updated.
3197 pub fn with_selection_effects_deferred<R>(
3198 &mut self,
3199 window: &mut Window,
3200 cx: &mut Context<Self>,
3201 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3202 ) -> R {
3203 let already_deferred = self.defer_selection_effects;
3204 self.defer_selection_effects = true;
3205 let result = update(self, window, cx);
3206 if !already_deferred {
3207 self.defer_selection_effects = false;
3208 if let Some(state) = self.deferred_selection_effects_state.take() {
3209 self.apply_selection_effects(state, window, cx);
3210 }
3211 }
3212 result
3213 }
3214
3215 fn apply_selection_effects(
3216 &mut self,
3217 state: DeferredSelectionEffectsState,
3218 window: &mut Window,
3219 cx: &mut Context<Self>,
3220 ) {
3221 if state.changed {
3222 self.selection_history.push(state.history_entry);
3223
3224 if let Some(autoscroll) = state.effects.scroll {
3225 self.request_autoscroll(autoscroll, cx);
3226 }
3227
3228 let old_cursor_position = &state.old_cursor_position;
3229
3230 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3231
3232 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3233 self.show_signature_help(&ShowSignatureHelp, window, cx);
3234 }
3235 }
3236 }
3237
3238 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3239 where
3240 I: IntoIterator<Item = (Range<S>, T)>,
3241 S: ToOffset,
3242 T: Into<Arc<str>>,
3243 {
3244 if self.read_only(cx) {
3245 return;
3246 }
3247
3248 self.buffer
3249 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3250 }
3251
3252 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3253 where
3254 I: IntoIterator<Item = (Range<S>, T)>,
3255 S: ToOffset,
3256 T: Into<Arc<str>>,
3257 {
3258 if self.read_only(cx) {
3259 return;
3260 }
3261
3262 self.buffer.update(cx, |buffer, cx| {
3263 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3264 });
3265 }
3266
3267 pub fn edit_with_block_indent<I, S, T>(
3268 &mut self,
3269 edits: I,
3270 original_indent_columns: Vec<Option<u32>>,
3271 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.update(cx, |buffer, cx| {
3282 buffer.edit(
3283 edits,
3284 Some(AutoindentMode::Block {
3285 original_indent_columns,
3286 }),
3287 cx,
3288 )
3289 });
3290 }
3291
3292 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3293 self.hide_context_menu(window, cx);
3294
3295 match phase {
3296 SelectPhase::Begin {
3297 position,
3298 add,
3299 click_count,
3300 } => self.begin_selection(position, add, click_count, window, cx),
3301 SelectPhase::BeginColumnar {
3302 position,
3303 goal_column,
3304 reset,
3305 mode,
3306 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3307 SelectPhase::Extend {
3308 position,
3309 click_count,
3310 } => self.extend_selection(position, click_count, window, cx),
3311 SelectPhase::Update {
3312 position,
3313 goal_column,
3314 scroll_delta,
3315 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3316 SelectPhase::End => self.end_selection(window, cx),
3317 }
3318 }
3319
3320 fn extend_selection(
3321 &mut self,
3322 position: DisplayPoint,
3323 click_count: usize,
3324 window: &mut Window,
3325 cx: &mut Context<Self>,
3326 ) {
3327 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3328 let tail = self.selections.newest::<usize>(cx).tail();
3329 self.begin_selection(position, false, click_count, window, cx);
3330
3331 let position = position.to_offset(&display_map, Bias::Left);
3332 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3333
3334 let mut pending_selection = self
3335 .selections
3336 .pending_anchor()
3337 .expect("extend_selection not called with pending selection");
3338 if position >= tail {
3339 pending_selection.start = tail_anchor;
3340 } else {
3341 pending_selection.end = tail_anchor;
3342 pending_selection.reversed = true;
3343 }
3344
3345 let mut pending_mode = self.selections.pending_mode().unwrap();
3346 match &mut pending_mode {
3347 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3348 _ => {}
3349 }
3350
3351 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3352 SelectionEffects::scroll(Autoscroll::fit())
3353 } else {
3354 SelectionEffects::no_scroll()
3355 };
3356
3357 self.change_selections(effects, window, cx, |s| {
3358 s.set_pending(pending_selection, pending_mode)
3359 });
3360 }
3361
3362 fn begin_selection(
3363 &mut self,
3364 position: DisplayPoint,
3365 add: bool,
3366 click_count: usize,
3367 window: &mut Window,
3368 cx: &mut Context<Self>,
3369 ) {
3370 if !self.focus_handle.is_focused(window) {
3371 self.last_focused_descendant = None;
3372 window.focus(&self.focus_handle);
3373 }
3374
3375 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3376 let buffer = &display_map.buffer_snapshot;
3377 let position = display_map.clip_point(position, Bias::Left);
3378
3379 let start;
3380 let end;
3381 let mode;
3382 let mut auto_scroll;
3383 match click_count {
3384 1 => {
3385 start = buffer.anchor_before(position.to_point(&display_map));
3386 end = start;
3387 mode = SelectMode::Character;
3388 auto_scroll = true;
3389 }
3390 2 => {
3391 let position = display_map
3392 .clip_point(position, Bias::Left)
3393 .to_offset(&display_map, Bias::Left);
3394 let (range, _) = buffer.surrounding_word(position, false);
3395 start = buffer.anchor_before(range.start);
3396 end = buffer.anchor_before(range.end);
3397 mode = SelectMode::Word(start..end);
3398 auto_scroll = true;
3399 }
3400 3 => {
3401 let position = display_map
3402 .clip_point(position, Bias::Left)
3403 .to_point(&display_map);
3404 let line_start = display_map.prev_line_boundary(position).0;
3405 let next_line_start = buffer.clip_point(
3406 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3407 Bias::Left,
3408 );
3409 start = buffer.anchor_before(line_start);
3410 end = buffer.anchor_before(next_line_start);
3411 mode = SelectMode::Line(start..end);
3412 auto_scroll = true;
3413 }
3414 _ => {
3415 start = buffer.anchor_before(0);
3416 end = buffer.anchor_before(buffer.len());
3417 mode = SelectMode::All;
3418 auto_scroll = false;
3419 }
3420 }
3421 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3422
3423 let point_to_delete: Option<usize> = {
3424 let selected_points: Vec<Selection<Point>> =
3425 self.selections.disjoint_in_range(start..end, cx);
3426
3427 if !add || click_count > 1 {
3428 None
3429 } else if !selected_points.is_empty() {
3430 Some(selected_points[0].id)
3431 } else {
3432 let clicked_point_already_selected =
3433 self.selections.disjoint.iter().find(|selection| {
3434 selection.start.to_point(buffer) == start.to_point(buffer)
3435 || selection.end.to_point(buffer) == end.to_point(buffer)
3436 });
3437
3438 clicked_point_already_selected.map(|selection| selection.id)
3439 }
3440 };
3441
3442 let selections_count = self.selections.count();
3443
3444 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3445 if let Some(point_to_delete) = point_to_delete {
3446 s.delete(point_to_delete);
3447
3448 if selections_count == 1 {
3449 s.set_pending_anchor_range(start..end, mode);
3450 }
3451 } else {
3452 if !add {
3453 s.clear_disjoint();
3454 }
3455
3456 s.set_pending_anchor_range(start..end, mode);
3457 }
3458 });
3459 }
3460
3461 fn begin_columnar_selection(
3462 &mut self,
3463 position: DisplayPoint,
3464 goal_column: u32,
3465 reset: bool,
3466 mode: ColumnarMode,
3467 window: &mut Window,
3468 cx: &mut Context<Self>,
3469 ) {
3470 if !self.focus_handle.is_focused(window) {
3471 self.last_focused_descendant = None;
3472 window.focus(&self.focus_handle);
3473 }
3474
3475 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3476
3477 if reset {
3478 let pointer_position = display_map
3479 .buffer_snapshot
3480 .anchor_before(position.to_point(&display_map));
3481
3482 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3483 s.clear_disjoint();
3484 s.set_pending_anchor_range(
3485 pointer_position..pointer_position,
3486 SelectMode::Character,
3487 );
3488 });
3489 };
3490
3491 let tail = self.selections.newest::<Point>(cx).tail();
3492 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3493 self.columnar_selection_state = match mode {
3494 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3495 selection_tail: selection_anchor,
3496 display_point: if reset {
3497 if position.column() != goal_column {
3498 Some(DisplayPoint::new(position.row(), goal_column))
3499 } else {
3500 None
3501 }
3502 } else {
3503 None
3504 },
3505 }),
3506 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3507 selection_tail: selection_anchor,
3508 }),
3509 };
3510
3511 if !reset {
3512 self.select_columns(position, goal_column, &display_map, window, cx);
3513 }
3514 }
3515
3516 fn update_selection(
3517 &mut self,
3518 position: DisplayPoint,
3519 goal_column: u32,
3520 scroll_delta: gpui::Point<f32>,
3521 window: &mut Window,
3522 cx: &mut Context<Self>,
3523 ) {
3524 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3525
3526 if self.columnar_selection_state.is_some() {
3527 self.select_columns(position, goal_column, &display_map, window, cx);
3528 } else if let Some(mut pending) = self.selections.pending_anchor() {
3529 let buffer = &display_map.buffer_snapshot;
3530 let head;
3531 let tail;
3532 let mode = self.selections.pending_mode().unwrap();
3533 match &mode {
3534 SelectMode::Character => {
3535 head = position.to_point(&display_map);
3536 tail = pending.tail().to_point(buffer);
3537 }
3538 SelectMode::Word(original_range) => {
3539 let offset = display_map
3540 .clip_point(position, Bias::Left)
3541 .to_offset(&display_map, Bias::Left);
3542 let original_range = original_range.to_offset(buffer);
3543
3544 let head_offset = if buffer.is_inside_word(offset, false)
3545 || original_range.contains(&offset)
3546 {
3547 let (word_range, _) = buffer.surrounding_word(offset, false);
3548 if word_range.start < original_range.start {
3549 word_range.start
3550 } else {
3551 word_range.end
3552 }
3553 } else {
3554 offset
3555 };
3556
3557 head = head_offset.to_point(buffer);
3558 if head_offset <= original_range.start {
3559 tail = original_range.end.to_point(buffer);
3560 } else {
3561 tail = original_range.start.to_point(buffer);
3562 }
3563 }
3564 SelectMode::Line(original_range) => {
3565 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3566
3567 let position = display_map
3568 .clip_point(position, Bias::Left)
3569 .to_point(&display_map);
3570 let line_start = display_map.prev_line_boundary(position).0;
3571 let next_line_start = buffer.clip_point(
3572 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3573 Bias::Left,
3574 );
3575
3576 if line_start < original_range.start {
3577 head = line_start
3578 } else {
3579 head = next_line_start
3580 }
3581
3582 if head <= original_range.start {
3583 tail = original_range.end;
3584 } else {
3585 tail = original_range.start;
3586 }
3587 }
3588 SelectMode::All => {
3589 return;
3590 }
3591 };
3592
3593 if head < tail {
3594 pending.start = buffer.anchor_before(head);
3595 pending.end = buffer.anchor_before(tail);
3596 pending.reversed = true;
3597 } else {
3598 pending.start = buffer.anchor_before(tail);
3599 pending.end = buffer.anchor_before(head);
3600 pending.reversed = false;
3601 }
3602
3603 self.change_selections(None, window, cx, |s| {
3604 s.set_pending(pending, mode);
3605 });
3606 } else {
3607 log::error!("update_selection dispatched with no pending selection");
3608 return;
3609 }
3610
3611 self.apply_scroll_delta(scroll_delta, window, cx);
3612 cx.notify();
3613 }
3614
3615 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3616 self.columnar_selection_state.take();
3617 if self.selections.pending_anchor().is_some() {
3618 let selections = self.selections.all::<usize>(cx);
3619 self.change_selections(None, window, cx, |s| {
3620 s.select(selections);
3621 s.clear_pending();
3622 });
3623 }
3624 }
3625
3626 fn select_columns(
3627 &mut self,
3628 head: DisplayPoint,
3629 goal_column: u32,
3630 display_map: &DisplaySnapshot,
3631 window: &mut Window,
3632 cx: &mut Context<Self>,
3633 ) {
3634 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3635 return;
3636 };
3637
3638 let tail = match columnar_state {
3639 ColumnarSelectionState::FromMouse {
3640 selection_tail,
3641 display_point,
3642 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(&display_map)),
3643 ColumnarSelectionState::FromSelection { selection_tail } => {
3644 selection_tail.to_display_point(&display_map)
3645 }
3646 };
3647
3648 let start_row = cmp::min(tail.row(), head.row());
3649 let end_row = cmp::max(tail.row(), head.row());
3650 let start_column = cmp::min(tail.column(), goal_column);
3651 let end_column = cmp::max(tail.column(), goal_column);
3652 let reversed = start_column < tail.column();
3653
3654 let selection_ranges = (start_row.0..=end_row.0)
3655 .map(DisplayRow)
3656 .filter_map(|row| {
3657 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3658 || start_column <= display_map.line_len(row))
3659 && !display_map.is_block_line(row)
3660 {
3661 let start = display_map
3662 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3663 .to_point(display_map);
3664 let end = display_map
3665 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3666 .to_point(display_map);
3667 if reversed {
3668 Some(end..start)
3669 } else {
3670 Some(start..end)
3671 }
3672 } else {
3673 None
3674 }
3675 })
3676 .collect::<Vec<_>>();
3677
3678 let ranges = match columnar_state {
3679 ColumnarSelectionState::FromMouse { .. } => {
3680 let mut non_empty_ranges = selection_ranges
3681 .iter()
3682 .filter(|selection_range| selection_range.start != selection_range.end)
3683 .peekable();
3684 if non_empty_ranges.peek().is_some() {
3685 non_empty_ranges.cloned().collect()
3686 } else {
3687 selection_ranges
3688 }
3689 }
3690 _ => selection_ranges,
3691 };
3692
3693 self.change_selections(None, window, cx, |s| {
3694 s.select_ranges(ranges);
3695 });
3696 cx.notify();
3697 }
3698
3699 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3700 self.selections
3701 .all_adjusted(cx)
3702 .iter()
3703 .any(|selection| !selection.is_empty())
3704 }
3705
3706 pub fn has_pending_nonempty_selection(&self) -> bool {
3707 let pending_nonempty_selection = match self.selections.pending_anchor() {
3708 Some(Selection { start, end, .. }) => start != end,
3709 None => false,
3710 };
3711
3712 pending_nonempty_selection
3713 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3714 }
3715
3716 pub fn has_pending_selection(&self) -> bool {
3717 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3718 }
3719
3720 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3721 self.selection_mark_mode = false;
3722 self.selection_drag_state = SelectionDragState::None;
3723
3724 if self.clear_expanded_diff_hunks(cx) {
3725 cx.notify();
3726 return;
3727 }
3728 if self.dismiss_menus_and_popups(true, window, cx) {
3729 return;
3730 }
3731
3732 if self.mode.is_full()
3733 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3734 {
3735 return;
3736 }
3737
3738 cx.propagate();
3739 }
3740
3741 pub fn dismiss_menus_and_popups(
3742 &mut self,
3743 is_user_requested: bool,
3744 window: &mut Window,
3745 cx: &mut Context<Self>,
3746 ) -> bool {
3747 if self.take_rename(false, window, cx).is_some() {
3748 return true;
3749 }
3750
3751 if hide_hover(self, cx) {
3752 return true;
3753 }
3754
3755 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3756 return true;
3757 }
3758
3759 if self.hide_context_menu(window, cx).is_some() {
3760 return true;
3761 }
3762
3763 if self.mouse_context_menu.take().is_some() {
3764 return true;
3765 }
3766
3767 if is_user_requested && self.discard_inline_completion(true, cx) {
3768 return true;
3769 }
3770
3771 if self.snippet_stack.pop().is_some() {
3772 return true;
3773 }
3774
3775 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3776 self.dismiss_diagnostics(cx);
3777 return true;
3778 }
3779
3780 false
3781 }
3782
3783 fn linked_editing_ranges_for(
3784 &self,
3785 selection: Range<text::Anchor>,
3786 cx: &App,
3787 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3788 if self.linked_edit_ranges.is_empty() {
3789 return None;
3790 }
3791 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3792 selection.end.buffer_id.and_then(|end_buffer_id| {
3793 if selection.start.buffer_id != Some(end_buffer_id) {
3794 return None;
3795 }
3796 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3797 let snapshot = buffer.read(cx).snapshot();
3798 self.linked_edit_ranges
3799 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3800 .map(|ranges| (ranges, snapshot, buffer))
3801 })?;
3802 use text::ToOffset as TO;
3803 // find offset from the start of current range to current cursor position
3804 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3805
3806 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3807 let start_difference = start_offset - start_byte_offset;
3808 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3809 let end_difference = end_offset - start_byte_offset;
3810 // Current range has associated linked ranges.
3811 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3812 for range in linked_ranges.iter() {
3813 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3814 let end_offset = start_offset + end_difference;
3815 let start_offset = start_offset + start_difference;
3816 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3817 continue;
3818 }
3819 if self.selections.disjoint_anchor_ranges().any(|s| {
3820 if s.start.buffer_id != selection.start.buffer_id
3821 || s.end.buffer_id != selection.end.buffer_id
3822 {
3823 return false;
3824 }
3825 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3826 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3827 }) {
3828 continue;
3829 }
3830 let start = buffer_snapshot.anchor_after(start_offset);
3831 let end = buffer_snapshot.anchor_after(end_offset);
3832 linked_edits
3833 .entry(buffer.clone())
3834 .or_default()
3835 .push(start..end);
3836 }
3837 Some(linked_edits)
3838 }
3839
3840 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3841 let text: Arc<str> = text.into();
3842
3843 if self.read_only(cx) {
3844 return;
3845 }
3846
3847 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3848
3849 let selections = self.selections.all_adjusted(cx);
3850 let mut bracket_inserted = false;
3851 let mut edits = Vec::new();
3852 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3853 let mut new_selections = Vec::with_capacity(selections.len());
3854 let mut new_autoclose_regions = Vec::new();
3855 let snapshot = self.buffer.read(cx).read(cx);
3856 let mut clear_linked_edit_ranges = false;
3857
3858 for (selection, autoclose_region) in
3859 self.selections_with_autoclose_regions(selections, &snapshot)
3860 {
3861 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3862 // Determine if the inserted text matches the opening or closing
3863 // bracket of any of this language's bracket pairs.
3864 let mut bracket_pair = None;
3865 let mut is_bracket_pair_start = false;
3866 let mut is_bracket_pair_end = false;
3867 if !text.is_empty() {
3868 let mut bracket_pair_matching_end = None;
3869 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3870 // and they are removing the character that triggered IME popup.
3871 for (pair, enabled) in scope.brackets() {
3872 if !pair.close && !pair.surround {
3873 continue;
3874 }
3875
3876 if enabled && pair.start.ends_with(text.as_ref()) {
3877 let prefix_len = pair.start.len() - text.len();
3878 let preceding_text_matches_prefix = prefix_len == 0
3879 || (selection.start.column >= (prefix_len as u32)
3880 && snapshot.contains_str_at(
3881 Point::new(
3882 selection.start.row,
3883 selection.start.column - (prefix_len as u32),
3884 ),
3885 &pair.start[..prefix_len],
3886 ));
3887 if preceding_text_matches_prefix {
3888 bracket_pair = Some(pair.clone());
3889 is_bracket_pair_start = true;
3890 break;
3891 }
3892 }
3893 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3894 {
3895 // take first bracket pair matching end, but don't break in case a later bracket
3896 // pair matches start
3897 bracket_pair_matching_end = Some(pair.clone());
3898 }
3899 }
3900 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3901 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3902 is_bracket_pair_end = true;
3903 }
3904 }
3905
3906 if let Some(bracket_pair) = bracket_pair {
3907 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3908 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3909 let auto_surround =
3910 self.use_auto_surround && snapshot_settings.use_auto_surround;
3911 if selection.is_empty() {
3912 if is_bracket_pair_start {
3913 // If the inserted text is a suffix of an opening bracket and the
3914 // selection is preceded by the rest of the opening bracket, then
3915 // insert the closing bracket.
3916 let following_text_allows_autoclose = snapshot
3917 .chars_at(selection.start)
3918 .next()
3919 .map_or(true, |c| scope.should_autoclose_before(c));
3920
3921 let preceding_text_allows_autoclose = selection.start.column == 0
3922 || snapshot.reversed_chars_at(selection.start).next().map_or(
3923 true,
3924 |c| {
3925 bracket_pair.start != bracket_pair.end
3926 || !snapshot
3927 .char_classifier_at(selection.start)
3928 .is_word(c)
3929 },
3930 );
3931
3932 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3933 && bracket_pair.start.len() == 1
3934 {
3935 let target = bracket_pair.start.chars().next().unwrap();
3936 let current_line_count = snapshot
3937 .reversed_chars_at(selection.start)
3938 .take_while(|&c| c != '\n')
3939 .filter(|&c| c == target)
3940 .count();
3941 current_line_count % 2 == 1
3942 } else {
3943 false
3944 };
3945
3946 if autoclose
3947 && bracket_pair.close
3948 && following_text_allows_autoclose
3949 && preceding_text_allows_autoclose
3950 && !is_closing_quote
3951 {
3952 let anchor = snapshot.anchor_before(selection.end);
3953 new_selections.push((selection.map(|_| anchor), text.len()));
3954 new_autoclose_regions.push((
3955 anchor,
3956 text.len(),
3957 selection.id,
3958 bracket_pair.clone(),
3959 ));
3960 edits.push((
3961 selection.range(),
3962 format!("{}{}", text, bracket_pair.end).into(),
3963 ));
3964 bracket_inserted = true;
3965 continue;
3966 }
3967 }
3968
3969 if let Some(region) = autoclose_region {
3970 // If the selection is followed by an auto-inserted closing bracket,
3971 // then don't insert that closing bracket again; just move the selection
3972 // past the closing bracket.
3973 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3974 && text.as_ref() == region.pair.end.as_str();
3975 if should_skip {
3976 let anchor = snapshot.anchor_after(selection.end);
3977 new_selections
3978 .push((selection.map(|_| anchor), region.pair.end.len()));
3979 continue;
3980 }
3981 }
3982
3983 let always_treat_brackets_as_autoclosed = snapshot
3984 .language_settings_at(selection.start, cx)
3985 .always_treat_brackets_as_autoclosed;
3986 if always_treat_brackets_as_autoclosed
3987 && is_bracket_pair_end
3988 && snapshot.contains_str_at(selection.end, text.as_ref())
3989 {
3990 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3991 // and the inserted text is a closing bracket and the selection is followed
3992 // by the closing bracket then move the selection past the closing bracket.
3993 let anchor = snapshot.anchor_after(selection.end);
3994 new_selections.push((selection.map(|_| anchor), text.len()));
3995 continue;
3996 }
3997 }
3998 // If an opening bracket is 1 character long and is typed while
3999 // text is selected, then surround that text with the bracket pair.
4000 else if auto_surround
4001 && bracket_pair.surround
4002 && is_bracket_pair_start
4003 && bracket_pair.start.chars().count() == 1
4004 {
4005 edits.push((selection.start..selection.start, text.clone()));
4006 edits.push((
4007 selection.end..selection.end,
4008 bracket_pair.end.as_str().into(),
4009 ));
4010 bracket_inserted = true;
4011 new_selections.push((
4012 Selection {
4013 id: selection.id,
4014 start: snapshot.anchor_after(selection.start),
4015 end: snapshot.anchor_before(selection.end),
4016 reversed: selection.reversed,
4017 goal: selection.goal,
4018 },
4019 0,
4020 ));
4021 continue;
4022 }
4023 }
4024 }
4025
4026 if self.auto_replace_emoji_shortcode
4027 && selection.is_empty()
4028 && text.as_ref().ends_with(':')
4029 {
4030 if let Some(possible_emoji_short_code) =
4031 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4032 {
4033 if !possible_emoji_short_code.is_empty() {
4034 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
4035 let emoji_shortcode_start = Point::new(
4036 selection.start.row,
4037 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4038 );
4039
4040 // Remove shortcode from buffer
4041 edits.push((
4042 emoji_shortcode_start..selection.start,
4043 "".to_string().into(),
4044 ));
4045 new_selections.push((
4046 Selection {
4047 id: selection.id,
4048 start: snapshot.anchor_after(emoji_shortcode_start),
4049 end: snapshot.anchor_before(selection.start),
4050 reversed: selection.reversed,
4051 goal: selection.goal,
4052 },
4053 0,
4054 ));
4055
4056 // Insert emoji
4057 let selection_start_anchor = snapshot.anchor_after(selection.start);
4058 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4059 edits.push((selection.start..selection.end, emoji.to_string().into()));
4060
4061 continue;
4062 }
4063 }
4064 }
4065 }
4066
4067 // If not handling any auto-close operation, then just replace the selected
4068 // text with the given input and move the selection to the end of the
4069 // newly inserted text.
4070 let anchor = snapshot.anchor_after(selection.end);
4071 if !self.linked_edit_ranges.is_empty() {
4072 let start_anchor = snapshot.anchor_before(selection.start);
4073
4074 let is_word_char = text.chars().next().map_or(true, |char| {
4075 let classifier = snapshot
4076 .char_classifier_at(start_anchor.to_offset(&snapshot))
4077 .ignore_punctuation(true);
4078 classifier.is_word(char)
4079 });
4080
4081 if is_word_char {
4082 if let Some(ranges) = self
4083 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4084 {
4085 for (buffer, edits) in ranges {
4086 linked_edits
4087 .entry(buffer.clone())
4088 .or_default()
4089 .extend(edits.into_iter().map(|range| (range, text.clone())));
4090 }
4091 }
4092 } else {
4093 clear_linked_edit_ranges = true;
4094 }
4095 }
4096
4097 new_selections.push((selection.map(|_| anchor), 0));
4098 edits.push((selection.start..selection.end, text.clone()));
4099 }
4100
4101 drop(snapshot);
4102
4103 self.transact(window, cx, |this, window, cx| {
4104 if clear_linked_edit_ranges {
4105 this.linked_edit_ranges.clear();
4106 }
4107 let initial_buffer_versions =
4108 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4109
4110 this.buffer.update(cx, |buffer, cx| {
4111 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4112 });
4113 for (buffer, edits) in linked_edits {
4114 buffer.update(cx, |buffer, cx| {
4115 let snapshot = buffer.snapshot();
4116 let edits = edits
4117 .into_iter()
4118 .map(|(range, text)| {
4119 use text::ToPoint as TP;
4120 let end_point = TP::to_point(&range.end, &snapshot);
4121 let start_point = TP::to_point(&range.start, &snapshot);
4122 (start_point..end_point, text)
4123 })
4124 .sorted_by_key(|(range, _)| range.start);
4125 buffer.edit(edits, None, cx);
4126 })
4127 }
4128 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4129 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4130 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4131 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4132 .zip(new_selection_deltas)
4133 .map(|(selection, delta)| Selection {
4134 id: selection.id,
4135 start: selection.start + delta,
4136 end: selection.end + delta,
4137 reversed: selection.reversed,
4138 goal: SelectionGoal::None,
4139 })
4140 .collect::<Vec<_>>();
4141
4142 let mut i = 0;
4143 for (position, delta, selection_id, pair) in new_autoclose_regions {
4144 let position = position.to_offset(&map.buffer_snapshot) + delta;
4145 let start = map.buffer_snapshot.anchor_before(position);
4146 let end = map.buffer_snapshot.anchor_after(position);
4147 while let Some(existing_state) = this.autoclose_regions.get(i) {
4148 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4149 Ordering::Less => i += 1,
4150 Ordering::Greater => break,
4151 Ordering::Equal => {
4152 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4153 Ordering::Less => i += 1,
4154 Ordering::Equal => break,
4155 Ordering::Greater => break,
4156 }
4157 }
4158 }
4159 }
4160 this.autoclose_regions.insert(
4161 i,
4162 AutocloseRegion {
4163 selection_id,
4164 range: start..end,
4165 pair,
4166 },
4167 );
4168 }
4169
4170 let had_active_inline_completion = this.has_active_inline_completion();
4171 this.change_selections(
4172 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4173 window,
4174 cx,
4175 |s| s.select(new_selections),
4176 );
4177
4178 if !bracket_inserted {
4179 if let Some(on_type_format_task) =
4180 this.trigger_on_type_formatting(text.to_string(), window, cx)
4181 {
4182 on_type_format_task.detach_and_log_err(cx);
4183 }
4184 }
4185
4186 let editor_settings = EditorSettings::get_global(cx);
4187 if bracket_inserted
4188 && (editor_settings.auto_signature_help
4189 || editor_settings.show_signature_help_after_edits)
4190 {
4191 this.show_signature_help(&ShowSignatureHelp, window, cx);
4192 }
4193
4194 let trigger_in_words =
4195 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4196 if this.hard_wrap.is_some() {
4197 let latest: Range<Point> = this.selections.newest(cx).range();
4198 if latest.is_empty()
4199 && this
4200 .buffer()
4201 .read(cx)
4202 .snapshot(cx)
4203 .line_len(MultiBufferRow(latest.start.row))
4204 == latest.start.column
4205 {
4206 this.rewrap_impl(
4207 RewrapOptions {
4208 override_language_settings: true,
4209 preserve_existing_whitespace: true,
4210 },
4211 cx,
4212 )
4213 }
4214 }
4215 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4216 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4217 this.refresh_inline_completion(true, false, window, cx);
4218 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4219 });
4220 }
4221
4222 fn find_possible_emoji_shortcode_at_position(
4223 snapshot: &MultiBufferSnapshot,
4224 position: Point,
4225 ) -> Option<String> {
4226 let mut chars = Vec::new();
4227 let mut found_colon = false;
4228 for char in snapshot.reversed_chars_at(position).take(100) {
4229 // Found a possible emoji shortcode in the middle of the buffer
4230 if found_colon {
4231 if char.is_whitespace() {
4232 chars.reverse();
4233 return Some(chars.iter().collect());
4234 }
4235 // If the previous character is not a whitespace, we are in the middle of a word
4236 // and we only want to complete the shortcode if the word is made up of other emojis
4237 let mut containing_word = String::new();
4238 for ch in snapshot
4239 .reversed_chars_at(position)
4240 .skip(chars.len() + 1)
4241 .take(100)
4242 {
4243 if ch.is_whitespace() {
4244 break;
4245 }
4246 containing_word.push(ch);
4247 }
4248 let containing_word = containing_word.chars().rev().collect::<String>();
4249 if util::word_consists_of_emojis(containing_word.as_str()) {
4250 chars.reverse();
4251 return Some(chars.iter().collect());
4252 }
4253 }
4254
4255 if char.is_whitespace() || !char.is_ascii() {
4256 return None;
4257 }
4258 if char == ':' {
4259 found_colon = true;
4260 } else {
4261 chars.push(char);
4262 }
4263 }
4264 // Found a possible emoji shortcode at the beginning of the buffer
4265 chars.reverse();
4266 Some(chars.iter().collect())
4267 }
4268
4269 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4270 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4271 self.transact(window, cx, |this, window, cx| {
4272 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4273 let selections = this.selections.all::<usize>(cx);
4274 let multi_buffer = this.buffer.read(cx);
4275 let buffer = multi_buffer.snapshot(cx);
4276 selections
4277 .iter()
4278 .map(|selection| {
4279 let start_point = selection.start.to_point(&buffer);
4280 let mut existing_indent =
4281 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4282 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4283 let start = selection.start;
4284 let end = selection.end;
4285 let selection_is_empty = start == end;
4286 let language_scope = buffer.language_scope_at(start);
4287 let (
4288 comment_delimiter,
4289 doc_delimiter,
4290 insert_extra_newline,
4291 indent_on_newline,
4292 indent_on_extra_newline,
4293 ) = if let Some(language) = &language_scope {
4294 let mut insert_extra_newline =
4295 insert_extra_newline_brackets(&buffer, start..end, language)
4296 || insert_extra_newline_tree_sitter(&buffer, start..end);
4297
4298 // Comment extension on newline is allowed only for cursor selections
4299 let comment_delimiter = maybe!({
4300 if !selection_is_empty {
4301 return None;
4302 }
4303
4304 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4305 return None;
4306 }
4307
4308 let delimiters = language.line_comment_prefixes();
4309 let max_len_of_delimiter =
4310 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4311 let (snapshot, range) =
4312 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4313
4314 let num_of_whitespaces = snapshot
4315 .chars_for_range(range.clone())
4316 .take_while(|c| c.is_whitespace())
4317 .count();
4318 let comment_candidate = snapshot
4319 .chars_for_range(range)
4320 .skip(num_of_whitespaces)
4321 .take(max_len_of_delimiter)
4322 .collect::<String>();
4323 let (delimiter, trimmed_len) = delimiters
4324 .iter()
4325 .filter_map(|delimiter| {
4326 let prefix = delimiter.trim_end();
4327 if comment_candidate.starts_with(prefix) {
4328 Some((delimiter, prefix.len()))
4329 } else {
4330 None
4331 }
4332 })
4333 .max_by_key(|(_, len)| *len)?;
4334
4335 let cursor_is_placed_after_comment_marker =
4336 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4337 if cursor_is_placed_after_comment_marker {
4338 Some(delimiter.clone())
4339 } else {
4340 None
4341 }
4342 });
4343
4344 let mut indent_on_newline = IndentSize::spaces(0);
4345 let mut indent_on_extra_newline = IndentSize::spaces(0);
4346
4347 let doc_delimiter = maybe!({
4348 if !selection_is_empty {
4349 return None;
4350 }
4351
4352 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4353 return None;
4354 }
4355
4356 let DocumentationConfig {
4357 start: start_tag,
4358 end: end_tag,
4359 prefix: delimiter,
4360 tab_size: len,
4361 } = language.documentation()?;
4362
4363 let is_within_block_comment = buffer
4364 .language_scope_at(start_point)
4365 .is_some_and(|scope| scope.override_name() == Some("comment"));
4366 if !is_within_block_comment {
4367 return None;
4368 }
4369
4370 let (snapshot, range) =
4371 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4372
4373 let num_of_whitespaces = snapshot
4374 .chars_for_range(range.clone())
4375 .take_while(|c| c.is_whitespace())
4376 .count();
4377
4378 // 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.
4379 let column = start_point.column;
4380 let cursor_is_after_start_tag = {
4381 let start_tag_len = start_tag.len();
4382 let start_tag_line = snapshot
4383 .chars_for_range(range.clone())
4384 .skip(num_of_whitespaces)
4385 .take(start_tag_len)
4386 .collect::<String>();
4387 if start_tag_line.starts_with(start_tag.as_ref()) {
4388 num_of_whitespaces + start_tag_len <= column as usize
4389 } else {
4390 false
4391 }
4392 };
4393
4394 let cursor_is_after_delimiter = {
4395 let delimiter_trim = delimiter.trim_end();
4396 let delimiter_line = snapshot
4397 .chars_for_range(range.clone())
4398 .skip(num_of_whitespaces)
4399 .take(delimiter_trim.len())
4400 .collect::<String>();
4401 if delimiter_line.starts_with(delimiter_trim) {
4402 num_of_whitespaces + delimiter_trim.len() <= column as usize
4403 } else {
4404 false
4405 }
4406 };
4407
4408 let cursor_is_before_end_tag_if_exists = {
4409 let mut char_position = 0u32;
4410 let mut end_tag_offset = None;
4411
4412 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4413 if let Some(byte_pos) = chunk.find(&**end_tag) {
4414 let chars_before_match =
4415 chunk[..byte_pos].chars().count() as u32;
4416 end_tag_offset =
4417 Some(char_position + chars_before_match);
4418 break 'outer;
4419 }
4420 char_position += chunk.chars().count() as u32;
4421 }
4422
4423 if let Some(end_tag_offset) = end_tag_offset {
4424 let cursor_is_before_end_tag = column <= end_tag_offset;
4425 if cursor_is_after_start_tag {
4426 if cursor_is_before_end_tag {
4427 insert_extra_newline = true;
4428 }
4429 let cursor_is_at_start_of_end_tag =
4430 column == end_tag_offset;
4431 if cursor_is_at_start_of_end_tag {
4432 indent_on_extra_newline.len = (*len).into();
4433 }
4434 }
4435 cursor_is_before_end_tag
4436 } else {
4437 true
4438 }
4439 };
4440
4441 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4442 && cursor_is_before_end_tag_if_exists
4443 {
4444 if cursor_is_after_start_tag {
4445 indent_on_newline.len = (*len).into();
4446 }
4447 Some(delimiter.clone())
4448 } else {
4449 None
4450 }
4451 });
4452
4453 (
4454 comment_delimiter,
4455 doc_delimiter,
4456 insert_extra_newline,
4457 indent_on_newline,
4458 indent_on_extra_newline,
4459 )
4460 } else {
4461 (
4462 None,
4463 None,
4464 false,
4465 IndentSize::default(),
4466 IndentSize::default(),
4467 )
4468 };
4469
4470 let prevent_auto_indent = doc_delimiter.is_some();
4471 let delimiter = comment_delimiter.or(doc_delimiter);
4472
4473 let capacity_for_delimiter =
4474 delimiter.as_deref().map(str::len).unwrap_or_default();
4475 let mut new_text = String::with_capacity(
4476 1 + capacity_for_delimiter
4477 + existing_indent.len as usize
4478 + indent_on_newline.len as usize
4479 + indent_on_extra_newline.len as usize,
4480 );
4481 new_text.push('\n');
4482 new_text.extend(existing_indent.chars());
4483 new_text.extend(indent_on_newline.chars());
4484
4485 if let Some(delimiter) = &delimiter {
4486 new_text.push_str(delimiter);
4487 }
4488
4489 if insert_extra_newline {
4490 new_text.push('\n');
4491 new_text.extend(existing_indent.chars());
4492 new_text.extend(indent_on_extra_newline.chars());
4493 }
4494
4495 let anchor = buffer.anchor_after(end);
4496 let new_selection = selection.map(|_| anchor);
4497 (
4498 ((start..end, new_text), prevent_auto_indent),
4499 (insert_extra_newline, new_selection),
4500 )
4501 })
4502 .unzip()
4503 };
4504
4505 let mut auto_indent_edits = Vec::new();
4506 let mut edits = Vec::new();
4507 for (edit, prevent_auto_indent) in edits_with_flags {
4508 if prevent_auto_indent {
4509 edits.push(edit);
4510 } else {
4511 auto_indent_edits.push(edit);
4512 }
4513 }
4514 if !edits.is_empty() {
4515 this.edit(edits, cx);
4516 }
4517 if !auto_indent_edits.is_empty() {
4518 this.edit_with_autoindent(auto_indent_edits, cx);
4519 }
4520
4521 let buffer = this.buffer.read(cx).snapshot(cx);
4522 let new_selections = selection_info
4523 .into_iter()
4524 .map(|(extra_newline_inserted, new_selection)| {
4525 let mut cursor = new_selection.end.to_point(&buffer);
4526 if extra_newline_inserted {
4527 cursor.row -= 1;
4528 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4529 }
4530 new_selection.map(|_| cursor)
4531 })
4532 .collect();
4533
4534 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4535 s.select(new_selections)
4536 });
4537 this.refresh_inline_completion(true, false, window, cx);
4538 });
4539 }
4540
4541 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4542 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4543
4544 let buffer = self.buffer.read(cx);
4545 let snapshot = buffer.snapshot(cx);
4546
4547 let mut edits = Vec::new();
4548 let mut rows = Vec::new();
4549
4550 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4551 let cursor = selection.head();
4552 let row = cursor.row;
4553
4554 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4555
4556 let newline = "\n".to_string();
4557 edits.push((start_of_line..start_of_line, newline));
4558
4559 rows.push(row + rows_inserted as u32);
4560 }
4561
4562 self.transact(window, cx, |editor, window, cx| {
4563 editor.edit(edits, cx);
4564
4565 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4566 let mut index = 0;
4567 s.move_cursors_with(|map, _, _| {
4568 let row = rows[index];
4569 index += 1;
4570
4571 let point = Point::new(row, 0);
4572 let boundary = map.next_line_boundary(point).1;
4573 let clipped = map.clip_point(boundary, Bias::Left);
4574
4575 (clipped, SelectionGoal::None)
4576 });
4577 });
4578
4579 let mut indent_edits = Vec::new();
4580 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4581 for row in rows {
4582 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4583 for (row, indent) in indents {
4584 if indent.len == 0 {
4585 continue;
4586 }
4587
4588 let text = match indent.kind {
4589 IndentKind::Space => " ".repeat(indent.len as usize),
4590 IndentKind::Tab => "\t".repeat(indent.len as usize),
4591 };
4592 let point = Point::new(row.0, 0);
4593 indent_edits.push((point..point, text));
4594 }
4595 }
4596 editor.edit(indent_edits, cx);
4597 });
4598 }
4599
4600 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4601 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4602
4603 let buffer = self.buffer.read(cx);
4604 let snapshot = buffer.snapshot(cx);
4605
4606 let mut edits = Vec::new();
4607 let mut rows = Vec::new();
4608 let mut rows_inserted = 0;
4609
4610 for selection in self.selections.all_adjusted(cx) {
4611 let cursor = selection.head();
4612 let row = cursor.row;
4613
4614 let point = Point::new(row + 1, 0);
4615 let start_of_line = snapshot.clip_point(point, Bias::Left);
4616
4617 let newline = "\n".to_string();
4618 edits.push((start_of_line..start_of_line, newline));
4619
4620 rows_inserted += 1;
4621 rows.push(row + rows_inserted);
4622 }
4623
4624 self.transact(window, cx, |editor, window, cx| {
4625 editor.edit(edits, cx);
4626
4627 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4628 let mut index = 0;
4629 s.move_cursors_with(|map, _, _| {
4630 let row = rows[index];
4631 index += 1;
4632
4633 let point = Point::new(row, 0);
4634 let boundary = map.next_line_boundary(point).1;
4635 let clipped = map.clip_point(boundary, Bias::Left);
4636
4637 (clipped, SelectionGoal::None)
4638 });
4639 });
4640
4641 let mut indent_edits = Vec::new();
4642 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4643 for row in rows {
4644 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4645 for (row, indent) in indents {
4646 if indent.len == 0 {
4647 continue;
4648 }
4649
4650 let text = match indent.kind {
4651 IndentKind::Space => " ".repeat(indent.len as usize),
4652 IndentKind::Tab => "\t".repeat(indent.len as usize),
4653 };
4654 let point = Point::new(row.0, 0);
4655 indent_edits.push((point..point, text));
4656 }
4657 }
4658 editor.edit(indent_edits, cx);
4659 });
4660 }
4661
4662 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4663 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4664 original_indent_columns: Vec::new(),
4665 });
4666 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4667 }
4668
4669 fn insert_with_autoindent_mode(
4670 &mut self,
4671 text: &str,
4672 autoindent_mode: Option<AutoindentMode>,
4673 window: &mut Window,
4674 cx: &mut Context<Self>,
4675 ) {
4676 if self.read_only(cx) {
4677 return;
4678 }
4679
4680 let text: Arc<str> = text.into();
4681 self.transact(window, cx, |this, window, cx| {
4682 let old_selections = this.selections.all_adjusted(cx);
4683 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4684 let anchors = {
4685 let snapshot = buffer.read(cx);
4686 old_selections
4687 .iter()
4688 .map(|s| {
4689 let anchor = snapshot.anchor_after(s.head());
4690 s.map(|_| anchor)
4691 })
4692 .collect::<Vec<_>>()
4693 };
4694 buffer.edit(
4695 old_selections
4696 .iter()
4697 .map(|s| (s.start..s.end, text.clone())),
4698 autoindent_mode,
4699 cx,
4700 );
4701 anchors
4702 });
4703
4704 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4705 s.select_anchors(selection_anchors);
4706 });
4707
4708 cx.notify();
4709 });
4710 }
4711
4712 fn trigger_completion_on_input(
4713 &mut self,
4714 text: &str,
4715 trigger_in_words: bool,
4716 window: &mut Window,
4717 cx: &mut Context<Self>,
4718 ) {
4719 let completions_source = self
4720 .context_menu
4721 .borrow()
4722 .as_ref()
4723 .and_then(|menu| match menu {
4724 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4725 CodeContextMenu::CodeActions(_) => None,
4726 });
4727
4728 match completions_source {
4729 Some(CompletionsMenuSource::Words) => {
4730 self.show_word_completions(&ShowWordCompletions, window, cx)
4731 }
4732 Some(CompletionsMenuSource::Normal)
4733 | Some(CompletionsMenuSource::SnippetChoices)
4734 | None
4735 if self.is_completion_trigger(
4736 text,
4737 trigger_in_words,
4738 completions_source.is_some(),
4739 cx,
4740 ) =>
4741 {
4742 self.show_completions(
4743 &ShowCompletions {
4744 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4745 },
4746 window,
4747 cx,
4748 )
4749 }
4750 _ => {
4751 self.hide_context_menu(window, cx);
4752 }
4753 }
4754 }
4755
4756 fn is_completion_trigger(
4757 &self,
4758 text: &str,
4759 trigger_in_words: bool,
4760 menu_is_open: bool,
4761 cx: &mut Context<Self>,
4762 ) -> bool {
4763 let position = self.selections.newest_anchor().head();
4764 let multibuffer = self.buffer.read(cx);
4765 let Some(buffer) = position
4766 .buffer_id
4767 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4768 else {
4769 return false;
4770 };
4771
4772 if let Some(completion_provider) = &self.completion_provider {
4773 completion_provider.is_completion_trigger(
4774 &buffer,
4775 position.text_anchor,
4776 text,
4777 trigger_in_words,
4778 menu_is_open,
4779 cx,
4780 )
4781 } else {
4782 false
4783 }
4784 }
4785
4786 /// If any empty selections is touching the start of its innermost containing autoclose
4787 /// region, expand it to select the brackets.
4788 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4789 let selections = self.selections.all::<usize>(cx);
4790 let buffer = self.buffer.read(cx).read(cx);
4791 let new_selections = self
4792 .selections_with_autoclose_regions(selections, &buffer)
4793 .map(|(mut selection, region)| {
4794 if !selection.is_empty() {
4795 return selection;
4796 }
4797
4798 if let Some(region) = region {
4799 let mut range = region.range.to_offset(&buffer);
4800 if selection.start == range.start && range.start >= region.pair.start.len() {
4801 range.start -= region.pair.start.len();
4802 if buffer.contains_str_at(range.start, ®ion.pair.start)
4803 && buffer.contains_str_at(range.end, ®ion.pair.end)
4804 {
4805 range.end += region.pair.end.len();
4806 selection.start = range.start;
4807 selection.end = range.end;
4808
4809 return selection;
4810 }
4811 }
4812 }
4813
4814 let always_treat_brackets_as_autoclosed = buffer
4815 .language_settings_at(selection.start, cx)
4816 .always_treat_brackets_as_autoclosed;
4817
4818 if !always_treat_brackets_as_autoclosed {
4819 return selection;
4820 }
4821
4822 if let Some(scope) = buffer.language_scope_at(selection.start) {
4823 for (pair, enabled) in scope.brackets() {
4824 if !enabled || !pair.close {
4825 continue;
4826 }
4827
4828 if buffer.contains_str_at(selection.start, &pair.end) {
4829 let pair_start_len = pair.start.len();
4830 if buffer.contains_str_at(
4831 selection.start.saturating_sub(pair_start_len),
4832 &pair.start,
4833 ) {
4834 selection.start -= pair_start_len;
4835 selection.end += pair.end.len();
4836
4837 return selection;
4838 }
4839 }
4840 }
4841 }
4842
4843 selection
4844 })
4845 .collect();
4846
4847 drop(buffer);
4848 self.change_selections(None, window, cx, |selections| {
4849 selections.select(new_selections)
4850 });
4851 }
4852
4853 /// Iterate the given selections, and for each one, find the smallest surrounding
4854 /// autoclose region. This uses the ordering of the selections and the autoclose
4855 /// regions to avoid repeated comparisons.
4856 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4857 &'a self,
4858 selections: impl IntoIterator<Item = Selection<D>>,
4859 buffer: &'a MultiBufferSnapshot,
4860 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4861 let mut i = 0;
4862 let mut regions = self.autoclose_regions.as_slice();
4863 selections.into_iter().map(move |selection| {
4864 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4865
4866 let mut enclosing = None;
4867 while let Some(pair_state) = regions.get(i) {
4868 if pair_state.range.end.to_offset(buffer) < range.start {
4869 regions = ®ions[i + 1..];
4870 i = 0;
4871 } else if pair_state.range.start.to_offset(buffer) > range.end {
4872 break;
4873 } else {
4874 if pair_state.selection_id == selection.id {
4875 enclosing = Some(pair_state);
4876 }
4877 i += 1;
4878 }
4879 }
4880
4881 (selection, enclosing)
4882 })
4883 }
4884
4885 /// Remove any autoclose regions that no longer contain their selection.
4886 fn invalidate_autoclose_regions(
4887 &mut self,
4888 mut selections: &[Selection<Anchor>],
4889 buffer: &MultiBufferSnapshot,
4890 ) {
4891 self.autoclose_regions.retain(|state| {
4892 let mut i = 0;
4893 while let Some(selection) = selections.get(i) {
4894 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4895 selections = &selections[1..];
4896 continue;
4897 }
4898 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4899 break;
4900 }
4901 if selection.id == state.selection_id {
4902 return true;
4903 } else {
4904 i += 1;
4905 }
4906 }
4907 false
4908 });
4909 }
4910
4911 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4912 let offset = position.to_offset(buffer);
4913 let (word_range, kind) = buffer.surrounding_word(offset, true);
4914 if offset > word_range.start && kind == Some(CharKind::Word) {
4915 Some(
4916 buffer
4917 .text_for_range(word_range.start..offset)
4918 .collect::<String>(),
4919 )
4920 } else {
4921 None
4922 }
4923 }
4924
4925 pub fn toggle_inline_values(
4926 &mut self,
4927 _: &ToggleInlineValues,
4928 _: &mut Window,
4929 cx: &mut Context<Self>,
4930 ) {
4931 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4932
4933 self.refresh_inline_values(cx);
4934 }
4935
4936 pub fn toggle_inlay_hints(
4937 &mut self,
4938 _: &ToggleInlayHints,
4939 _: &mut Window,
4940 cx: &mut Context<Self>,
4941 ) {
4942 self.refresh_inlay_hints(
4943 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4944 cx,
4945 );
4946 }
4947
4948 pub fn inlay_hints_enabled(&self) -> bool {
4949 self.inlay_hint_cache.enabled
4950 }
4951
4952 pub fn inline_values_enabled(&self) -> bool {
4953 self.inline_value_cache.enabled
4954 }
4955
4956 #[cfg(any(test, feature = "test-support"))]
4957 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4958 self.display_map
4959 .read(cx)
4960 .current_inlays()
4961 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4962 .cloned()
4963 .collect()
4964 }
4965
4966 #[cfg(any(test, feature = "test-support"))]
4967 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
4968 self.display_map
4969 .read(cx)
4970 .current_inlays()
4971 .cloned()
4972 .collect()
4973 }
4974
4975 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4976 if self.semantics_provider.is_none() || !self.mode.is_full() {
4977 return;
4978 }
4979
4980 let reason_description = reason.description();
4981 let ignore_debounce = matches!(
4982 reason,
4983 InlayHintRefreshReason::SettingsChange(_)
4984 | InlayHintRefreshReason::Toggle(_)
4985 | InlayHintRefreshReason::ExcerptsRemoved(_)
4986 | InlayHintRefreshReason::ModifiersChanged(_)
4987 );
4988 let (invalidate_cache, required_languages) = match reason {
4989 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4990 match self.inlay_hint_cache.modifiers_override(enabled) {
4991 Some(enabled) => {
4992 if enabled {
4993 (InvalidationStrategy::RefreshRequested, None)
4994 } else {
4995 self.splice_inlays(
4996 &self
4997 .visible_inlay_hints(cx)
4998 .iter()
4999 .map(|inlay| inlay.id)
5000 .collect::<Vec<InlayId>>(),
5001 Vec::new(),
5002 cx,
5003 );
5004 return;
5005 }
5006 }
5007 None => return,
5008 }
5009 }
5010 InlayHintRefreshReason::Toggle(enabled) => {
5011 if self.inlay_hint_cache.toggle(enabled) {
5012 if enabled {
5013 (InvalidationStrategy::RefreshRequested, None)
5014 } else {
5015 self.splice_inlays(
5016 &self
5017 .visible_inlay_hints(cx)
5018 .iter()
5019 .map(|inlay| inlay.id)
5020 .collect::<Vec<InlayId>>(),
5021 Vec::new(),
5022 cx,
5023 );
5024 return;
5025 }
5026 } else {
5027 return;
5028 }
5029 }
5030 InlayHintRefreshReason::SettingsChange(new_settings) => {
5031 match self.inlay_hint_cache.update_settings(
5032 &self.buffer,
5033 new_settings,
5034 self.visible_inlay_hints(cx),
5035 cx,
5036 ) {
5037 ControlFlow::Break(Some(InlaySplice {
5038 to_remove,
5039 to_insert,
5040 })) => {
5041 self.splice_inlays(&to_remove, to_insert, cx);
5042 return;
5043 }
5044 ControlFlow::Break(None) => return,
5045 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5046 }
5047 }
5048 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5049 if let Some(InlaySplice {
5050 to_remove,
5051 to_insert,
5052 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5053 {
5054 self.splice_inlays(&to_remove, to_insert, cx);
5055 }
5056 self.display_map.update(cx, |display_map, _| {
5057 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5058 });
5059 return;
5060 }
5061 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5062 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5063 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5064 }
5065 InlayHintRefreshReason::RefreshRequested => {
5066 (InvalidationStrategy::RefreshRequested, None)
5067 }
5068 };
5069
5070 if let Some(InlaySplice {
5071 to_remove,
5072 to_insert,
5073 }) = self.inlay_hint_cache.spawn_hint_refresh(
5074 reason_description,
5075 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
5076 invalidate_cache,
5077 ignore_debounce,
5078 cx,
5079 ) {
5080 self.splice_inlays(&to_remove, to_insert, cx);
5081 }
5082 }
5083
5084 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5085 self.display_map
5086 .read(cx)
5087 .current_inlays()
5088 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5089 .cloned()
5090 .collect()
5091 }
5092
5093 pub fn excerpts_for_inlay_hints_query(
5094 &self,
5095 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5096 cx: &mut Context<Editor>,
5097 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5098 let Some(project) = self.project.as_ref() else {
5099 return HashMap::default();
5100 };
5101 let project = project.read(cx);
5102 let multi_buffer = self.buffer().read(cx);
5103 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5104 let multi_buffer_visible_start = self
5105 .scroll_manager
5106 .anchor()
5107 .anchor
5108 .to_point(&multi_buffer_snapshot);
5109 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5110 multi_buffer_visible_start
5111 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5112 Bias::Left,
5113 );
5114 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5115 multi_buffer_snapshot
5116 .range_to_buffer_ranges(multi_buffer_visible_range)
5117 .into_iter()
5118 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5119 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5120 let buffer_file = project::File::from_dyn(buffer.file())?;
5121 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5122 let worktree_entry = buffer_worktree
5123 .read(cx)
5124 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5125 if worktree_entry.is_ignored {
5126 return None;
5127 }
5128
5129 let language = buffer.language()?;
5130 if let Some(restrict_to_languages) = restrict_to_languages {
5131 if !restrict_to_languages.contains(language) {
5132 return None;
5133 }
5134 }
5135 Some((
5136 excerpt_id,
5137 (
5138 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5139 buffer.version().clone(),
5140 excerpt_visible_range,
5141 ),
5142 ))
5143 })
5144 .collect()
5145 }
5146
5147 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5148 TextLayoutDetails {
5149 text_system: window.text_system().clone(),
5150 editor_style: self.style.clone().unwrap(),
5151 rem_size: window.rem_size(),
5152 scroll_anchor: self.scroll_manager.anchor(),
5153 visible_rows: self.visible_line_count(),
5154 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5155 }
5156 }
5157
5158 pub fn splice_inlays(
5159 &self,
5160 to_remove: &[InlayId],
5161 to_insert: Vec<Inlay>,
5162 cx: &mut Context<Self>,
5163 ) {
5164 self.display_map.update(cx, |display_map, cx| {
5165 display_map.splice_inlays(to_remove, to_insert, cx)
5166 });
5167 cx.notify();
5168 }
5169
5170 fn trigger_on_type_formatting(
5171 &self,
5172 input: String,
5173 window: &mut Window,
5174 cx: &mut Context<Self>,
5175 ) -> Option<Task<Result<()>>> {
5176 if input.len() != 1 {
5177 return None;
5178 }
5179
5180 let project = self.project.as_ref()?;
5181 let position = self.selections.newest_anchor().head();
5182 let (buffer, buffer_position) = self
5183 .buffer
5184 .read(cx)
5185 .text_anchor_for_position(position, cx)?;
5186
5187 let settings = language_settings::language_settings(
5188 buffer
5189 .read(cx)
5190 .language_at(buffer_position)
5191 .map(|l| l.name()),
5192 buffer.read(cx).file(),
5193 cx,
5194 );
5195 if !settings.use_on_type_format {
5196 return None;
5197 }
5198
5199 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5200 // hence we do LSP request & edit on host side only — add formats to host's history.
5201 let push_to_lsp_host_history = true;
5202 // If this is not the host, append its history with new edits.
5203 let push_to_client_history = project.read(cx).is_via_collab();
5204
5205 let on_type_formatting = project.update(cx, |project, cx| {
5206 project.on_type_format(
5207 buffer.clone(),
5208 buffer_position,
5209 input,
5210 push_to_lsp_host_history,
5211 cx,
5212 )
5213 });
5214 Some(cx.spawn_in(window, async move |editor, cx| {
5215 if let Some(transaction) = on_type_formatting.await? {
5216 if push_to_client_history {
5217 buffer
5218 .update(cx, |buffer, _| {
5219 buffer.push_transaction(transaction, Instant::now());
5220 buffer.finalize_last_transaction();
5221 })
5222 .ok();
5223 }
5224 editor.update(cx, |editor, cx| {
5225 editor.refresh_document_highlights(cx);
5226 })?;
5227 }
5228 Ok(())
5229 }))
5230 }
5231
5232 pub fn show_word_completions(
5233 &mut self,
5234 _: &ShowWordCompletions,
5235 window: &mut Window,
5236 cx: &mut Context<Self>,
5237 ) {
5238 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5239 }
5240
5241 pub fn show_completions(
5242 &mut self,
5243 options: &ShowCompletions,
5244 window: &mut Window,
5245 cx: &mut Context<Self>,
5246 ) {
5247 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5248 }
5249
5250 fn open_or_update_completions_menu(
5251 &mut self,
5252 requested_source: Option<CompletionsMenuSource>,
5253 trigger: Option<&str>,
5254 window: &mut Window,
5255 cx: &mut Context<Self>,
5256 ) {
5257 if self.pending_rename.is_some() {
5258 return;
5259 }
5260
5261 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5262
5263 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5264 // inserted and selected. To handle that case, the start of the selection is used so that
5265 // the menu starts with all choices.
5266 let position = self
5267 .selections
5268 .newest_anchor()
5269 .start
5270 .bias_right(&multibuffer_snapshot);
5271 if position.diff_base_anchor.is_some() {
5272 return;
5273 }
5274 let (buffer, buffer_position) =
5275 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5276 output
5277 } else {
5278 return;
5279 };
5280 let buffer_snapshot = buffer.read(cx).snapshot();
5281
5282 let query: Option<Arc<String>> =
5283 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5284
5285 drop(multibuffer_snapshot);
5286
5287 let provider = match requested_source {
5288 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5289 Some(CompletionsMenuSource::Words) => None,
5290 Some(CompletionsMenuSource::SnippetChoices) => {
5291 log::error!("bug: SnippetChoices requested_source is not handled");
5292 None
5293 }
5294 };
5295
5296 let sort_completions = provider
5297 .as_ref()
5298 .map_or(false, |provider| provider.sort_completions());
5299
5300 let filter_completions = provider
5301 .as_ref()
5302 .map_or(true, |provider| provider.filter_completions());
5303
5304 let trigger_kind = match trigger {
5305 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5306 CompletionTriggerKind::TRIGGER_CHARACTER
5307 }
5308 _ => CompletionTriggerKind::INVOKED,
5309 };
5310 let completion_context = CompletionContext {
5311 trigger_character: trigger.and_then(|trigger| {
5312 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5313 Some(String::from(trigger))
5314 } else {
5315 None
5316 }
5317 }),
5318 trigger_kind,
5319 };
5320
5321 // Hide the current completions menu when a trigger char is typed. Without this, cached
5322 // completions from before the trigger char may be reused (#32774). Snippet choices could
5323 // involve trigger chars, so this is skipped in that case.
5324 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5325 {
5326 let menu_is_open = matches!(
5327 self.context_menu.borrow().as_ref(),
5328 Some(CodeContextMenu::Completions(_))
5329 );
5330 if menu_is_open {
5331 self.hide_context_menu(window, cx);
5332 }
5333 }
5334
5335 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5336 if filter_completions {
5337 menu.filter(query.clone(), provider.clone(), window, cx);
5338 }
5339 // When `is_incomplete` is false, no need to re-query completions when the current query
5340 // is a suffix of the initial query.
5341 if !menu.is_incomplete {
5342 // If the new query is a suffix of the old query (typing more characters) and
5343 // the previous result was complete, the existing completions can be filtered.
5344 //
5345 // Note that this is always true for snippet completions.
5346 let query_matches = match (&menu.initial_query, &query) {
5347 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5348 (None, _) => true,
5349 _ => false,
5350 };
5351 if query_matches {
5352 let position_matches = if menu.initial_position == position {
5353 true
5354 } else {
5355 let snapshot = self.buffer.read(cx).read(cx);
5356 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5357 };
5358 if position_matches {
5359 return;
5360 }
5361 }
5362 }
5363 };
5364
5365 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5366 buffer_snapshot.surrounding_word(buffer_position)
5367 {
5368 let word_to_exclude = buffer_snapshot
5369 .text_for_range(word_range.clone())
5370 .collect::<String>();
5371 (
5372 buffer_snapshot.anchor_before(word_range.start)
5373 ..buffer_snapshot.anchor_after(buffer_position),
5374 Some(word_to_exclude),
5375 )
5376 } else {
5377 (buffer_position..buffer_position, None)
5378 };
5379
5380 let language = buffer_snapshot
5381 .language_at(buffer_position)
5382 .map(|language| language.name());
5383
5384 let completion_settings =
5385 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5386
5387 let show_completion_documentation = buffer_snapshot
5388 .settings_at(buffer_position, cx)
5389 .show_completion_documentation;
5390
5391 // The document can be large, so stay in reasonable bounds when searching for words,
5392 // otherwise completion pop-up might be slow to appear.
5393 const WORD_LOOKUP_ROWS: u32 = 5_000;
5394 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5395 let min_word_search = buffer_snapshot.clip_point(
5396 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5397 Bias::Left,
5398 );
5399 let max_word_search = buffer_snapshot.clip_point(
5400 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5401 Bias::Right,
5402 );
5403 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5404 ..buffer_snapshot.point_to_offset(max_word_search);
5405
5406 let skip_digits = query
5407 .as_ref()
5408 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5409
5410 let (mut words, provider_responses) = match &provider {
5411 Some(provider) => {
5412 let provider_responses = provider.completions(
5413 position.excerpt_id,
5414 &buffer,
5415 buffer_position,
5416 completion_context,
5417 window,
5418 cx,
5419 );
5420
5421 let words = match completion_settings.words {
5422 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5423 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5424 .background_spawn(async move {
5425 buffer_snapshot.words_in_range(WordsQuery {
5426 fuzzy_contents: None,
5427 range: word_search_range,
5428 skip_digits,
5429 })
5430 }),
5431 };
5432
5433 (words, provider_responses)
5434 }
5435 None => (
5436 cx.background_spawn(async move {
5437 buffer_snapshot.words_in_range(WordsQuery {
5438 fuzzy_contents: None,
5439 range: word_search_range,
5440 skip_digits,
5441 })
5442 }),
5443 Task::ready(Ok(Vec::new())),
5444 ),
5445 };
5446
5447 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5448
5449 let id = post_inc(&mut self.next_completion_id);
5450 let task = cx.spawn_in(window, async move |editor, cx| {
5451 let Ok(()) = editor.update(cx, |this, _| {
5452 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5453 }) else {
5454 return;
5455 };
5456
5457 // TODO: Ideally completions from different sources would be selectively re-queried, so
5458 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5459 let mut completions = Vec::new();
5460 let mut is_incomplete = false;
5461 if let Some(provider_responses) = provider_responses.await.log_err() {
5462 if !provider_responses.is_empty() {
5463 for response in provider_responses {
5464 completions.extend(response.completions);
5465 is_incomplete = is_incomplete || response.is_incomplete;
5466 }
5467 if completion_settings.words == WordsCompletionMode::Fallback {
5468 words = Task::ready(BTreeMap::default());
5469 }
5470 }
5471 }
5472
5473 let mut words = words.await;
5474 if let Some(word_to_exclude) = &word_to_exclude {
5475 words.remove(word_to_exclude);
5476 }
5477 for lsp_completion in &completions {
5478 words.remove(&lsp_completion.new_text);
5479 }
5480 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5481 replace_range: word_replace_range.clone(),
5482 new_text: word.clone(),
5483 label: CodeLabel::plain(word, None),
5484 icon_path: None,
5485 documentation: None,
5486 source: CompletionSource::BufferWord {
5487 word_range,
5488 resolved: false,
5489 },
5490 insert_text_mode: Some(InsertTextMode::AS_IS),
5491 confirm: None,
5492 }));
5493
5494 let menu = if completions.is_empty() {
5495 None
5496 } else {
5497 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5498 let languages = editor
5499 .workspace
5500 .as_ref()
5501 .and_then(|(workspace, _)| workspace.upgrade())
5502 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5503 let menu = CompletionsMenu::new(
5504 id,
5505 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5506 sort_completions,
5507 show_completion_documentation,
5508 position,
5509 query.clone(),
5510 is_incomplete,
5511 buffer.clone(),
5512 completions.into(),
5513 snippet_sort_order,
5514 languages,
5515 language,
5516 cx,
5517 );
5518
5519 let query = if filter_completions { query } else { None };
5520 let matches_task = if let Some(query) = query {
5521 menu.do_async_filtering(query, cx)
5522 } else {
5523 Task::ready(menu.unfiltered_matches())
5524 };
5525 (menu, matches_task)
5526 }) else {
5527 return;
5528 };
5529
5530 let matches = matches_task.await;
5531
5532 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5533 // Newer menu already set, so exit.
5534 match editor.context_menu.borrow().as_ref() {
5535 Some(CodeContextMenu::Completions(prev_menu)) => {
5536 if prev_menu.id > id {
5537 return;
5538 }
5539 }
5540 _ => {}
5541 };
5542
5543 // Only valid to take prev_menu because it the new menu is immediately set
5544 // below, or the menu is hidden.
5545 match editor.context_menu.borrow_mut().take() {
5546 Some(CodeContextMenu::Completions(prev_menu)) => {
5547 let position_matches =
5548 if prev_menu.initial_position == menu.initial_position {
5549 true
5550 } else {
5551 let snapshot = editor.buffer.read(cx).read(cx);
5552 prev_menu.initial_position.to_offset(&snapshot)
5553 == menu.initial_position.to_offset(&snapshot)
5554 };
5555 if position_matches {
5556 // Preserve markdown cache before `set_filter_results` because it will
5557 // try to populate the documentation cache.
5558 menu.preserve_markdown_cache(prev_menu);
5559 }
5560 }
5561 _ => {}
5562 };
5563
5564 menu.set_filter_results(matches, provider, window, cx);
5565 }) else {
5566 return;
5567 };
5568
5569 menu.visible().then_some(menu)
5570 };
5571
5572 editor
5573 .update_in(cx, |editor, window, cx| {
5574 if editor.focus_handle.is_focused(window) {
5575 if let Some(menu) = menu {
5576 *editor.context_menu.borrow_mut() =
5577 Some(CodeContextMenu::Completions(menu));
5578
5579 crate::hover_popover::hide_hover(editor, cx);
5580 if editor.show_edit_predictions_in_menu() {
5581 editor.update_visible_inline_completion(window, cx);
5582 } else {
5583 editor.discard_inline_completion(false, cx);
5584 }
5585
5586 cx.notify();
5587 return;
5588 }
5589 }
5590
5591 if editor.completion_tasks.len() <= 1 {
5592 // If there are no more completion tasks and the last menu was empty, we should hide it.
5593 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5594 // If it was already hidden and we don't show inline completions in the menu, we should
5595 // also show the inline-completion when available.
5596 if was_hidden && editor.show_edit_predictions_in_menu() {
5597 editor.update_visible_inline_completion(window, cx);
5598 }
5599 }
5600 })
5601 .ok();
5602 });
5603
5604 self.completion_tasks.push((id, task));
5605 }
5606
5607 #[cfg(feature = "test-support")]
5608 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5609 let menu = self.context_menu.borrow();
5610 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5611 let completions = menu.completions.borrow();
5612 Some(completions.to_vec())
5613 } else {
5614 None
5615 }
5616 }
5617
5618 pub fn with_completions_menu_matching_id<R>(
5619 &self,
5620 id: CompletionId,
5621 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5622 ) -> R {
5623 let mut context_menu = self.context_menu.borrow_mut();
5624 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5625 return f(None);
5626 };
5627 if completions_menu.id != id {
5628 return f(None);
5629 }
5630 f(Some(completions_menu))
5631 }
5632
5633 pub fn confirm_completion(
5634 &mut self,
5635 action: &ConfirmCompletion,
5636 window: &mut Window,
5637 cx: &mut Context<Self>,
5638 ) -> Option<Task<Result<()>>> {
5639 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5640 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5641 }
5642
5643 pub fn confirm_completion_insert(
5644 &mut self,
5645 _: &ConfirmCompletionInsert,
5646 window: &mut Window,
5647 cx: &mut Context<Self>,
5648 ) -> Option<Task<Result<()>>> {
5649 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5650 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5651 }
5652
5653 pub fn confirm_completion_replace(
5654 &mut self,
5655 _: &ConfirmCompletionReplace,
5656 window: &mut Window,
5657 cx: &mut Context<Self>,
5658 ) -> Option<Task<Result<()>>> {
5659 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5660 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5661 }
5662
5663 pub fn compose_completion(
5664 &mut self,
5665 action: &ComposeCompletion,
5666 window: &mut Window,
5667 cx: &mut Context<Self>,
5668 ) -> Option<Task<Result<()>>> {
5669 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5670 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5671 }
5672
5673 fn do_completion(
5674 &mut self,
5675 item_ix: Option<usize>,
5676 intent: CompletionIntent,
5677 window: &mut Window,
5678 cx: &mut Context<Editor>,
5679 ) -> Option<Task<Result<()>>> {
5680 use language::ToOffset as _;
5681
5682 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5683 else {
5684 return None;
5685 };
5686
5687 let candidate_id = {
5688 let entries = completions_menu.entries.borrow();
5689 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5690 if self.show_edit_predictions_in_menu() {
5691 self.discard_inline_completion(true, cx);
5692 }
5693 mat.candidate_id
5694 };
5695
5696 let completion = completions_menu
5697 .completions
5698 .borrow()
5699 .get(candidate_id)?
5700 .clone();
5701 cx.stop_propagation();
5702
5703 let buffer_handle = completions_menu.buffer.clone();
5704
5705 let CompletionEdit {
5706 new_text,
5707 snippet,
5708 replace_range,
5709 } = process_completion_for_edit(
5710 &completion,
5711 intent,
5712 &buffer_handle,
5713 &completions_menu.initial_position.text_anchor,
5714 cx,
5715 );
5716
5717 let buffer = buffer_handle.read(cx);
5718 let snapshot = self.buffer.read(cx).snapshot(cx);
5719 let newest_anchor = self.selections.newest_anchor();
5720 let replace_range_multibuffer = {
5721 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5722 let multibuffer_anchor = snapshot
5723 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5724 .unwrap()
5725 ..snapshot
5726 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5727 .unwrap();
5728 multibuffer_anchor.start.to_offset(&snapshot)
5729 ..multibuffer_anchor.end.to_offset(&snapshot)
5730 };
5731 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5732 return None;
5733 }
5734
5735 let old_text = buffer
5736 .text_for_range(replace_range.clone())
5737 .collect::<String>();
5738 let lookbehind = newest_anchor
5739 .start
5740 .text_anchor
5741 .to_offset(buffer)
5742 .saturating_sub(replace_range.start);
5743 let lookahead = replace_range
5744 .end
5745 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5746 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5747 let suffix = &old_text[lookbehind.min(old_text.len())..];
5748
5749 let selections = self.selections.all::<usize>(cx);
5750 let mut ranges = Vec::new();
5751 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5752
5753 for selection in &selections {
5754 let range = if selection.id == newest_anchor.id {
5755 replace_range_multibuffer.clone()
5756 } else {
5757 let mut range = selection.range();
5758
5759 // if prefix is present, don't duplicate it
5760 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5761 range.start = range.start.saturating_sub(lookbehind);
5762
5763 // if suffix is also present, mimic the newest cursor and replace it
5764 if selection.id != newest_anchor.id
5765 && snapshot.contains_str_at(range.end, suffix)
5766 {
5767 range.end += lookahead;
5768 }
5769 }
5770 range
5771 };
5772
5773 ranges.push(range.clone());
5774
5775 if !self.linked_edit_ranges.is_empty() {
5776 let start_anchor = snapshot.anchor_before(range.start);
5777 let end_anchor = snapshot.anchor_after(range.end);
5778 if let Some(ranges) = self
5779 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5780 {
5781 for (buffer, edits) in ranges {
5782 linked_edits
5783 .entry(buffer.clone())
5784 .or_default()
5785 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5786 }
5787 }
5788 }
5789 }
5790
5791 let common_prefix_len = old_text
5792 .chars()
5793 .zip(new_text.chars())
5794 .take_while(|(a, b)| a == b)
5795 .map(|(a, _)| a.len_utf8())
5796 .sum::<usize>();
5797
5798 cx.emit(EditorEvent::InputHandled {
5799 utf16_range_to_replace: None,
5800 text: new_text[common_prefix_len..].into(),
5801 });
5802
5803 self.transact(window, cx, |this, window, cx| {
5804 if let Some(mut snippet) = snippet {
5805 snippet.text = new_text.to_string();
5806 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5807 } else {
5808 this.buffer.update(cx, |buffer, cx| {
5809 let auto_indent = match completion.insert_text_mode {
5810 Some(InsertTextMode::AS_IS) => None,
5811 _ => this.autoindent_mode.clone(),
5812 };
5813 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5814 buffer.edit(edits, auto_indent, cx);
5815 });
5816 }
5817 for (buffer, edits) in linked_edits {
5818 buffer.update(cx, |buffer, cx| {
5819 let snapshot = buffer.snapshot();
5820 let edits = edits
5821 .into_iter()
5822 .map(|(range, text)| {
5823 use text::ToPoint as TP;
5824 let end_point = TP::to_point(&range.end, &snapshot);
5825 let start_point = TP::to_point(&range.start, &snapshot);
5826 (start_point..end_point, text)
5827 })
5828 .sorted_by_key(|(range, _)| range.start);
5829 buffer.edit(edits, None, cx);
5830 })
5831 }
5832
5833 this.refresh_inline_completion(true, false, window, cx);
5834 });
5835
5836 let show_new_completions_on_confirm = completion
5837 .confirm
5838 .as_ref()
5839 .map_or(false, |confirm| confirm(intent, window, cx));
5840 if show_new_completions_on_confirm {
5841 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5842 }
5843
5844 let provider = self.completion_provider.as_ref()?;
5845 drop(completion);
5846 let apply_edits = provider.apply_additional_edits_for_completion(
5847 buffer_handle,
5848 completions_menu.completions.clone(),
5849 candidate_id,
5850 true,
5851 cx,
5852 );
5853
5854 let editor_settings = EditorSettings::get_global(cx);
5855 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5856 // After the code completion is finished, users often want to know what signatures are needed.
5857 // so we should automatically call signature_help
5858 self.show_signature_help(&ShowSignatureHelp, window, cx);
5859 }
5860
5861 Some(cx.foreground_executor().spawn(async move {
5862 apply_edits.await?;
5863 Ok(())
5864 }))
5865 }
5866
5867 pub fn toggle_code_actions(
5868 &mut self,
5869 action: &ToggleCodeActions,
5870 window: &mut Window,
5871 cx: &mut Context<Self>,
5872 ) {
5873 let quick_launch = action.quick_launch;
5874 let mut context_menu = self.context_menu.borrow_mut();
5875 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5876 if code_actions.deployed_from == action.deployed_from {
5877 // Toggle if we're selecting the same one
5878 *context_menu = None;
5879 cx.notify();
5880 return;
5881 } else {
5882 // Otherwise, clear it and start a new one
5883 *context_menu = None;
5884 cx.notify();
5885 }
5886 }
5887 drop(context_menu);
5888 let snapshot = self.snapshot(window, cx);
5889 let deployed_from = action.deployed_from.clone();
5890 let action = action.clone();
5891 self.completion_tasks.clear();
5892 self.discard_inline_completion(false, cx);
5893
5894 let multibuffer_point = match &action.deployed_from {
5895 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5896 DisplayPoint::new(*row, 0).to_point(&snapshot)
5897 }
5898 _ => self.selections.newest::<Point>(cx).head(),
5899 };
5900 let Some((buffer, buffer_row)) = snapshot
5901 .buffer_snapshot
5902 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5903 .and_then(|(buffer_snapshot, range)| {
5904 self.buffer()
5905 .read(cx)
5906 .buffer(buffer_snapshot.remote_id())
5907 .map(|buffer| (buffer, range.start.row))
5908 })
5909 else {
5910 return;
5911 };
5912 let buffer_id = buffer.read(cx).remote_id();
5913 let tasks = self
5914 .tasks
5915 .get(&(buffer_id, buffer_row))
5916 .map(|t| Arc::new(t.to_owned()));
5917
5918 if !self.focus_handle.is_focused(window) {
5919 return;
5920 }
5921 let project = self.project.clone();
5922
5923 let code_actions_task = match deployed_from {
5924 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
5925 _ => self.code_actions(buffer_row, window, cx),
5926 };
5927
5928 let runnable_task = match deployed_from {
5929 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
5930 _ => {
5931 let mut task_context_task = Task::ready(None);
5932 if let Some(tasks) = &tasks {
5933 if let Some(project) = project {
5934 task_context_task =
5935 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
5936 }
5937 }
5938
5939 cx.spawn_in(window, {
5940 let buffer = buffer.clone();
5941 async move |editor, cx| {
5942 let task_context = task_context_task.await;
5943
5944 let resolved_tasks =
5945 tasks
5946 .zip(task_context.clone())
5947 .map(|(tasks, task_context)| ResolvedTasks {
5948 templates: tasks.resolve(&task_context).collect(),
5949 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5950 multibuffer_point.row,
5951 tasks.column,
5952 )),
5953 });
5954 let debug_scenarios = editor
5955 .update(cx, |editor, cx| {
5956 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
5957 })?
5958 .await;
5959 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
5960 }
5961 })
5962 }
5963 };
5964
5965 cx.spawn_in(window, async move |editor, cx| {
5966 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
5967 let code_actions = code_actions_task.await;
5968 let spawn_straight_away = quick_launch
5969 && resolved_tasks
5970 .as_ref()
5971 .map_or(false, |tasks| tasks.templates.len() == 1)
5972 && code_actions
5973 .as_ref()
5974 .map_or(true, |actions| actions.is_empty())
5975 && debug_scenarios.is_empty();
5976
5977 editor.update_in(cx, |editor, window, cx| {
5978 crate::hover_popover::hide_hover(editor, cx);
5979 let actions = CodeActionContents::new(
5980 resolved_tasks,
5981 code_actions,
5982 debug_scenarios,
5983 task_context.unwrap_or_default(),
5984 );
5985
5986 // Don't show the menu if there are no actions available
5987 if actions.is_empty() {
5988 cx.notify();
5989 return Task::ready(Ok(()));
5990 }
5991
5992 *editor.context_menu.borrow_mut() =
5993 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5994 buffer,
5995 actions,
5996 selected_item: Default::default(),
5997 scroll_handle: UniformListScrollHandle::default(),
5998 deployed_from,
5999 }));
6000 cx.notify();
6001 if spawn_straight_away {
6002 if let Some(task) = editor.confirm_code_action(
6003 &ConfirmCodeAction { item_ix: Some(0) },
6004 window,
6005 cx,
6006 ) {
6007 return task;
6008 }
6009 }
6010
6011 Task::ready(Ok(()))
6012 })
6013 })
6014 .detach_and_log_err(cx);
6015 }
6016
6017 fn debug_scenarios(
6018 &mut self,
6019 resolved_tasks: &Option<ResolvedTasks>,
6020 buffer: &Entity<Buffer>,
6021 cx: &mut App,
6022 ) -> Task<Vec<task::DebugScenario>> {
6023 maybe!({
6024 let project = self.project.as_ref()?;
6025 let dap_store = project.read(cx).dap_store();
6026 let mut scenarios = vec![];
6027 let resolved_tasks = resolved_tasks.as_ref()?;
6028 let buffer = buffer.read(cx);
6029 let language = buffer.language()?;
6030 let file = buffer.file();
6031 let debug_adapter = language_settings(language.name().into(), file, cx)
6032 .debuggers
6033 .first()
6034 .map(SharedString::from)
6035 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6036
6037 dap_store.update(cx, |dap_store, cx| {
6038 for (_, task) in &resolved_tasks.templates {
6039 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6040 task.original_task().clone(),
6041 debug_adapter.clone().into(),
6042 task.display_label().to_owned().into(),
6043 cx,
6044 );
6045 scenarios.push(maybe_scenario);
6046 }
6047 });
6048 Some(cx.background_spawn(async move {
6049 let scenarios = futures::future::join_all(scenarios)
6050 .await
6051 .into_iter()
6052 .flatten()
6053 .collect::<Vec<_>>();
6054 scenarios
6055 }))
6056 })
6057 .unwrap_or_else(|| Task::ready(vec![]))
6058 }
6059
6060 fn code_actions(
6061 &mut self,
6062 buffer_row: u32,
6063 window: &mut Window,
6064 cx: &mut Context<Self>,
6065 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6066 let mut task = self.code_actions_task.take();
6067 cx.spawn_in(window, async move |editor, cx| {
6068 while let Some(prev_task) = task {
6069 prev_task.await.log_err();
6070 task = editor
6071 .update(cx, |this, _| this.code_actions_task.take())
6072 .ok()?;
6073 }
6074
6075 editor
6076 .update(cx, |editor, cx| {
6077 editor
6078 .available_code_actions
6079 .clone()
6080 .and_then(|(location, code_actions)| {
6081 let snapshot = location.buffer.read(cx).snapshot();
6082 let point_range = location.range.to_point(&snapshot);
6083 let point_range = point_range.start.row..=point_range.end.row;
6084 if point_range.contains(&buffer_row) {
6085 Some(code_actions)
6086 } else {
6087 None
6088 }
6089 })
6090 })
6091 .ok()
6092 .flatten()
6093 })
6094 }
6095
6096 pub fn confirm_code_action(
6097 &mut self,
6098 action: &ConfirmCodeAction,
6099 window: &mut Window,
6100 cx: &mut Context<Self>,
6101 ) -> Option<Task<Result<()>>> {
6102 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6103
6104 let actions_menu =
6105 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6106 menu
6107 } else {
6108 return None;
6109 };
6110
6111 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6112 let action = actions_menu.actions.get(action_ix)?;
6113 let title = action.label();
6114 let buffer = actions_menu.buffer;
6115 let workspace = self.workspace()?;
6116
6117 match action {
6118 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6119 workspace.update(cx, |workspace, cx| {
6120 workspace.schedule_resolved_task(
6121 task_source_kind,
6122 resolved_task,
6123 false,
6124 window,
6125 cx,
6126 );
6127
6128 Some(Task::ready(Ok(())))
6129 })
6130 }
6131 CodeActionsItem::CodeAction {
6132 excerpt_id,
6133 action,
6134 provider,
6135 } => {
6136 let apply_code_action =
6137 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6138 let workspace = workspace.downgrade();
6139 Some(cx.spawn_in(window, async move |editor, cx| {
6140 let project_transaction = apply_code_action.await?;
6141 Self::open_project_transaction(
6142 &editor,
6143 workspace,
6144 project_transaction,
6145 title,
6146 cx,
6147 )
6148 .await
6149 }))
6150 }
6151 CodeActionsItem::DebugScenario(scenario) => {
6152 let context = actions_menu.actions.context.clone();
6153
6154 workspace.update(cx, |workspace, cx| {
6155 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6156 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
6157 });
6158 Some(Task::ready(Ok(())))
6159 }
6160 }
6161 }
6162
6163 pub async fn open_project_transaction(
6164 this: &WeakEntity<Editor>,
6165 workspace: WeakEntity<Workspace>,
6166 transaction: ProjectTransaction,
6167 title: String,
6168 cx: &mut AsyncWindowContext,
6169 ) -> Result<()> {
6170 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6171 cx.update(|_, cx| {
6172 entries.sort_unstable_by_key(|(buffer, _)| {
6173 buffer.read(cx).file().map(|f| f.path().clone())
6174 });
6175 })?;
6176
6177 // If the project transaction's edits are all contained within this editor, then
6178 // avoid opening a new editor to display them.
6179
6180 if let Some((buffer, transaction)) = entries.first() {
6181 if entries.len() == 1 {
6182 let excerpt = this.update(cx, |editor, cx| {
6183 editor
6184 .buffer()
6185 .read(cx)
6186 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6187 })?;
6188 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6189 if excerpted_buffer == *buffer {
6190 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6191 let excerpt_range = excerpt_range.to_offset(buffer);
6192 buffer
6193 .edited_ranges_for_transaction::<usize>(transaction)
6194 .all(|range| {
6195 excerpt_range.start <= range.start
6196 && excerpt_range.end >= range.end
6197 })
6198 })?;
6199
6200 if all_edits_within_excerpt {
6201 return Ok(());
6202 }
6203 }
6204 }
6205 }
6206 } else {
6207 return Ok(());
6208 }
6209
6210 let mut ranges_to_highlight = Vec::new();
6211 let excerpt_buffer = cx.new(|cx| {
6212 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6213 for (buffer_handle, transaction) in &entries {
6214 let edited_ranges = buffer_handle
6215 .read(cx)
6216 .edited_ranges_for_transaction::<Point>(transaction)
6217 .collect::<Vec<_>>();
6218 let (ranges, _) = multibuffer.set_excerpts_for_path(
6219 PathKey::for_buffer(buffer_handle, cx),
6220 buffer_handle.clone(),
6221 edited_ranges,
6222 DEFAULT_MULTIBUFFER_CONTEXT,
6223 cx,
6224 );
6225
6226 ranges_to_highlight.extend(ranges);
6227 }
6228 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6229 multibuffer
6230 })?;
6231
6232 workspace.update_in(cx, |workspace, window, cx| {
6233 let project = workspace.project().clone();
6234 let editor =
6235 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6236 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6237 editor.update(cx, |editor, cx| {
6238 editor.highlight_background::<Self>(
6239 &ranges_to_highlight,
6240 |theme| theme.colors().editor_highlighted_line_background,
6241 cx,
6242 );
6243 });
6244 })?;
6245
6246 Ok(())
6247 }
6248
6249 pub fn clear_code_action_providers(&mut self) {
6250 self.code_action_providers.clear();
6251 self.available_code_actions.take();
6252 }
6253
6254 pub fn add_code_action_provider(
6255 &mut self,
6256 provider: Rc<dyn CodeActionProvider>,
6257 window: &mut Window,
6258 cx: &mut Context<Self>,
6259 ) {
6260 if self
6261 .code_action_providers
6262 .iter()
6263 .any(|existing_provider| existing_provider.id() == provider.id())
6264 {
6265 return;
6266 }
6267
6268 self.code_action_providers.push(provider);
6269 self.refresh_code_actions(window, cx);
6270 }
6271
6272 pub fn remove_code_action_provider(
6273 &mut self,
6274 id: Arc<str>,
6275 window: &mut Window,
6276 cx: &mut Context<Self>,
6277 ) {
6278 self.code_action_providers
6279 .retain(|provider| provider.id() != id);
6280 self.refresh_code_actions(window, cx);
6281 }
6282
6283 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6284 !self.code_action_providers.is_empty()
6285 && EditorSettings::get_global(cx).toolbar.code_actions
6286 }
6287
6288 pub fn has_available_code_actions(&self) -> bool {
6289 self.available_code_actions
6290 .as_ref()
6291 .is_some_and(|(_, actions)| !actions.is_empty())
6292 }
6293
6294 fn render_inline_code_actions(
6295 &self,
6296 icon_size: ui::IconSize,
6297 display_row: DisplayRow,
6298 is_active: bool,
6299 cx: &mut Context<Self>,
6300 ) -> AnyElement {
6301 let show_tooltip = !self.context_menu_visible();
6302 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6303 .icon_size(icon_size)
6304 .shape(ui::IconButtonShape::Square)
6305 .style(ButtonStyle::Transparent)
6306 .icon_color(ui::Color::Hidden)
6307 .toggle_state(is_active)
6308 .when(show_tooltip, |this| {
6309 this.tooltip({
6310 let focus_handle = self.focus_handle.clone();
6311 move |window, cx| {
6312 Tooltip::for_action_in(
6313 "Toggle Code Actions",
6314 &ToggleCodeActions {
6315 deployed_from: None,
6316 quick_launch: false,
6317 },
6318 &focus_handle,
6319 window,
6320 cx,
6321 )
6322 }
6323 })
6324 })
6325 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6326 window.focus(&editor.focus_handle(cx));
6327 editor.toggle_code_actions(
6328 &crate::actions::ToggleCodeActions {
6329 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6330 display_row,
6331 )),
6332 quick_launch: false,
6333 },
6334 window,
6335 cx,
6336 );
6337 }))
6338 .into_any_element()
6339 }
6340
6341 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6342 &self.context_menu
6343 }
6344
6345 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6346 let newest_selection = self.selections.newest_anchor().clone();
6347 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6348 let buffer = self.buffer.read(cx);
6349 if newest_selection.head().diff_base_anchor.is_some() {
6350 return None;
6351 }
6352 let (start_buffer, start) =
6353 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6354 let (end_buffer, end) =
6355 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6356 if start_buffer != end_buffer {
6357 return None;
6358 }
6359
6360 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6361 cx.background_executor()
6362 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6363 .await;
6364
6365 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6366 let providers = this.code_action_providers.clone();
6367 let tasks = this
6368 .code_action_providers
6369 .iter()
6370 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6371 .collect::<Vec<_>>();
6372 (providers, tasks)
6373 })?;
6374
6375 let mut actions = Vec::new();
6376 for (provider, provider_actions) in
6377 providers.into_iter().zip(future::join_all(tasks).await)
6378 {
6379 if let Some(provider_actions) = provider_actions.log_err() {
6380 actions.extend(provider_actions.into_iter().map(|action| {
6381 AvailableCodeAction {
6382 excerpt_id: newest_selection.start.excerpt_id,
6383 action,
6384 provider: provider.clone(),
6385 }
6386 }));
6387 }
6388 }
6389
6390 this.update(cx, |this, cx| {
6391 this.available_code_actions = if actions.is_empty() {
6392 None
6393 } else {
6394 Some((
6395 Location {
6396 buffer: start_buffer,
6397 range: start..end,
6398 },
6399 actions.into(),
6400 ))
6401 };
6402 cx.notify();
6403 })
6404 }));
6405 None
6406 }
6407
6408 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6409 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6410 self.show_git_blame_inline = false;
6411
6412 self.show_git_blame_inline_delay_task =
6413 Some(cx.spawn_in(window, async move |this, cx| {
6414 cx.background_executor().timer(delay).await;
6415
6416 this.update(cx, |this, cx| {
6417 this.show_git_blame_inline = true;
6418 cx.notify();
6419 })
6420 .log_err();
6421 }));
6422 }
6423 }
6424
6425 fn show_blame_popover(
6426 &mut self,
6427 blame_entry: &BlameEntry,
6428 position: gpui::Point<Pixels>,
6429 cx: &mut Context<Self>,
6430 ) {
6431 if let Some(state) = &mut self.inline_blame_popover {
6432 state.hide_task.take();
6433 } else {
6434 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6435 let blame_entry = blame_entry.clone();
6436 let show_task = cx.spawn(async move |editor, cx| {
6437 cx.background_executor()
6438 .timer(std::time::Duration::from_millis(delay))
6439 .await;
6440 editor
6441 .update(cx, |editor, cx| {
6442 editor.inline_blame_popover_show_task.take();
6443 let Some(blame) = editor.blame.as_ref() else {
6444 return;
6445 };
6446 let blame = blame.read(cx);
6447 let details = blame.details_for_entry(&blame_entry);
6448 let markdown = cx.new(|cx| {
6449 Markdown::new(
6450 details
6451 .as_ref()
6452 .map(|message| message.message.clone())
6453 .unwrap_or_default(),
6454 None,
6455 None,
6456 cx,
6457 )
6458 });
6459 editor.inline_blame_popover = Some(InlineBlamePopover {
6460 position,
6461 hide_task: None,
6462 popover_bounds: None,
6463 popover_state: InlineBlamePopoverState {
6464 scroll_handle: ScrollHandle::new(),
6465 commit_message: details,
6466 markdown,
6467 },
6468 });
6469 cx.notify();
6470 })
6471 .ok();
6472 });
6473 self.inline_blame_popover_show_task = Some(show_task);
6474 }
6475 }
6476
6477 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6478 self.inline_blame_popover_show_task.take();
6479 if let Some(state) = &mut self.inline_blame_popover {
6480 let hide_task = cx.spawn(async move |editor, cx| {
6481 cx.background_executor()
6482 .timer(std::time::Duration::from_millis(100))
6483 .await;
6484 editor
6485 .update(cx, |editor, cx| {
6486 editor.inline_blame_popover.take();
6487 cx.notify();
6488 })
6489 .ok();
6490 });
6491 state.hide_task = Some(hide_task);
6492 }
6493 }
6494
6495 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6496 if self.pending_rename.is_some() {
6497 return None;
6498 }
6499
6500 let provider = self.semantics_provider.clone()?;
6501 let buffer = self.buffer.read(cx);
6502 let newest_selection = self.selections.newest_anchor().clone();
6503 let cursor_position = newest_selection.head();
6504 let (cursor_buffer, cursor_buffer_position) =
6505 buffer.text_anchor_for_position(cursor_position, cx)?;
6506 let (tail_buffer, tail_buffer_position) =
6507 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6508 if cursor_buffer != tail_buffer {
6509 return None;
6510 }
6511
6512 let snapshot = cursor_buffer.read(cx).snapshot();
6513 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6514 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6515 if start_word_range != end_word_range {
6516 self.document_highlights_task.take();
6517 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6518 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6519 return None;
6520 }
6521
6522 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6523 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6524 cx.background_executor()
6525 .timer(Duration::from_millis(debounce))
6526 .await;
6527
6528 let highlights = if let Some(highlights) = cx
6529 .update(|cx| {
6530 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6531 })
6532 .ok()
6533 .flatten()
6534 {
6535 highlights.await.log_err()
6536 } else {
6537 None
6538 };
6539
6540 if let Some(highlights) = highlights {
6541 this.update(cx, |this, cx| {
6542 if this.pending_rename.is_some() {
6543 return;
6544 }
6545
6546 let buffer_id = cursor_position.buffer_id;
6547 let buffer = this.buffer.read(cx);
6548 if !buffer
6549 .text_anchor_for_position(cursor_position, cx)
6550 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6551 {
6552 return;
6553 }
6554
6555 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6556 let mut write_ranges = Vec::new();
6557 let mut read_ranges = Vec::new();
6558 for highlight in highlights {
6559 for (excerpt_id, excerpt_range) in
6560 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6561 {
6562 let start = highlight
6563 .range
6564 .start
6565 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6566 let end = highlight
6567 .range
6568 .end
6569 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6570 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6571 continue;
6572 }
6573
6574 let range = Anchor {
6575 buffer_id,
6576 excerpt_id,
6577 text_anchor: start,
6578 diff_base_anchor: None,
6579 }..Anchor {
6580 buffer_id,
6581 excerpt_id,
6582 text_anchor: end,
6583 diff_base_anchor: None,
6584 };
6585 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6586 write_ranges.push(range);
6587 } else {
6588 read_ranges.push(range);
6589 }
6590 }
6591 }
6592
6593 this.highlight_background::<DocumentHighlightRead>(
6594 &read_ranges,
6595 |theme| theme.colors().editor_document_highlight_read_background,
6596 cx,
6597 );
6598 this.highlight_background::<DocumentHighlightWrite>(
6599 &write_ranges,
6600 |theme| theme.colors().editor_document_highlight_write_background,
6601 cx,
6602 );
6603 cx.notify();
6604 })
6605 .log_err();
6606 }
6607 }));
6608 None
6609 }
6610
6611 fn prepare_highlight_query_from_selection(
6612 &mut self,
6613 cx: &mut Context<Editor>,
6614 ) -> Option<(String, Range<Anchor>)> {
6615 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6616 return None;
6617 }
6618 if !EditorSettings::get_global(cx).selection_highlight {
6619 return None;
6620 }
6621 if self.selections.count() != 1 || self.selections.line_mode {
6622 return None;
6623 }
6624 let selection = self.selections.newest::<Point>(cx);
6625 if selection.is_empty() || selection.start.row != selection.end.row {
6626 return None;
6627 }
6628 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6629 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6630 let query = multi_buffer_snapshot
6631 .text_for_range(selection_anchor_range.clone())
6632 .collect::<String>();
6633 if query.trim().is_empty() {
6634 return None;
6635 }
6636 Some((query, selection_anchor_range))
6637 }
6638
6639 fn update_selection_occurrence_highlights(
6640 &mut self,
6641 query_text: String,
6642 query_range: Range<Anchor>,
6643 multi_buffer_range_to_query: Range<Point>,
6644 use_debounce: bool,
6645 window: &mut Window,
6646 cx: &mut Context<Editor>,
6647 ) -> Task<()> {
6648 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6649 cx.spawn_in(window, async move |editor, cx| {
6650 if use_debounce {
6651 cx.background_executor()
6652 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6653 .await;
6654 }
6655 let match_task = cx.background_spawn(async move {
6656 let buffer_ranges = multi_buffer_snapshot
6657 .range_to_buffer_ranges(multi_buffer_range_to_query)
6658 .into_iter()
6659 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6660 let mut match_ranges = Vec::new();
6661 let Ok(regex) = project::search::SearchQuery::text(
6662 query_text.clone(),
6663 false,
6664 false,
6665 false,
6666 Default::default(),
6667 Default::default(),
6668 false,
6669 None,
6670 ) else {
6671 return Vec::default();
6672 };
6673 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6674 match_ranges.extend(
6675 regex
6676 .search(&buffer_snapshot, Some(search_range.clone()))
6677 .await
6678 .into_iter()
6679 .filter_map(|match_range| {
6680 let match_start = buffer_snapshot
6681 .anchor_after(search_range.start + match_range.start);
6682 let match_end = buffer_snapshot
6683 .anchor_before(search_range.start + match_range.end);
6684 let match_anchor_range = Anchor::range_in_buffer(
6685 excerpt_id,
6686 buffer_snapshot.remote_id(),
6687 match_start..match_end,
6688 );
6689 (match_anchor_range != query_range).then_some(match_anchor_range)
6690 }),
6691 );
6692 }
6693 match_ranges
6694 });
6695 let match_ranges = match_task.await;
6696 editor
6697 .update_in(cx, |editor, _, cx| {
6698 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6699 if !match_ranges.is_empty() {
6700 editor.highlight_background::<SelectedTextHighlight>(
6701 &match_ranges,
6702 |theme| theme.colors().editor_document_highlight_bracket_background,
6703 cx,
6704 )
6705 }
6706 })
6707 .log_err();
6708 })
6709 }
6710
6711 fn refresh_selected_text_highlights(
6712 &mut self,
6713 on_buffer_edit: bool,
6714 window: &mut Window,
6715 cx: &mut Context<Editor>,
6716 ) {
6717 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6718 else {
6719 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6720 self.quick_selection_highlight_task.take();
6721 self.debounced_selection_highlight_task.take();
6722 return;
6723 };
6724 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6725 if on_buffer_edit
6726 || self
6727 .quick_selection_highlight_task
6728 .as_ref()
6729 .map_or(true, |(prev_anchor_range, _)| {
6730 prev_anchor_range != &query_range
6731 })
6732 {
6733 let multi_buffer_visible_start = self
6734 .scroll_manager
6735 .anchor()
6736 .anchor
6737 .to_point(&multi_buffer_snapshot);
6738 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6739 multi_buffer_visible_start
6740 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6741 Bias::Left,
6742 );
6743 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6744 self.quick_selection_highlight_task = Some((
6745 query_range.clone(),
6746 self.update_selection_occurrence_highlights(
6747 query_text.clone(),
6748 query_range.clone(),
6749 multi_buffer_visible_range,
6750 false,
6751 window,
6752 cx,
6753 ),
6754 ));
6755 }
6756 if on_buffer_edit
6757 || self
6758 .debounced_selection_highlight_task
6759 .as_ref()
6760 .map_or(true, |(prev_anchor_range, _)| {
6761 prev_anchor_range != &query_range
6762 })
6763 {
6764 let multi_buffer_start = multi_buffer_snapshot
6765 .anchor_before(0)
6766 .to_point(&multi_buffer_snapshot);
6767 let multi_buffer_end = multi_buffer_snapshot
6768 .anchor_after(multi_buffer_snapshot.len())
6769 .to_point(&multi_buffer_snapshot);
6770 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6771 self.debounced_selection_highlight_task = Some((
6772 query_range.clone(),
6773 self.update_selection_occurrence_highlights(
6774 query_text,
6775 query_range,
6776 multi_buffer_full_range,
6777 true,
6778 window,
6779 cx,
6780 ),
6781 ));
6782 }
6783 }
6784
6785 pub fn refresh_inline_completion(
6786 &mut self,
6787 debounce: bool,
6788 user_requested: bool,
6789 window: &mut Window,
6790 cx: &mut Context<Self>,
6791 ) -> Option<()> {
6792 let provider = self.edit_prediction_provider()?;
6793 let cursor = self.selections.newest_anchor().head();
6794 let (buffer, cursor_buffer_position) =
6795 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6796
6797 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6798 self.discard_inline_completion(false, cx);
6799 return None;
6800 }
6801
6802 if !user_requested
6803 && (!self.should_show_edit_predictions()
6804 || !self.is_focused(window)
6805 || buffer.read(cx).is_empty())
6806 {
6807 self.discard_inline_completion(false, cx);
6808 return None;
6809 }
6810
6811 self.update_visible_inline_completion(window, cx);
6812 provider.refresh(
6813 self.project.clone(),
6814 buffer,
6815 cursor_buffer_position,
6816 debounce,
6817 cx,
6818 );
6819 Some(())
6820 }
6821
6822 fn show_edit_predictions_in_menu(&self) -> bool {
6823 match self.edit_prediction_settings {
6824 EditPredictionSettings::Disabled => false,
6825 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6826 }
6827 }
6828
6829 pub fn edit_predictions_enabled(&self) -> bool {
6830 match self.edit_prediction_settings {
6831 EditPredictionSettings::Disabled => false,
6832 EditPredictionSettings::Enabled { .. } => true,
6833 }
6834 }
6835
6836 fn edit_prediction_requires_modifier(&self) -> bool {
6837 match self.edit_prediction_settings {
6838 EditPredictionSettings::Disabled => false,
6839 EditPredictionSettings::Enabled {
6840 preview_requires_modifier,
6841 ..
6842 } => preview_requires_modifier,
6843 }
6844 }
6845
6846 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6847 if self.edit_prediction_provider.is_none() {
6848 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6849 } else {
6850 let selection = self.selections.newest_anchor();
6851 let cursor = selection.head();
6852
6853 if let Some((buffer, cursor_buffer_position)) =
6854 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6855 {
6856 self.edit_prediction_settings =
6857 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6858 }
6859 }
6860 }
6861
6862 fn edit_prediction_settings_at_position(
6863 &self,
6864 buffer: &Entity<Buffer>,
6865 buffer_position: language::Anchor,
6866 cx: &App,
6867 ) -> EditPredictionSettings {
6868 if !self.mode.is_full()
6869 || !self.show_inline_completions_override.unwrap_or(true)
6870 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6871 {
6872 return EditPredictionSettings::Disabled;
6873 }
6874
6875 let buffer = buffer.read(cx);
6876
6877 let file = buffer.file();
6878
6879 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6880 return EditPredictionSettings::Disabled;
6881 };
6882
6883 let by_provider = matches!(
6884 self.menu_inline_completions_policy,
6885 MenuInlineCompletionsPolicy::ByProvider
6886 );
6887
6888 let show_in_menu = by_provider
6889 && self
6890 .edit_prediction_provider
6891 .as_ref()
6892 .map_or(false, |provider| {
6893 provider.provider.show_completions_in_menu()
6894 });
6895
6896 let preview_requires_modifier =
6897 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6898
6899 EditPredictionSettings::Enabled {
6900 show_in_menu,
6901 preview_requires_modifier,
6902 }
6903 }
6904
6905 fn should_show_edit_predictions(&self) -> bool {
6906 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6907 }
6908
6909 pub fn edit_prediction_preview_is_active(&self) -> bool {
6910 matches!(
6911 self.edit_prediction_preview,
6912 EditPredictionPreview::Active { .. }
6913 )
6914 }
6915
6916 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6917 let cursor = self.selections.newest_anchor().head();
6918 if let Some((buffer, cursor_position)) =
6919 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6920 {
6921 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6922 } else {
6923 false
6924 }
6925 }
6926
6927 pub fn supports_minimap(&self, cx: &App) -> bool {
6928 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6929 }
6930
6931 fn edit_predictions_enabled_in_buffer(
6932 &self,
6933 buffer: &Entity<Buffer>,
6934 buffer_position: language::Anchor,
6935 cx: &App,
6936 ) -> bool {
6937 maybe!({
6938 if self.read_only(cx) {
6939 return Some(false);
6940 }
6941 let provider = self.edit_prediction_provider()?;
6942 if !provider.is_enabled(&buffer, buffer_position, cx) {
6943 return Some(false);
6944 }
6945 let buffer = buffer.read(cx);
6946 let Some(file) = buffer.file() else {
6947 return Some(true);
6948 };
6949 let settings = all_language_settings(Some(file), cx);
6950 Some(settings.edit_predictions_enabled_for_file(file, cx))
6951 })
6952 .unwrap_or(false)
6953 }
6954
6955 fn cycle_inline_completion(
6956 &mut self,
6957 direction: Direction,
6958 window: &mut Window,
6959 cx: &mut Context<Self>,
6960 ) -> Option<()> {
6961 let provider = self.edit_prediction_provider()?;
6962 let cursor = self.selections.newest_anchor().head();
6963 let (buffer, cursor_buffer_position) =
6964 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6965 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6966 return None;
6967 }
6968
6969 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6970 self.update_visible_inline_completion(window, cx);
6971
6972 Some(())
6973 }
6974
6975 pub fn show_inline_completion(
6976 &mut self,
6977 _: &ShowEditPrediction,
6978 window: &mut Window,
6979 cx: &mut Context<Self>,
6980 ) {
6981 if !self.has_active_inline_completion() {
6982 self.refresh_inline_completion(false, true, window, cx);
6983 return;
6984 }
6985
6986 self.update_visible_inline_completion(window, cx);
6987 }
6988
6989 pub fn display_cursor_names(
6990 &mut self,
6991 _: &DisplayCursorNames,
6992 window: &mut Window,
6993 cx: &mut Context<Self>,
6994 ) {
6995 self.show_cursor_names(window, cx);
6996 }
6997
6998 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6999 self.show_cursor_names = true;
7000 cx.notify();
7001 cx.spawn_in(window, async move |this, cx| {
7002 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7003 this.update(cx, |this, cx| {
7004 this.show_cursor_names = false;
7005 cx.notify()
7006 })
7007 .ok()
7008 })
7009 .detach();
7010 }
7011
7012 pub fn next_edit_prediction(
7013 &mut self,
7014 _: &NextEditPrediction,
7015 window: &mut Window,
7016 cx: &mut Context<Self>,
7017 ) {
7018 if self.has_active_inline_completion() {
7019 self.cycle_inline_completion(Direction::Next, window, cx);
7020 } else {
7021 let is_copilot_disabled = self
7022 .refresh_inline_completion(false, true, window, cx)
7023 .is_none();
7024 if is_copilot_disabled {
7025 cx.propagate();
7026 }
7027 }
7028 }
7029
7030 pub fn previous_edit_prediction(
7031 &mut self,
7032 _: &PreviousEditPrediction,
7033 window: &mut Window,
7034 cx: &mut Context<Self>,
7035 ) {
7036 if self.has_active_inline_completion() {
7037 self.cycle_inline_completion(Direction::Prev, window, cx);
7038 } else {
7039 let is_copilot_disabled = self
7040 .refresh_inline_completion(false, true, window, cx)
7041 .is_none();
7042 if is_copilot_disabled {
7043 cx.propagate();
7044 }
7045 }
7046 }
7047
7048 pub fn accept_edit_prediction(
7049 &mut self,
7050 _: &AcceptEditPrediction,
7051 window: &mut Window,
7052 cx: &mut Context<Self>,
7053 ) {
7054 if self.show_edit_predictions_in_menu() {
7055 self.hide_context_menu(window, cx);
7056 }
7057
7058 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7059 return;
7060 };
7061
7062 self.report_inline_completion_event(
7063 active_inline_completion.completion_id.clone(),
7064 true,
7065 cx,
7066 );
7067
7068 match &active_inline_completion.completion {
7069 InlineCompletion::Move { target, .. } => {
7070 let target = *target;
7071
7072 if let Some(position_map) = &self.last_position_map {
7073 if position_map
7074 .visible_row_range
7075 .contains(&target.to_display_point(&position_map.snapshot).row())
7076 || !self.edit_prediction_requires_modifier()
7077 {
7078 self.unfold_ranges(&[target..target], true, false, cx);
7079 // Note that this is also done in vim's handler of the Tab action.
7080 self.change_selections(
7081 Some(Autoscroll::newest()),
7082 window,
7083 cx,
7084 |selections| {
7085 selections.select_anchor_ranges([target..target]);
7086 },
7087 );
7088 self.clear_row_highlights::<EditPredictionPreview>();
7089
7090 self.edit_prediction_preview
7091 .set_previous_scroll_position(None);
7092 } else {
7093 self.edit_prediction_preview
7094 .set_previous_scroll_position(Some(
7095 position_map.snapshot.scroll_anchor,
7096 ));
7097
7098 self.highlight_rows::<EditPredictionPreview>(
7099 target..target,
7100 cx.theme().colors().editor_highlighted_line_background,
7101 RowHighlightOptions {
7102 autoscroll: true,
7103 ..Default::default()
7104 },
7105 cx,
7106 );
7107 self.request_autoscroll(Autoscroll::fit(), cx);
7108 }
7109 }
7110 }
7111 InlineCompletion::Edit { edits, .. } => {
7112 if let Some(provider) = self.edit_prediction_provider() {
7113 provider.accept(cx);
7114 }
7115
7116 // Store the transaction ID and selections before applying the edit
7117 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7118
7119 let snapshot = self.buffer.read(cx).snapshot(cx);
7120 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7121
7122 self.buffer.update(cx, |buffer, cx| {
7123 buffer.edit(edits.iter().cloned(), None, cx)
7124 });
7125
7126 self.change_selections(None, window, cx, |s| {
7127 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7128 });
7129
7130 let selections = self.selections.disjoint_anchors();
7131 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7132 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7133 if has_new_transaction {
7134 self.selection_history
7135 .insert_transaction(transaction_id_now, selections);
7136 }
7137 }
7138
7139 self.update_visible_inline_completion(window, cx);
7140 if self.active_inline_completion.is_none() {
7141 self.refresh_inline_completion(true, true, window, cx);
7142 }
7143
7144 cx.notify();
7145 }
7146 }
7147
7148 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7149 }
7150
7151 pub fn accept_partial_inline_completion(
7152 &mut self,
7153 _: &AcceptPartialEditPrediction,
7154 window: &mut Window,
7155 cx: &mut Context<Self>,
7156 ) {
7157 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7158 return;
7159 };
7160 if self.selections.count() != 1 {
7161 return;
7162 }
7163
7164 self.report_inline_completion_event(
7165 active_inline_completion.completion_id.clone(),
7166 true,
7167 cx,
7168 );
7169
7170 match &active_inline_completion.completion {
7171 InlineCompletion::Move { target, .. } => {
7172 let target = *target;
7173 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
7174 selections.select_anchor_ranges([target..target]);
7175 });
7176 }
7177 InlineCompletion::Edit { edits, .. } => {
7178 // Find an insertion that starts at the cursor position.
7179 let snapshot = self.buffer.read(cx).snapshot(cx);
7180 let cursor_offset = self.selections.newest::<usize>(cx).head();
7181 let insertion = edits.iter().find_map(|(range, text)| {
7182 let range = range.to_offset(&snapshot);
7183 if range.is_empty() && range.start == cursor_offset {
7184 Some(text)
7185 } else {
7186 None
7187 }
7188 });
7189
7190 if let Some(text) = insertion {
7191 let mut partial_completion = text
7192 .chars()
7193 .by_ref()
7194 .take_while(|c| c.is_alphabetic())
7195 .collect::<String>();
7196 if partial_completion.is_empty() {
7197 partial_completion = text
7198 .chars()
7199 .by_ref()
7200 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7201 .collect::<String>();
7202 }
7203
7204 cx.emit(EditorEvent::InputHandled {
7205 utf16_range_to_replace: None,
7206 text: partial_completion.clone().into(),
7207 });
7208
7209 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7210
7211 self.refresh_inline_completion(true, true, window, cx);
7212 cx.notify();
7213 } else {
7214 self.accept_edit_prediction(&Default::default(), window, cx);
7215 }
7216 }
7217 }
7218 }
7219
7220 fn discard_inline_completion(
7221 &mut self,
7222 should_report_inline_completion_event: bool,
7223 cx: &mut Context<Self>,
7224 ) -> bool {
7225 if should_report_inline_completion_event {
7226 let completion_id = self
7227 .active_inline_completion
7228 .as_ref()
7229 .and_then(|active_completion| active_completion.completion_id.clone());
7230
7231 self.report_inline_completion_event(completion_id, false, cx);
7232 }
7233
7234 if let Some(provider) = self.edit_prediction_provider() {
7235 provider.discard(cx);
7236 }
7237
7238 self.take_active_inline_completion(cx)
7239 }
7240
7241 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7242 let Some(provider) = self.edit_prediction_provider() else {
7243 return;
7244 };
7245
7246 let Some((_, buffer, _)) = self
7247 .buffer
7248 .read(cx)
7249 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7250 else {
7251 return;
7252 };
7253
7254 let extension = buffer
7255 .read(cx)
7256 .file()
7257 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7258
7259 let event_type = match accepted {
7260 true => "Edit Prediction Accepted",
7261 false => "Edit Prediction Discarded",
7262 };
7263 telemetry::event!(
7264 event_type,
7265 provider = provider.name(),
7266 prediction_id = id,
7267 suggestion_accepted = accepted,
7268 file_extension = extension,
7269 );
7270 }
7271
7272 pub fn has_active_inline_completion(&self) -> bool {
7273 self.active_inline_completion.is_some()
7274 }
7275
7276 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7277 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7278 return false;
7279 };
7280
7281 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7282 self.clear_highlights::<InlineCompletionHighlight>(cx);
7283 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7284 true
7285 }
7286
7287 /// Returns true when we're displaying the edit prediction popover below the cursor
7288 /// like we are not previewing and the LSP autocomplete menu is visible
7289 /// or we are in `when_holding_modifier` mode.
7290 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7291 if self.edit_prediction_preview_is_active()
7292 || !self.show_edit_predictions_in_menu()
7293 || !self.edit_predictions_enabled()
7294 {
7295 return false;
7296 }
7297
7298 if self.has_visible_completions_menu() {
7299 return true;
7300 }
7301
7302 has_completion && self.edit_prediction_requires_modifier()
7303 }
7304
7305 fn handle_modifiers_changed(
7306 &mut self,
7307 modifiers: Modifiers,
7308 position_map: &PositionMap,
7309 window: &mut Window,
7310 cx: &mut Context<Self>,
7311 ) {
7312 if self.show_edit_predictions_in_menu() {
7313 self.update_edit_prediction_preview(&modifiers, window, cx);
7314 }
7315
7316 self.update_selection_mode(&modifiers, position_map, window, cx);
7317
7318 let mouse_position = window.mouse_position();
7319 if !position_map.text_hitbox.is_hovered(window) {
7320 return;
7321 }
7322
7323 self.update_hovered_link(
7324 position_map.point_for_position(mouse_position),
7325 &position_map.snapshot,
7326 modifiers,
7327 window,
7328 cx,
7329 )
7330 }
7331
7332 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7333 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7334 if invert {
7335 match multi_cursor_setting {
7336 MultiCursorModifier::Alt => modifiers.alt,
7337 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7338 }
7339 } else {
7340 match multi_cursor_setting {
7341 MultiCursorModifier::Alt => modifiers.secondary(),
7342 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7343 }
7344 }
7345 }
7346
7347 fn columnar_selection_mode(
7348 modifiers: &Modifiers,
7349 cx: &mut Context<Self>,
7350 ) -> Option<ColumnarMode> {
7351 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7352 if Self::multi_cursor_modifier(false, modifiers, cx) {
7353 Some(ColumnarMode::FromMouse)
7354 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7355 Some(ColumnarMode::FromSelection)
7356 } else {
7357 None
7358 }
7359 } else {
7360 None
7361 }
7362 }
7363
7364 fn update_selection_mode(
7365 &mut self,
7366 modifiers: &Modifiers,
7367 position_map: &PositionMap,
7368 window: &mut Window,
7369 cx: &mut Context<Self>,
7370 ) {
7371 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7372 return;
7373 };
7374 if self.selections.pending.is_none() {
7375 return;
7376 }
7377
7378 let mouse_position = window.mouse_position();
7379 let point_for_position = position_map.point_for_position(mouse_position);
7380 let position = point_for_position.previous_valid;
7381
7382 self.select(
7383 SelectPhase::BeginColumnar {
7384 position,
7385 reset: false,
7386 mode,
7387 goal_column: point_for_position.exact_unclipped.column(),
7388 },
7389 window,
7390 cx,
7391 );
7392 }
7393
7394 fn update_edit_prediction_preview(
7395 &mut self,
7396 modifiers: &Modifiers,
7397 window: &mut Window,
7398 cx: &mut Context<Self>,
7399 ) {
7400 let mut modifiers_held = false;
7401 if let Some(accept_keystroke) = self
7402 .accept_edit_prediction_keybind(false, window, cx)
7403 .keystroke()
7404 {
7405 modifiers_held = modifiers_held
7406 || (&accept_keystroke.modifiers == modifiers
7407 && accept_keystroke.modifiers.modified());
7408 };
7409 if let Some(accept_partial_keystroke) = self
7410 .accept_edit_prediction_keybind(true, window, cx)
7411 .keystroke()
7412 {
7413 modifiers_held = modifiers_held
7414 || (&accept_partial_keystroke.modifiers == modifiers
7415 && accept_partial_keystroke.modifiers.modified());
7416 }
7417
7418 if modifiers_held {
7419 if matches!(
7420 self.edit_prediction_preview,
7421 EditPredictionPreview::Inactive { .. }
7422 ) {
7423 self.edit_prediction_preview = EditPredictionPreview::Active {
7424 previous_scroll_position: None,
7425 since: Instant::now(),
7426 };
7427
7428 self.update_visible_inline_completion(window, cx);
7429 cx.notify();
7430 }
7431 } else if let EditPredictionPreview::Active {
7432 previous_scroll_position,
7433 since,
7434 } = self.edit_prediction_preview
7435 {
7436 if let (Some(previous_scroll_position), Some(position_map)) =
7437 (previous_scroll_position, self.last_position_map.as_ref())
7438 {
7439 self.set_scroll_position(
7440 previous_scroll_position
7441 .scroll_position(&position_map.snapshot.display_snapshot),
7442 window,
7443 cx,
7444 );
7445 }
7446
7447 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7448 released_too_fast: since.elapsed() < Duration::from_millis(200),
7449 };
7450 self.clear_row_highlights::<EditPredictionPreview>();
7451 self.update_visible_inline_completion(window, cx);
7452 cx.notify();
7453 }
7454 }
7455
7456 fn update_visible_inline_completion(
7457 &mut self,
7458 _window: &mut Window,
7459 cx: &mut Context<Self>,
7460 ) -> Option<()> {
7461 let selection = self.selections.newest_anchor();
7462 let cursor = selection.head();
7463 let multibuffer = self.buffer.read(cx).snapshot(cx);
7464 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7465 let excerpt_id = cursor.excerpt_id;
7466
7467 let show_in_menu = self.show_edit_predictions_in_menu();
7468 let completions_menu_has_precedence = !show_in_menu
7469 && (self.context_menu.borrow().is_some()
7470 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7471
7472 if completions_menu_has_precedence
7473 || !offset_selection.is_empty()
7474 || self
7475 .active_inline_completion
7476 .as_ref()
7477 .map_or(false, |completion| {
7478 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7479 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7480 !invalidation_range.contains(&offset_selection.head())
7481 })
7482 {
7483 self.discard_inline_completion(false, cx);
7484 return None;
7485 }
7486
7487 self.take_active_inline_completion(cx);
7488 let Some(provider) = self.edit_prediction_provider() else {
7489 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7490 return None;
7491 };
7492
7493 let (buffer, cursor_buffer_position) =
7494 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7495
7496 self.edit_prediction_settings =
7497 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7498
7499 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7500
7501 if self.edit_prediction_indent_conflict {
7502 let cursor_point = cursor.to_point(&multibuffer);
7503
7504 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7505
7506 if let Some((_, indent)) = indents.iter().next() {
7507 if indent.len == cursor_point.column {
7508 self.edit_prediction_indent_conflict = false;
7509 }
7510 }
7511 }
7512
7513 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7514 let edits = inline_completion
7515 .edits
7516 .into_iter()
7517 .flat_map(|(range, new_text)| {
7518 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7519 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7520 Some((start..end, new_text))
7521 })
7522 .collect::<Vec<_>>();
7523 if edits.is_empty() {
7524 return None;
7525 }
7526
7527 let first_edit_start = edits.first().unwrap().0.start;
7528 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7529 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7530
7531 let last_edit_end = edits.last().unwrap().0.end;
7532 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7533 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7534
7535 let cursor_row = cursor.to_point(&multibuffer).row;
7536
7537 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7538
7539 let mut inlay_ids = Vec::new();
7540 let invalidation_row_range;
7541 let move_invalidation_row_range = if cursor_row < edit_start_row {
7542 Some(cursor_row..edit_end_row)
7543 } else if cursor_row > edit_end_row {
7544 Some(edit_start_row..cursor_row)
7545 } else {
7546 None
7547 };
7548 let is_move =
7549 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7550 let completion = if is_move {
7551 invalidation_row_range =
7552 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7553 let target = first_edit_start;
7554 InlineCompletion::Move { target, snapshot }
7555 } else {
7556 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7557 && !self.inline_completions_hidden_for_vim_mode;
7558
7559 if show_completions_in_buffer {
7560 if edits
7561 .iter()
7562 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7563 {
7564 let mut inlays = Vec::new();
7565 for (range, new_text) in &edits {
7566 let inlay = Inlay::inline_completion(
7567 post_inc(&mut self.next_inlay_id),
7568 range.start,
7569 new_text.as_str(),
7570 );
7571 inlay_ids.push(inlay.id);
7572 inlays.push(inlay);
7573 }
7574
7575 self.splice_inlays(&[], inlays, cx);
7576 } else {
7577 let background_color = cx.theme().status().deleted_background;
7578 self.highlight_text::<InlineCompletionHighlight>(
7579 edits.iter().map(|(range, _)| range.clone()).collect(),
7580 HighlightStyle {
7581 background_color: Some(background_color),
7582 ..Default::default()
7583 },
7584 cx,
7585 );
7586 }
7587 }
7588
7589 invalidation_row_range = edit_start_row..edit_end_row;
7590
7591 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7592 if provider.show_tab_accept_marker() {
7593 EditDisplayMode::TabAccept
7594 } else {
7595 EditDisplayMode::Inline
7596 }
7597 } else {
7598 EditDisplayMode::DiffPopover
7599 };
7600
7601 InlineCompletion::Edit {
7602 edits,
7603 edit_preview: inline_completion.edit_preview,
7604 display_mode,
7605 snapshot,
7606 }
7607 };
7608
7609 let invalidation_range = multibuffer
7610 .anchor_before(Point::new(invalidation_row_range.start, 0))
7611 ..multibuffer.anchor_after(Point::new(
7612 invalidation_row_range.end,
7613 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7614 ));
7615
7616 self.stale_inline_completion_in_menu = None;
7617 self.active_inline_completion = Some(InlineCompletionState {
7618 inlay_ids,
7619 completion,
7620 completion_id: inline_completion.id,
7621 invalidation_range,
7622 });
7623
7624 cx.notify();
7625
7626 Some(())
7627 }
7628
7629 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7630 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7631 }
7632
7633 fn clear_tasks(&mut self) {
7634 self.tasks.clear()
7635 }
7636
7637 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7638 if self.tasks.insert(key, value).is_some() {
7639 // This case should hopefully be rare, but just in case...
7640 log::error!(
7641 "multiple different run targets found on a single line, only the last target will be rendered"
7642 )
7643 }
7644 }
7645
7646 /// Get all display points of breakpoints that will be rendered within editor
7647 ///
7648 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7649 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7650 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7651 fn active_breakpoints(
7652 &self,
7653 range: Range<DisplayRow>,
7654 window: &mut Window,
7655 cx: &mut Context<Self>,
7656 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7657 let mut breakpoint_display_points = HashMap::default();
7658
7659 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7660 return breakpoint_display_points;
7661 };
7662
7663 let snapshot = self.snapshot(window, cx);
7664
7665 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7666 let Some(project) = self.project.as_ref() else {
7667 return breakpoint_display_points;
7668 };
7669
7670 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7671 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7672
7673 for (buffer_snapshot, range, excerpt_id) in
7674 multi_buffer_snapshot.range_to_buffer_ranges(range)
7675 {
7676 let Some(buffer) = project
7677 .read(cx)
7678 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7679 else {
7680 continue;
7681 };
7682 let breakpoints = breakpoint_store.read(cx).breakpoints(
7683 &buffer,
7684 Some(
7685 buffer_snapshot.anchor_before(range.start)
7686 ..buffer_snapshot.anchor_after(range.end),
7687 ),
7688 buffer_snapshot,
7689 cx,
7690 );
7691 for (breakpoint, state) in breakpoints {
7692 let multi_buffer_anchor =
7693 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7694 let position = multi_buffer_anchor
7695 .to_point(&multi_buffer_snapshot)
7696 .to_display_point(&snapshot);
7697
7698 breakpoint_display_points.insert(
7699 position.row(),
7700 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7701 );
7702 }
7703 }
7704
7705 breakpoint_display_points
7706 }
7707
7708 fn breakpoint_context_menu(
7709 &self,
7710 anchor: Anchor,
7711 window: &mut Window,
7712 cx: &mut Context<Self>,
7713 ) -> Entity<ui::ContextMenu> {
7714 let weak_editor = cx.weak_entity();
7715 let focus_handle = self.focus_handle(cx);
7716
7717 let row = self
7718 .buffer
7719 .read(cx)
7720 .snapshot(cx)
7721 .summary_for_anchor::<Point>(&anchor)
7722 .row;
7723
7724 let breakpoint = self
7725 .breakpoint_at_row(row, window, cx)
7726 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7727
7728 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7729 "Edit Log Breakpoint"
7730 } else {
7731 "Set Log Breakpoint"
7732 };
7733
7734 let condition_breakpoint_msg = if breakpoint
7735 .as_ref()
7736 .is_some_and(|bp| bp.1.condition.is_some())
7737 {
7738 "Edit Condition Breakpoint"
7739 } else {
7740 "Set Condition Breakpoint"
7741 };
7742
7743 let hit_condition_breakpoint_msg = if breakpoint
7744 .as_ref()
7745 .is_some_and(|bp| bp.1.hit_condition.is_some())
7746 {
7747 "Edit Hit Condition Breakpoint"
7748 } else {
7749 "Set Hit Condition Breakpoint"
7750 };
7751
7752 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7753 "Unset Breakpoint"
7754 } else {
7755 "Set Breakpoint"
7756 };
7757
7758 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7759
7760 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7761 BreakpointState::Enabled => Some("Disable"),
7762 BreakpointState::Disabled => Some("Enable"),
7763 });
7764
7765 let (anchor, breakpoint) =
7766 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7767
7768 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7769 menu.on_blur_subscription(Subscription::new(|| {}))
7770 .context(focus_handle)
7771 .when(run_to_cursor, |this| {
7772 let weak_editor = weak_editor.clone();
7773 this.entry("Run to cursor", None, move |window, cx| {
7774 weak_editor
7775 .update(cx, |editor, cx| {
7776 editor.change_selections(None, window, cx, |s| {
7777 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7778 });
7779 })
7780 .ok();
7781
7782 window.dispatch_action(Box::new(RunToCursor), cx);
7783 })
7784 .separator()
7785 })
7786 .when_some(toggle_state_msg, |this, msg| {
7787 this.entry(msg, None, {
7788 let weak_editor = weak_editor.clone();
7789 let breakpoint = breakpoint.clone();
7790 move |_window, cx| {
7791 weak_editor
7792 .update(cx, |this, cx| {
7793 this.edit_breakpoint_at_anchor(
7794 anchor,
7795 breakpoint.as_ref().clone(),
7796 BreakpointEditAction::InvertState,
7797 cx,
7798 );
7799 })
7800 .log_err();
7801 }
7802 })
7803 })
7804 .entry(set_breakpoint_msg, None, {
7805 let weak_editor = weak_editor.clone();
7806 let breakpoint = breakpoint.clone();
7807 move |_window, cx| {
7808 weak_editor
7809 .update(cx, |this, cx| {
7810 this.edit_breakpoint_at_anchor(
7811 anchor,
7812 breakpoint.as_ref().clone(),
7813 BreakpointEditAction::Toggle,
7814 cx,
7815 );
7816 })
7817 .log_err();
7818 }
7819 })
7820 .entry(log_breakpoint_msg, None, {
7821 let breakpoint = breakpoint.clone();
7822 let weak_editor = weak_editor.clone();
7823 move |window, cx| {
7824 weak_editor
7825 .update(cx, |this, cx| {
7826 this.add_edit_breakpoint_block(
7827 anchor,
7828 breakpoint.as_ref(),
7829 BreakpointPromptEditAction::Log,
7830 window,
7831 cx,
7832 );
7833 })
7834 .log_err();
7835 }
7836 })
7837 .entry(condition_breakpoint_msg, None, {
7838 let breakpoint = breakpoint.clone();
7839 let weak_editor = weak_editor.clone();
7840 move |window, cx| {
7841 weak_editor
7842 .update(cx, |this, cx| {
7843 this.add_edit_breakpoint_block(
7844 anchor,
7845 breakpoint.as_ref(),
7846 BreakpointPromptEditAction::Condition,
7847 window,
7848 cx,
7849 );
7850 })
7851 .log_err();
7852 }
7853 })
7854 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7855 weak_editor
7856 .update(cx, |this, cx| {
7857 this.add_edit_breakpoint_block(
7858 anchor,
7859 breakpoint.as_ref(),
7860 BreakpointPromptEditAction::HitCondition,
7861 window,
7862 cx,
7863 );
7864 })
7865 .log_err();
7866 })
7867 })
7868 }
7869
7870 fn render_breakpoint(
7871 &self,
7872 position: Anchor,
7873 row: DisplayRow,
7874 breakpoint: &Breakpoint,
7875 state: Option<BreakpointSessionState>,
7876 cx: &mut Context<Self>,
7877 ) -> IconButton {
7878 let is_rejected = state.is_some_and(|s| !s.verified);
7879 // Is it a breakpoint that shows up when hovering over gutter?
7880 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7881 (false, false),
7882 |PhantomBreakpointIndicator {
7883 is_active,
7884 display_row,
7885 collides_with_existing_breakpoint,
7886 }| {
7887 (
7888 is_active && display_row == row,
7889 collides_with_existing_breakpoint,
7890 )
7891 },
7892 );
7893
7894 let (color, icon) = {
7895 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7896 (false, false) => ui::IconName::DebugBreakpoint,
7897 (true, false) => ui::IconName::DebugLogBreakpoint,
7898 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7899 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7900 };
7901
7902 let color = if is_phantom {
7903 Color::Hint
7904 } else if is_rejected {
7905 Color::Disabled
7906 } else {
7907 Color::Debugger
7908 };
7909
7910 (color, icon)
7911 };
7912
7913 let breakpoint = Arc::from(breakpoint.clone());
7914
7915 let alt_as_text = gpui::Keystroke {
7916 modifiers: Modifiers::secondary_key(),
7917 ..Default::default()
7918 };
7919 let primary_action_text = if breakpoint.is_disabled() {
7920 "Enable breakpoint"
7921 } else if is_phantom && !collides_with_existing {
7922 "Set breakpoint"
7923 } else {
7924 "Unset breakpoint"
7925 };
7926 let focus_handle = self.focus_handle.clone();
7927
7928 let meta = if is_rejected {
7929 SharedString::from("No executable code is associated with this line.")
7930 } else if collides_with_existing && !breakpoint.is_disabled() {
7931 SharedString::from(format!(
7932 "{alt_as_text}-click to disable,\nright-click for more options."
7933 ))
7934 } else {
7935 SharedString::from("Right-click for more options.")
7936 };
7937 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7938 .icon_size(IconSize::XSmall)
7939 .size(ui::ButtonSize::None)
7940 .when(is_rejected, |this| {
7941 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7942 })
7943 .icon_color(color)
7944 .style(ButtonStyle::Transparent)
7945 .on_click(cx.listener({
7946 let breakpoint = breakpoint.clone();
7947
7948 move |editor, event: &ClickEvent, window, cx| {
7949 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7950 BreakpointEditAction::InvertState
7951 } else {
7952 BreakpointEditAction::Toggle
7953 };
7954
7955 window.focus(&editor.focus_handle(cx));
7956 editor.edit_breakpoint_at_anchor(
7957 position,
7958 breakpoint.as_ref().clone(),
7959 edit_action,
7960 cx,
7961 );
7962 }
7963 }))
7964 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7965 editor.set_breakpoint_context_menu(
7966 row,
7967 Some(position),
7968 event.down.position,
7969 window,
7970 cx,
7971 );
7972 }))
7973 .tooltip(move |window, cx| {
7974 Tooltip::with_meta_in(
7975 primary_action_text,
7976 Some(&ToggleBreakpoint),
7977 meta.clone(),
7978 &focus_handle,
7979 window,
7980 cx,
7981 )
7982 })
7983 }
7984
7985 fn build_tasks_context(
7986 project: &Entity<Project>,
7987 buffer: &Entity<Buffer>,
7988 buffer_row: u32,
7989 tasks: &Arc<RunnableTasks>,
7990 cx: &mut Context<Self>,
7991 ) -> Task<Option<task::TaskContext>> {
7992 let position = Point::new(buffer_row, tasks.column);
7993 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7994 let location = Location {
7995 buffer: buffer.clone(),
7996 range: range_start..range_start,
7997 };
7998 // Fill in the environmental variables from the tree-sitter captures
7999 let mut captured_task_variables = TaskVariables::default();
8000 for (capture_name, value) in tasks.extra_variables.clone() {
8001 captured_task_variables.insert(
8002 task::VariableName::Custom(capture_name.into()),
8003 value.clone(),
8004 );
8005 }
8006 project.update(cx, |project, cx| {
8007 project.task_store().update(cx, |task_store, cx| {
8008 task_store.task_context_for_location(captured_task_variables, location, cx)
8009 })
8010 })
8011 }
8012
8013 pub fn spawn_nearest_task(
8014 &mut self,
8015 action: &SpawnNearestTask,
8016 window: &mut Window,
8017 cx: &mut Context<Self>,
8018 ) {
8019 let Some((workspace, _)) = self.workspace.clone() else {
8020 return;
8021 };
8022 let Some(project) = self.project.clone() else {
8023 return;
8024 };
8025
8026 // Try to find a closest, enclosing node using tree-sitter that has a
8027 // task
8028 let Some((buffer, buffer_row, tasks)) = self
8029 .find_enclosing_node_task(cx)
8030 // Or find the task that's closest in row-distance.
8031 .or_else(|| self.find_closest_task(cx))
8032 else {
8033 return;
8034 };
8035
8036 let reveal_strategy = action.reveal;
8037 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8038 cx.spawn_in(window, async move |_, cx| {
8039 let context = task_context.await?;
8040 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8041
8042 let resolved = &mut resolved_task.resolved;
8043 resolved.reveal = reveal_strategy;
8044
8045 workspace
8046 .update_in(cx, |workspace, window, cx| {
8047 workspace.schedule_resolved_task(
8048 task_source_kind,
8049 resolved_task,
8050 false,
8051 window,
8052 cx,
8053 );
8054 })
8055 .ok()
8056 })
8057 .detach();
8058 }
8059
8060 fn find_closest_task(
8061 &mut self,
8062 cx: &mut Context<Self>,
8063 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8064 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8065
8066 let ((buffer_id, row), tasks) = self
8067 .tasks
8068 .iter()
8069 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8070
8071 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8072 let tasks = Arc::new(tasks.to_owned());
8073 Some((buffer, *row, tasks))
8074 }
8075
8076 fn find_enclosing_node_task(
8077 &mut self,
8078 cx: &mut Context<Self>,
8079 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8080 let snapshot = self.buffer.read(cx).snapshot(cx);
8081 let offset = self.selections.newest::<usize>(cx).head();
8082 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8083 let buffer_id = excerpt.buffer().remote_id();
8084
8085 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8086 let mut cursor = layer.node().walk();
8087
8088 while cursor.goto_first_child_for_byte(offset).is_some() {
8089 if cursor.node().end_byte() == offset {
8090 cursor.goto_next_sibling();
8091 }
8092 }
8093
8094 // Ascend to the smallest ancestor that contains the range and has a task.
8095 loop {
8096 let node = cursor.node();
8097 let node_range = node.byte_range();
8098 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8099
8100 // Check if this node contains our offset
8101 if node_range.start <= offset && node_range.end >= offset {
8102 // If it contains offset, check for task
8103 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8104 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8105 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8106 }
8107 }
8108
8109 if !cursor.goto_parent() {
8110 break;
8111 }
8112 }
8113 None
8114 }
8115
8116 fn render_run_indicator(
8117 &self,
8118 _style: &EditorStyle,
8119 is_active: bool,
8120 row: DisplayRow,
8121 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8122 cx: &mut Context<Self>,
8123 ) -> IconButton {
8124 let color = Color::Muted;
8125 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8126
8127 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
8128 .shape(ui::IconButtonShape::Square)
8129 .icon_size(IconSize::XSmall)
8130 .icon_color(color)
8131 .toggle_state(is_active)
8132 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8133 let quick_launch = e.down.button == MouseButton::Left;
8134 window.focus(&editor.focus_handle(cx));
8135 editor.toggle_code_actions(
8136 &ToggleCodeActions {
8137 deployed_from: Some(CodeActionSource::RunMenu(row)),
8138 quick_launch,
8139 },
8140 window,
8141 cx,
8142 );
8143 }))
8144 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8145 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
8146 }))
8147 }
8148
8149 pub fn context_menu_visible(&self) -> bool {
8150 !self.edit_prediction_preview_is_active()
8151 && self
8152 .context_menu
8153 .borrow()
8154 .as_ref()
8155 .map_or(false, |menu| menu.visible())
8156 }
8157
8158 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8159 self.context_menu
8160 .borrow()
8161 .as_ref()
8162 .map(|menu| menu.origin())
8163 }
8164
8165 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8166 self.context_menu_options = Some(options);
8167 }
8168
8169 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8170 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8171
8172 fn render_edit_prediction_popover(
8173 &mut self,
8174 text_bounds: &Bounds<Pixels>,
8175 content_origin: gpui::Point<Pixels>,
8176 right_margin: Pixels,
8177 editor_snapshot: &EditorSnapshot,
8178 visible_row_range: Range<DisplayRow>,
8179 scroll_top: f32,
8180 scroll_bottom: f32,
8181 line_layouts: &[LineWithInvisibles],
8182 line_height: Pixels,
8183 scroll_pixel_position: gpui::Point<Pixels>,
8184 newest_selection_head: Option<DisplayPoint>,
8185 editor_width: Pixels,
8186 style: &EditorStyle,
8187 window: &mut Window,
8188 cx: &mut App,
8189 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8190 if self.mode().is_minimap() {
8191 return None;
8192 }
8193 let active_inline_completion = self.active_inline_completion.as_ref()?;
8194
8195 if self.edit_prediction_visible_in_cursor_popover(true) {
8196 return None;
8197 }
8198
8199 match &active_inline_completion.completion {
8200 InlineCompletion::Move { target, .. } => {
8201 let target_display_point = target.to_display_point(editor_snapshot);
8202
8203 if self.edit_prediction_requires_modifier() {
8204 if !self.edit_prediction_preview_is_active() {
8205 return None;
8206 }
8207
8208 self.render_edit_prediction_modifier_jump_popover(
8209 text_bounds,
8210 content_origin,
8211 visible_row_range,
8212 line_layouts,
8213 line_height,
8214 scroll_pixel_position,
8215 newest_selection_head,
8216 target_display_point,
8217 window,
8218 cx,
8219 )
8220 } else {
8221 self.render_edit_prediction_eager_jump_popover(
8222 text_bounds,
8223 content_origin,
8224 editor_snapshot,
8225 visible_row_range,
8226 scroll_top,
8227 scroll_bottom,
8228 line_height,
8229 scroll_pixel_position,
8230 target_display_point,
8231 editor_width,
8232 window,
8233 cx,
8234 )
8235 }
8236 }
8237 InlineCompletion::Edit {
8238 display_mode: EditDisplayMode::Inline,
8239 ..
8240 } => None,
8241 InlineCompletion::Edit {
8242 display_mode: EditDisplayMode::TabAccept,
8243 edits,
8244 ..
8245 } => {
8246 let range = &edits.first()?.0;
8247 let target_display_point = range.end.to_display_point(editor_snapshot);
8248
8249 self.render_edit_prediction_end_of_line_popover(
8250 "Accept",
8251 editor_snapshot,
8252 visible_row_range,
8253 target_display_point,
8254 line_height,
8255 scroll_pixel_position,
8256 content_origin,
8257 editor_width,
8258 window,
8259 cx,
8260 )
8261 }
8262 InlineCompletion::Edit {
8263 edits,
8264 edit_preview,
8265 display_mode: EditDisplayMode::DiffPopover,
8266 snapshot,
8267 } => self.render_edit_prediction_diff_popover(
8268 text_bounds,
8269 content_origin,
8270 right_margin,
8271 editor_snapshot,
8272 visible_row_range,
8273 line_layouts,
8274 line_height,
8275 scroll_pixel_position,
8276 newest_selection_head,
8277 editor_width,
8278 style,
8279 edits,
8280 edit_preview,
8281 snapshot,
8282 window,
8283 cx,
8284 ),
8285 }
8286 }
8287
8288 fn render_edit_prediction_modifier_jump_popover(
8289 &mut self,
8290 text_bounds: &Bounds<Pixels>,
8291 content_origin: gpui::Point<Pixels>,
8292 visible_row_range: Range<DisplayRow>,
8293 line_layouts: &[LineWithInvisibles],
8294 line_height: Pixels,
8295 scroll_pixel_position: gpui::Point<Pixels>,
8296 newest_selection_head: Option<DisplayPoint>,
8297 target_display_point: DisplayPoint,
8298 window: &mut Window,
8299 cx: &mut App,
8300 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8301 let scrolled_content_origin =
8302 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8303
8304 const SCROLL_PADDING_Y: Pixels = px(12.);
8305
8306 if target_display_point.row() < visible_row_range.start {
8307 return self.render_edit_prediction_scroll_popover(
8308 |_| SCROLL_PADDING_Y,
8309 IconName::ArrowUp,
8310 visible_row_range,
8311 line_layouts,
8312 newest_selection_head,
8313 scrolled_content_origin,
8314 window,
8315 cx,
8316 );
8317 } else if target_display_point.row() >= visible_row_range.end {
8318 return self.render_edit_prediction_scroll_popover(
8319 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8320 IconName::ArrowDown,
8321 visible_row_range,
8322 line_layouts,
8323 newest_selection_head,
8324 scrolled_content_origin,
8325 window,
8326 cx,
8327 );
8328 }
8329
8330 const POLE_WIDTH: Pixels = px(2.);
8331
8332 let line_layout =
8333 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8334 let target_column = target_display_point.column() as usize;
8335
8336 let target_x = line_layout.x_for_index(target_column);
8337 let target_y =
8338 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8339
8340 let flag_on_right = target_x < text_bounds.size.width / 2.;
8341
8342 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8343 border_color.l += 0.001;
8344
8345 let mut element = v_flex()
8346 .items_end()
8347 .when(flag_on_right, |el| el.items_start())
8348 .child(if flag_on_right {
8349 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8350 .rounded_bl(px(0.))
8351 .rounded_tl(px(0.))
8352 .border_l_2()
8353 .border_color(border_color)
8354 } else {
8355 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8356 .rounded_br(px(0.))
8357 .rounded_tr(px(0.))
8358 .border_r_2()
8359 .border_color(border_color)
8360 })
8361 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8362 .into_any();
8363
8364 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8365
8366 let mut origin = scrolled_content_origin + point(target_x, target_y)
8367 - point(
8368 if flag_on_right {
8369 POLE_WIDTH
8370 } else {
8371 size.width - POLE_WIDTH
8372 },
8373 size.height - line_height,
8374 );
8375
8376 origin.x = origin.x.max(content_origin.x);
8377
8378 element.prepaint_at(origin, window, cx);
8379
8380 Some((element, origin))
8381 }
8382
8383 fn render_edit_prediction_scroll_popover(
8384 &mut self,
8385 to_y: impl Fn(Size<Pixels>) -> Pixels,
8386 scroll_icon: IconName,
8387 visible_row_range: Range<DisplayRow>,
8388 line_layouts: &[LineWithInvisibles],
8389 newest_selection_head: Option<DisplayPoint>,
8390 scrolled_content_origin: gpui::Point<Pixels>,
8391 window: &mut Window,
8392 cx: &mut App,
8393 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8394 let mut element = self
8395 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8396 .into_any();
8397
8398 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8399
8400 let cursor = newest_selection_head?;
8401 let cursor_row_layout =
8402 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8403 let cursor_column = cursor.column() as usize;
8404
8405 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8406
8407 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8408
8409 element.prepaint_at(origin, window, cx);
8410 Some((element, origin))
8411 }
8412
8413 fn render_edit_prediction_eager_jump_popover(
8414 &mut self,
8415 text_bounds: &Bounds<Pixels>,
8416 content_origin: gpui::Point<Pixels>,
8417 editor_snapshot: &EditorSnapshot,
8418 visible_row_range: Range<DisplayRow>,
8419 scroll_top: f32,
8420 scroll_bottom: f32,
8421 line_height: Pixels,
8422 scroll_pixel_position: gpui::Point<Pixels>,
8423 target_display_point: DisplayPoint,
8424 editor_width: Pixels,
8425 window: &mut Window,
8426 cx: &mut App,
8427 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8428 if target_display_point.row().as_f32() < scroll_top {
8429 let mut element = self
8430 .render_edit_prediction_line_popover(
8431 "Jump to Edit",
8432 Some(IconName::ArrowUp),
8433 window,
8434 cx,
8435 )?
8436 .into_any();
8437
8438 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8439 let offset = point(
8440 (text_bounds.size.width - size.width) / 2.,
8441 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8442 );
8443
8444 let origin = text_bounds.origin + offset;
8445 element.prepaint_at(origin, window, cx);
8446 Some((element, origin))
8447 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8448 let mut element = self
8449 .render_edit_prediction_line_popover(
8450 "Jump to Edit",
8451 Some(IconName::ArrowDown),
8452 window,
8453 cx,
8454 )?
8455 .into_any();
8456
8457 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8458 let offset = point(
8459 (text_bounds.size.width - size.width) / 2.,
8460 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8461 );
8462
8463 let origin = text_bounds.origin + offset;
8464 element.prepaint_at(origin, window, cx);
8465 Some((element, origin))
8466 } else {
8467 self.render_edit_prediction_end_of_line_popover(
8468 "Jump to Edit",
8469 editor_snapshot,
8470 visible_row_range,
8471 target_display_point,
8472 line_height,
8473 scroll_pixel_position,
8474 content_origin,
8475 editor_width,
8476 window,
8477 cx,
8478 )
8479 }
8480 }
8481
8482 fn render_edit_prediction_end_of_line_popover(
8483 self: &mut Editor,
8484 label: &'static str,
8485 editor_snapshot: &EditorSnapshot,
8486 visible_row_range: Range<DisplayRow>,
8487 target_display_point: DisplayPoint,
8488 line_height: Pixels,
8489 scroll_pixel_position: gpui::Point<Pixels>,
8490 content_origin: gpui::Point<Pixels>,
8491 editor_width: Pixels,
8492 window: &mut Window,
8493 cx: &mut App,
8494 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8495 let target_line_end = DisplayPoint::new(
8496 target_display_point.row(),
8497 editor_snapshot.line_len(target_display_point.row()),
8498 );
8499
8500 let mut element = self
8501 .render_edit_prediction_line_popover(label, None, window, cx)?
8502 .into_any();
8503
8504 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8505
8506 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8507
8508 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8509 let mut origin = start_point
8510 + line_origin
8511 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8512 origin.x = origin.x.max(content_origin.x);
8513
8514 let max_x = content_origin.x + editor_width - size.width;
8515
8516 if origin.x > max_x {
8517 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8518
8519 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8520 origin.y += offset;
8521 IconName::ArrowUp
8522 } else {
8523 origin.y -= offset;
8524 IconName::ArrowDown
8525 };
8526
8527 element = self
8528 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8529 .into_any();
8530
8531 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8532
8533 origin.x = content_origin.x + editor_width - size.width - px(2.);
8534 }
8535
8536 element.prepaint_at(origin, window, cx);
8537 Some((element, origin))
8538 }
8539
8540 fn render_edit_prediction_diff_popover(
8541 self: &Editor,
8542 text_bounds: &Bounds<Pixels>,
8543 content_origin: gpui::Point<Pixels>,
8544 right_margin: Pixels,
8545 editor_snapshot: &EditorSnapshot,
8546 visible_row_range: Range<DisplayRow>,
8547 line_layouts: &[LineWithInvisibles],
8548 line_height: Pixels,
8549 scroll_pixel_position: gpui::Point<Pixels>,
8550 newest_selection_head: Option<DisplayPoint>,
8551 editor_width: Pixels,
8552 style: &EditorStyle,
8553 edits: &Vec<(Range<Anchor>, String)>,
8554 edit_preview: &Option<language::EditPreview>,
8555 snapshot: &language::BufferSnapshot,
8556 window: &mut Window,
8557 cx: &mut App,
8558 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8559 let edit_start = edits
8560 .first()
8561 .unwrap()
8562 .0
8563 .start
8564 .to_display_point(editor_snapshot);
8565 let edit_end = edits
8566 .last()
8567 .unwrap()
8568 .0
8569 .end
8570 .to_display_point(editor_snapshot);
8571
8572 let is_visible = visible_row_range.contains(&edit_start.row())
8573 || visible_row_range.contains(&edit_end.row());
8574 if !is_visible {
8575 return None;
8576 }
8577
8578 let highlighted_edits =
8579 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8580
8581 let styled_text = highlighted_edits.to_styled_text(&style.text);
8582 let line_count = highlighted_edits.text.lines().count();
8583
8584 const BORDER_WIDTH: Pixels = px(1.);
8585
8586 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8587 let has_keybind = keybind.is_some();
8588
8589 let mut element = h_flex()
8590 .items_start()
8591 .child(
8592 h_flex()
8593 .bg(cx.theme().colors().editor_background)
8594 .border(BORDER_WIDTH)
8595 .shadow_sm()
8596 .border_color(cx.theme().colors().border)
8597 .rounded_l_lg()
8598 .when(line_count > 1, |el| el.rounded_br_lg())
8599 .pr_1()
8600 .child(styled_text),
8601 )
8602 .child(
8603 h_flex()
8604 .h(line_height + BORDER_WIDTH * 2.)
8605 .px_1p5()
8606 .gap_1()
8607 // Workaround: For some reason, there's a gap if we don't do this
8608 .ml(-BORDER_WIDTH)
8609 .shadow(vec![gpui::BoxShadow {
8610 color: gpui::black().opacity(0.05),
8611 offset: point(px(1.), px(1.)),
8612 blur_radius: px(2.),
8613 spread_radius: px(0.),
8614 }])
8615 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8616 .border(BORDER_WIDTH)
8617 .border_color(cx.theme().colors().border)
8618 .rounded_r_lg()
8619 .id("edit_prediction_diff_popover_keybind")
8620 .when(!has_keybind, |el| {
8621 let status_colors = cx.theme().status();
8622
8623 el.bg(status_colors.error_background)
8624 .border_color(status_colors.error.opacity(0.6))
8625 .child(Icon::new(IconName::Info).color(Color::Error))
8626 .cursor_default()
8627 .hoverable_tooltip(move |_window, cx| {
8628 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8629 })
8630 })
8631 .children(keybind),
8632 )
8633 .into_any();
8634
8635 let longest_row =
8636 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8637 let longest_line_width = if visible_row_range.contains(&longest_row) {
8638 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8639 } else {
8640 layout_line(
8641 longest_row,
8642 editor_snapshot,
8643 style,
8644 editor_width,
8645 |_| false,
8646 window,
8647 cx,
8648 )
8649 .width
8650 };
8651
8652 let viewport_bounds =
8653 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8654 right: -right_margin,
8655 ..Default::default()
8656 });
8657
8658 let x_after_longest =
8659 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8660 - scroll_pixel_position.x;
8661
8662 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8663
8664 // Fully visible if it can be displayed within the window (allow overlapping other
8665 // panes). However, this is only allowed if the popover starts within text_bounds.
8666 let can_position_to_the_right = x_after_longest < text_bounds.right()
8667 && x_after_longest + element_bounds.width < viewport_bounds.right();
8668
8669 let mut origin = if can_position_to_the_right {
8670 point(
8671 x_after_longest,
8672 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8673 - scroll_pixel_position.y,
8674 )
8675 } else {
8676 let cursor_row = newest_selection_head.map(|head| head.row());
8677 let above_edit = edit_start
8678 .row()
8679 .0
8680 .checked_sub(line_count as u32)
8681 .map(DisplayRow);
8682 let below_edit = Some(edit_end.row() + 1);
8683 let above_cursor =
8684 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8685 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8686
8687 // Place the edit popover adjacent to the edit if there is a location
8688 // available that is onscreen and does not obscure the cursor. Otherwise,
8689 // place it adjacent to the cursor.
8690 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8691 .into_iter()
8692 .flatten()
8693 .find(|&start_row| {
8694 let end_row = start_row + line_count as u32;
8695 visible_row_range.contains(&start_row)
8696 && visible_row_range.contains(&end_row)
8697 && cursor_row.map_or(true, |cursor_row| {
8698 !((start_row..end_row).contains(&cursor_row))
8699 })
8700 })?;
8701
8702 content_origin
8703 + point(
8704 -scroll_pixel_position.x,
8705 row_target.as_f32() * line_height - scroll_pixel_position.y,
8706 )
8707 };
8708
8709 origin.x -= BORDER_WIDTH;
8710
8711 window.defer_draw(element, origin, 1);
8712
8713 // Do not return an element, since it will already be drawn due to defer_draw.
8714 None
8715 }
8716
8717 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8718 px(30.)
8719 }
8720
8721 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8722 if self.read_only(cx) {
8723 cx.theme().players().read_only()
8724 } else {
8725 self.style.as_ref().unwrap().local_player
8726 }
8727 }
8728
8729 fn render_edit_prediction_accept_keybind(
8730 &self,
8731 window: &mut Window,
8732 cx: &App,
8733 ) -> Option<AnyElement> {
8734 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8735 let accept_keystroke = accept_binding.keystroke()?;
8736
8737 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8738
8739 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8740 Color::Accent
8741 } else {
8742 Color::Muted
8743 };
8744
8745 h_flex()
8746 .px_0p5()
8747 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8748 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8749 .text_size(TextSize::XSmall.rems(cx))
8750 .child(h_flex().children(ui::render_modifiers(
8751 &accept_keystroke.modifiers,
8752 PlatformStyle::platform(),
8753 Some(modifiers_color),
8754 Some(IconSize::XSmall.rems().into()),
8755 true,
8756 )))
8757 .when(is_platform_style_mac, |parent| {
8758 parent.child(accept_keystroke.key.clone())
8759 })
8760 .when(!is_platform_style_mac, |parent| {
8761 parent.child(
8762 Key::new(
8763 util::capitalize(&accept_keystroke.key),
8764 Some(Color::Default),
8765 )
8766 .size(Some(IconSize::XSmall.rems().into())),
8767 )
8768 })
8769 .into_any()
8770 .into()
8771 }
8772
8773 fn render_edit_prediction_line_popover(
8774 &self,
8775 label: impl Into<SharedString>,
8776 icon: Option<IconName>,
8777 window: &mut Window,
8778 cx: &App,
8779 ) -> Option<Stateful<Div>> {
8780 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8781
8782 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8783 let has_keybind = keybind.is_some();
8784
8785 let result = h_flex()
8786 .id("ep-line-popover")
8787 .py_0p5()
8788 .pl_1()
8789 .pr(padding_right)
8790 .gap_1()
8791 .rounded_md()
8792 .border_1()
8793 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8794 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8795 .shadow_sm()
8796 .when(!has_keybind, |el| {
8797 let status_colors = cx.theme().status();
8798
8799 el.bg(status_colors.error_background)
8800 .border_color(status_colors.error.opacity(0.6))
8801 .pl_2()
8802 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8803 .cursor_default()
8804 .hoverable_tooltip(move |_window, cx| {
8805 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8806 })
8807 })
8808 .children(keybind)
8809 .child(
8810 Label::new(label)
8811 .size(LabelSize::Small)
8812 .when(!has_keybind, |el| {
8813 el.color(cx.theme().status().error.into()).strikethrough()
8814 }),
8815 )
8816 .when(!has_keybind, |el| {
8817 el.child(
8818 h_flex().ml_1().child(
8819 Icon::new(IconName::Info)
8820 .size(IconSize::Small)
8821 .color(cx.theme().status().error.into()),
8822 ),
8823 )
8824 })
8825 .when_some(icon, |element, icon| {
8826 element.child(
8827 div()
8828 .mt(px(1.5))
8829 .child(Icon::new(icon).size(IconSize::Small)),
8830 )
8831 });
8832
8833 Some(result)
8834 }
8835
8836 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8837 let accent_color = cx.theme().colors().text_accent;
8838 let editor_bg_color = cx.theme().colors().editor_background;
8839 editor_bg_color.blend(accent_color.opacity(0.1))
8840 }
8841
8842 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8843 let accent_color = cx.theme().colors().text_accent;
8844 let editor_bg_color = cx.theme().colors().editor_background;
8845 editor_bg_color.blend(accent_color.opacity(0.6))
8846 }
8847
8848 fn render_edit_prediction_cursor_popover(
8849 &self,
8850 min_width: Pixels,
8851 max_width: Pixels,
8852 cursor_point: Point,
8853 style: &EditorStyle,
8854 accept_keystroke: Option<&gpui::Keystroke>,
8855 _window: &Window,
8856 cx: &mut Context<Editor>,
8857 ) -> Option<AnyElement> {
8858 let provider = self.edit_prediction_provider.as_ref()?;
8859
8860 if provider.provider.needs_terms_acceptance(cx) {
8861 return Some(
8862 h_flex()
8863 .min_w(min_width)
8864 .flex_1()
8865 .px_2()
8866 .py_1()
8867 .gap_3()
8868 .elevation_2(cx)
8869 .hover(|style| style.bg(cx.theme().colors().element_hover))
8870 .id("accept-terms")
8871 .cursor_pointer()
8872 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8873 .on_click(cx.listener(|this, _event, window, cx| {
8874 cx.stop_propagation();
8875 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8876 window.dispatch_action(
8877 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8878 cx,
8879 );
8880 }))
8881 .child(
8882 h_flex()
8883 .flex_1()
8884 .gap_2()
8885 .child(Icon::new(IconName::ZedPredict))
8886 .child(Label::new("Accept Terms of Service"))
8887 .child(div().w_full())
8888 .child(
8889 Icon::new(IconName::ArrowUpRight)
8890 .color(Color::Muted)
8891 .size(IconSize::Small),
8892 )
8893 .into_any_element(),
8894 )
8895 .into_any(),
8896 );
8897 }
8898
8899 let is_refreshing = provider.provider.is_refreshing(cx);
8900
8901 fn pending_completion_container() -> Div {
8902 h_flex()
8903 .h_full()
8904 .flex_1()
8905 .gap_2()
8906 .child(Icon::new(IconName::ZedPredict))
8907 }
8908
8909 let completion = match &self.active_inline_completion {
8910 Some(prediction) => {
8911 if !self.has_visible_completions_menu() {
8912 const RADIUS: Pixels = px(6.);
8913 const BORDER_WIDTH: Pixels = px(1.);
8914
8915 return Some(
8916 h_flex()
8917 .elevation_2(cx)
8918 .border(BORDER_WIDTH)
8919 .border_color(cx.theme().colors().border)
8920 .when(accept_keystroke.is_none(), |el| {
8921 el.border_color(cx.theme().status().error)
8922 })
8923 .rounded(RADIUS)
8924 .rounded_tl(px(0.))
8925 .overflow_hidden()
8926 .child(div().px_1p5().child(match &prediction.completion {
8927 InlineCompletion::Move { target, snapshot } => {
8928 use text::ToPoint as _;
8929 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8930 {
8931 Icon::new(IconName::ZedPredictDown)
8932 } else {
8933 Icon::new(IconName::ZedPredictUp)
8934 }
8935 }
8936 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8937 }))
8938 .child(
8939 h_flex()
8940 .gap_1()
8941 .py_1()
8942 .px_2()
8943 .rounded_r(RADIUS - BORDER_WIDTH)
8944 .border_l_1()
8945 .border_color(cx.theme().colors().border)
8946 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8947 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8948 el.child(
8949 Label::new("Hold")
8950 .size(LabelSize::Small)
8951 .when(accept_keystroke.is_none(), |el| {
8952 el.strikethrough()
8953 })
8954 .line_height_style(LineHeightStyle::UiLabel),
8955 )
8956 })
8957 .id("edit_prediction_cursor_popover_keybind")
8958 .when(accept_keystroke.is_none(), |el| {
8959 let status_colors = cx.theme().status();
8960
8961 el.bg(status_colors.error_background)
8962 .border_color(status_colors.error.opacity(0.6))
8963 .child(Icon::new(IconName::Info).color(Color::Error))
8964 .cursor_default()
8965 .hoverable_tooltip(move |_window, cx| {
8966 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8967 .into()
8968 })
8969 })
8970 .when_some(
8971 accept_keystroke.as_ref(),
8972 |el, accept_keystroke| {
8973 el.child(h_flex().children(ui::render_modifiers(
8974 &accept_keystroke.modifiers,
8975 PlatformStyle::platform(),
8976 Some(Color::Default),
8977 Some(IconSize::XSmall.rems().into()),
8978 false,
8979 )))
8980 },
8981 ),
8982 )
8983 .into_any(),
8984 );
8985 }
8986
8987 self.render_edit_prediction_cursor_popover_preview(
8988 prediction,
8989 cursor_point,
8990 style,
8991 cx,
8992 )?
8993 }
8994
8995 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8996 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8997 stale_completion,
8998 cursor_point,
8999 style,
9000 cx,
9001 )?,
9002
9003 None => {
9004 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
9005 }
9006 },
9007
9008 None => pending_completion_container().child(Label::new("No Prediction")),
9009 };
9010
9011 let completion = if is_refreshing {
9012 completion
9013 .with_animation(
9014 "loading-completion",
9015 Animation::new(Duration::from_secs(2))
9016 .repeat()
9017 .with_easing(pulsating_between(0.4, 0.8)),
9018 |label, delta| label.opacity(delta),
9019 )
9020 .into_any_element()
9021 } else {
9022 completion.into_any_element()
9023 };
9024
9025 let has_completion = self.active_inline_completion.is_some();
9026
9027 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9028 Some(
9029 h_flex()
9030 .min_w(min_width)
9031 .max_w(max_width)
9032 .flex_1()
9033 .elevation_2(cx)
9034 .border_color(cx.theme().colors().border)
9035 .child(
9036 div()
9037 .flex_1()
9038 .py_1()
9039 .px_2()
9040 .overflow_hidden()
9041 .child(completion),
9042 )
9043 .when_some(accept_keystroke, |el, accept_keystroke| {
9044 if !accept_keystroke.modifiers.modified() {
9045 return el;
9046 }
9047
9048 el.child(
9049 h_flex()
9050 .h_full()
9051 .border_l_1()
9052 .rounded_r_lg()
9053 .border_color(cx.theme().colors().border)
9054 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9055 .gap_1()
9056 .py_1()
9057 .px_2()
9058 .child(
9059 h_flex()
9060 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9061 .when(is_platform_style_mac, |parent| parent.gap_1())
9062 .child(h_flex().children(ui::render_modifiers(
9063 &accept_keystroke.modifiers,
9064 PlatformStyle::platform(),
9065 Some(if !has_completion {
9066 Color::Muted
9067 } else {
9068 Color::Default
9069 }),
9070 None,
9071 false,
9072 ))),
9073 )
9074 .child(Label::new("Preview").into_any_element())
9075 .opacity(if has_completion { 1.0 } else { 0.4 }),
9076 )
9077 })
9078 .into_any(),
9079 )
9080 }
9081
9082 fn render_edit_prediction_cursor_popover_preview(
9083 &self,
9084 completion: &InlineCompletionState,
9085 cursor_point: Point,
9086 style: &EditorStyle,
9087 cx: &mut Context<Editor>,
9088 ) -> Option<Div> {
9089 use text::ToPoint as _;
9090
9091 fn render_relative_row_jump(
9092 prefix: impl Into<String>,
9093 current_row: u32,
9094 target_row: u32,
9095 ) -> Div {
9096 let (row_diff, arrow) = if target_row < current_row {
9097 (current_row - target_row, IconName::ArrowUp)
9098 } else {
9099 (target_row - current_row, IconName::ArrowDown)
9100 };
9101
9102 h_flex()
9103 .child(
9104 Label::new(format!("{}{}", prefix.into(), row_diff))
9105 .color(Color::Muted)
9106 .size(LabelSize::Small),
9107 )
9108 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9109 }
9110
9111 match &completion.completion {
9112 InlineCompletion::Move {
9113 target, snapshot, ..
9114 } => Some(
9115 h_flex()
9116 .px_2()
9117 .gap_2()
9118 .flex_1()
9119 .child(
9120 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9121 Icon::new(IconName::ZedPredictDown)
9122 } else {
9123 Icon::new(IconName::ZedPredictUp)
9124 },
9125 )
9126 .child(Label::new("Jump to Edit")),
9127 ),
9128
9129 InlineCompletion::Edit {
9130 edits,
9131 edit_preview,
9132 snapshot,
9133 display_mode: _,
9134 } => {
9135 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9136
9137 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
9138 &snapshot,
9139 &edits,
9140 edit_preview.as_ref()?,
9141 true,
9142 cx,
9143 )
9144 .first_line_preview();
9145
9146 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9147 .with_default_highlights(&style.text, highlighted_edits.highlights);
9148
9149 let preview = h_flex()
9150 .gap_1()
9151 .min_w_16()
9152 .child(styled_text)
9153 .when(has_more_lines, |parent| parent.child("…"));
9154
9155 let left = if first_edit_row != cursor_point.row {
9156 render_relative_row_jump("", cursor_point.row, first_edit_row)
9157 .into_any_element()
9158 } else {
9159 Icon::new(IconName::ZedPredict).into_any_element()
9160 };
9161
9162 Some(
9163 h_flex()
9164 .h_full()
9165 .flex_1()
9166 .gap_2()
9167 .pr_1()
9168 .overflow_x_hidden()
9169 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9170 .child(left)
9171 .child(preview),
9172 )
9173 }
9174 }
9175 }
9176
9177 pub fn render_context_menu(
9178 &self,
9179 style: &EditorStyle,
9180 max_height_in_lines: u32,
9181 window: &mut Window,
9182 cx: &mut Context<Editor>,
9183 ) -> Option<AnyElement> {
9184 let menu = self.context_menu.borrow();
9185 let menu = menu.as_ref()?;
9186 if !menu.visible() {
9187 return None;
9188 };
9189 Some(menu.render(style, max_height_in_lines, window, cx))
9190 }
9191
9192 fn render_context_menu_aside(
9193 &mut self,
9194 max_size: Size<Pixels>,
9195 window: &mut Window,
9196 cx: &mut Context<Editor>,
9197 ) -> Option<AnyElement> {
9198 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9199 if menu.visible() {
9200 menu.render_aside(max_size, window, cx)
9201 } else {
9202 None
9203 }
9204 })
9205 }
9206
9207 fn hide_context_menu(
9208 &mut self,
9209 window: &mut Window,
9210 cx: &mut Context<Self>,
9211 ) -> Option<CodeContextMenu> {
9212 cx.notify();
9213 self.completion_tasks.clear();
9214 let context_menu = self.context_menu.borrow_mut().take();
9215 self.stale_inline_completion_in_menu.take();
9216 self.update_visible_inline_completion(window, cx);
9217 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9218 if let Some(completion_provider) = &self.completion_provider {
9219 completion_provider.selection_changed(None, window, cx);
9220 }
9221 }
9222 context_menu
9223 }
9224
9225 fn show_snippet_choices(
9226 &mut self,
9227 choices: &Vec<String>,
9228 selection: Range<Anchor>,
9229 cx: &mut Context<Self>,
9230 ) {
9231 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9232 (Some(a), Some(b)) if a == b => a,
9233 _ => {
9234 log::error!("expected anchor range to have matching buffer IDs");
9235 return;
9236 }
9237 };
9238 let multi_buffer = self.buffer().read(cx);
9239 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9240 return;
9241 };
9242
9243 let id = post_inc(&mut self.next_completion_id);
9244 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9245 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9246 CompletionsMenu::new_snippet_choices(
9247 id,
9248 true,
9249 choices,
9250 selection,
9251 buffer,
9252 snippet_sort_order,
9253 ),
9254 ));
9255 }
9256
9257 pub fn insert_snippet(
9258 &mut self,
9259 insertion_ranges: &[Range<usize>],
9260 snippet: Snippet,
9261 window: &mut Window,
9262 cx: &mut Context<Self>,
9263 ) -> Result<()> {
9264 struct Tabstop<T> {
9265 is_end_tabstop: bool,
9266 ranges: Vec<Range<T>>,
9267 choices: Option<Vec<String>>,
9268 }
9269
9270 let tabstops = self.buffer.update(cx, |buffer, cx| {
9271 let snippet_text: Arc<str> = snippet.text.clone().into();
9272 let edits = insertion_ranges
9273 .iter()
9274 .cloned()
9275 .map(|range| (range, snippet_text.clone()));
9276 let autoindent_mode = AutoindentMode::Block {
9277 original_indent_columns: Vec::new(),
9278 };
9279 buffer.edit(edits, Some(autoindent_mode), cx);
9280
9281 let snapshot = &*buffer.read(cx);
9282 let snippet = &snippet;
9283 snippet
9284 .tabstops
9285 .iter()
9286 .map(|tabstop| {
9287 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9288 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9289 });
9290 let mut tabstop_ranges = tabstop
9291 .ranges
9292 .iter()
9293 .flat_map(|tabstop_range| {
9294 let mut delta = 0_isize;
9295 insertion_ranges.iter().map(move |insertion_range| {
9296 let insertion_start = insertion_range.start as isize + delta;
9297 delta +=
9298 snippet.text.len() as isize - insertion_range.len() as isize;
9299
9300 let start = ((insertion_start + tabstop_range.start) as usize)
9301 .min(snapshot.len());
9302 let end = ((insertion_start + tabstop_range.end) as usize)
9303 .min(snapshot.len());
9304 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9305 })
9306 })
9307 .collect::<Vec<_>>();
9308 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9309
9310 Tabstop {
9311 is_end_tabstop,
9312 ranges: tabstop_ranges,
9313 choices: tabstop.choices.clone(),
9314 }
9315 })
9316 .collect::<Vec<_>>()
9317 });
9318 if let Some(tabstop) = tabstops.first() {
9319 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9320 // Reverse order so that the first range is the newest created selection.
9321 // Completions will use it and autoscroll will prioritize it.
9322 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9323 });
9324
9325 if let Some(choices) = &tabstop.choices {
9326 if let Some(selection) = tabstop.ranges.first() {
9327 self.show_snippet_choices(choices, selection.clone(), cx)
9328 }
9329 }
9330
9331 // If we're already at the last tabstop and it's at the end of the snippet,
9332 // we're done, we don't need to keep the state around.
9333 if !tabstop.is_end_tabstop {
9334 let choices = tabstops
9335 .iter()
9336 .map(|tabstop| tabstop.choices.clone())
9337 .collect();
9338
9339 let ranges = tabstops
9340 .into_iter()
9341 .map(|tabstop| tabstop.ranges)
9342 .collect::<Vec<_>>();
9343
9344 self.snippet_stack.push(SnippetState {
9345 active_index: 0,
9346 ranges,
9347 choices,
9348 });
9349 }
9350
9351 // Check whether the just-entered snippet ends with an auto-closable bracket.
9352 if self.autoclose_regions.is_empty() {
9353 let snapshot = self.buffer.read(cx).snapshot(cx);
9354 for selection in &mut self.selections.all::<Point>(cx) {
9355 let selection_head = selection.head();
9356 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9357 continue;
9358 };
9359
9360 let mut bracket_pair = None;
9361 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9362 let prev_chars = snapshot
9363 .reversed_chars_at(selection_head)
9364 .collect::<String>();
9365 for (pair, enabled) in scope.brackets() {
9366 if enabled
9367 && pair.close
9368 && prev_chars.starts_with(pair.start.as_str())
9369 && next_chars.starts_with(pair.end.as_str())
9370 {
9371 bracket_pair = Some(pair.clone());
9372 break;
9373 }
9374 }
9375 if let Some(pair) = bracket_pair {
9376 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9377 let autoclose_enabled =
9378 self.use_autoclose && snapshot_settings.use_autoclose;
9379 if autoclose_enabled {
9380 let start = snapshot.anchor_after(selection_head);
9381 let end = snapshot.anchor_after(selection_head);
9382 self.autoclose_regions.push(AutocloseRegion {
9383 selection_id: selection.id,
9384 range: start..end,
9385 pair,
9386 });
9387 }
9388 }
9389 }
9390 }
9391 }
9392 Ok(())
9393 }
9394
9395 pub fn move_to_next_snippet_tabstop(
9396 &mut self,
9397 window: &mut Window,
9398 cx: &mut Context<Self>,
9399 ) -> bool {
9400 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9401 }
9402
9403 pub fn move_to_prev_snippet_tabstop(
9404 &mut self,
9405 window: &mut Window,
9406 cx: &mut Context<Self>,
9407 ) -> bool {
9408 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9409 }
9410
9411 pub fn move_to_snippet_tabstop(
9412 &mut self,
9413 bias: Bias,
9414 window: &mut Window,
9415 cx: &mut Context<Self>,
9416 ) -> bool {
9417 if let Some(mut snippet) = self.snippet_stack.pop() {
9418 match bias {
9419 Bias::Left => {
9420 if snippet.active_index > 0 {
9421 snippet.active_index -= 1;
9422 } else {
9423 self.snippet_stack.push(snippet);
9424 return false;
9425 }
9426 }
9427 Bias::Right => {
9428 if snippet.active_index + 1 < snippet.ranges.len() {
9429 snippet.active_index += 1;
9430 } else {
9431 self.snippet_stack.push(snippet);
9432 return false;
9433 }
9434 }
9435 }
9436 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9437 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9438 // Reverse order so that the first range is the newest created selection.
9439 // Completions will use it and autoscroll will prioritize it.
9440 s.select_ranges(current_ranges.iter().rev().cloned())
9441 });
9442
9443 if let Some(choices) = &snippet.choices[snippet.active_index] {
9444 if let Some(selection) = current_ranges.first() {
9445 self.show_snippet_choices(&choices, selection.clone(), cx);
9446 }
9447 }
9448
9449 // If snippet state is not at the last tabstop, push it back on the stack
9450 if snippet.active_index + 1 < snippet.ranges.len() {
9451 self.snippet_stack.push(snippet);
9452 }
9453 return true;
9454 }
9455 }
9456
9457 false
9458 }
9459
9460 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9461 self.transact(window, cx, |this, window, cx| {
9462 this.select_all(&SelectAll, window, cx);
9463 this.insert("", window, cx);
9464 });
9465 }
9466
9467 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9468 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9469 self.transact(window, cx, |this, window, cx| {
9470 this.select_autoclose_pair(window, cx);
9471 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9472 if !this.linked_edit_ranges.is_empty() {
9473 let selections = this.selections.all::<MultiBufferPoint>(cx);
9474 let snapshot = this.buffer.read(cx).snapshot(cx);
9475
9476 for selection in selections.iter() {
9477 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9478 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9479 if selection_start.buffer_id != selection_end.buffer_id {
9480 continue;
9481 }
9482 if let Some(ranges) =
9483 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9484 {
9485 for (buffer, entries) in ranges {
9486 linked_ranges.entry(buffer).or_default().extend(entries);
9487 }
9488 }
9489 }
9490 }
9491
9492 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9493 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9494 for selection in &mut selections {
9495 if selection.is_empty() {
9496 let old_head = selection.head();
9497 let mut new_head =
9498 movement::left(&display_map, old_head.to_display_point(&display_map))
9499 .to_point(&display_map);
9500 if let Some((buffer, line_buffer_range)) = display_map
9501 .buffer_snapshot
9502 .buffer_line_for_row(MultiBufferRow(old_head.row))
9503 {
9504 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9505 let indent_len = match indent_size.kind {
9506 IndentKind::Space => {
9507 buffer.settings_at(line_buffer_range.start, cx).tab_size
9508 }
9509 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9510 };
9511 if old_head.column <= indent_size.len && old_head.column > 0 {
9512 let indent_len = indent_len.get();
9513 new_head = cmp::min(
9514 new_head,
9515 MultiBufferPoint::new(
9516 old_head.row,
9517 ((old_head.column - 1) / indent_len) * indent_len,
9518 ),
9519 );
9520 }
9521 }
9522
9523 selection.set_head(new_head, SelectionGoal::None);
9524 }
9525 }
9526
9527 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9528 s.select(selections)
9529 });
9530 this.insert("", window, cx);
9531 let empty_str: Arc<str> = Arc::from("");
9532 for (buffer, edits) in linked_ranges {
9533 let snapshot = buffer.read(cx).snapshot();
9534 use text::ToPoint as TP;
9535
9536 let edits = edits
9537 .into_iter()
9538 .map(|range| {
9539 let end_point = TP::to_point(&range.end, &snapshot);
9540 let mut start_point = TP::to_point(&range.start, &snapshot);
9541
9542 if end_point == start_point {
9543 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9544 .saturating_sub(1);
9545 start_point =
9546 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9547 };
9548
9549 (start_point..end_point, empty_str.clone())
9550 })
9551 .sorted_by_key(|(range, _)| range.start)
9552 .collect::<Vec<_>>();
9553 buffer.update(cx, |this, cx| {
9554 this.edit(edits, None, cx);
9555 })
9556 }
9557 this.refresh_inline_completion(true, false, window, cx);
9558 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9559 });
9560 }
9561
9562 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9563 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9564 self.transact(window, cx, |this, window, cx| {
9565 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9566 s.move_with(|map, selection| {
9567 if selection.is_empty() {
9568 let cursor = movement::right(map, selection.head());
9569 selection.end = cursor;
9570 selection.reversed = true;
9571 selection.goal = SelectionGoal::None;
9572 }
9573 })
9574 });
9575 this.insert("", window, cx);
9576 this.refresh_inline_completion(true, false, window, cx);
9577 });
9578 }
9579
9580 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9581 if self.mode.is_single_line() {
9582 cx.propagate();
9583 return;
9584 }
9585
9586 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9587 if self.move_to_prev_snippet_tabstop(window, cx) {
9588 return;
9589 }
9590 self.outdent(&Outdent, window, cx);
9591 }
9592
9593 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9594 if self.mode.is_single_line() {
9595 cx.propagate();
9596 return;
9597 }
9598
9599 if self.move_to_next_snippet_tabstop(window, cx) {
9600 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9601 return;
9602 }
9603 if self.read_only(cx) {
9604 return;
9605 }
9606 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9607 let mut selections = self.selections.all_adjusted(cx);
9608 let buffer = self.buffer.read(cx);
9609 let snapshot = buffer.snapshot(cx);
9610 let rows_iter = selections.iter().map(|s| s.head().row);
9611 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9612
9613 let has_some_cursor_in_whitespace = selections
9614 .iter()
9615 .filter(|selection| selection.is_empty())
9616 .any(|selection| {
9617 let cursor = selection.head();
9618 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9619 cursor.column < current_indent.len
9620 });
9621
9622 let mut edits = Vec::new();
9623 let mut prev_edited_row = 0;
9624 let mut row_delta = 0;
9625 for selection in &mut selections {
9626 if selection.start.row != prev_edited_row {
9627 row_delta = 0;
9628 }
9629 prev_edited_row = selection.end.row;
9630
9631 // If the selection is non-empty, then increase the indentation of the selected lines.
9632 if !selection.is_empty() {
9633 row_delta =
9634 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9635 continue;
9636 }
9637
9638 let cursor = selection.head();
9639 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9640 if let Some(suggested_indent) =
9641 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9642 {
9643 // Don't do anything if already at suggested indent
9644 // and there is any other cursor which is not
9645 if has_some_cursor_in_whitespace
9646 && cursor.column == current_indent.len
9647 && current_indent.len == suggested_indent.len
9648 {
9649 continue;
9650 }
9651
9652 // Adjust line and move cursor to suggested indent
9653 // if cursor is not at suggested indent
9654 if cursor.column < suggested_indent.len
9655 && cursor.column <= current_indent.len
9656 && current_indent.len <= suggested_indent.len
9657 {
9658 selection.start = Point::new(cursor.row, suggested_indent.len);
9659 selection.end = selection.start;
9660 if row_delta == 0 {
9661 edits.extend(Buffer::edit_for_indent_size_adjustment(
9662 cursor.row,
9663 current_indent,
9664 suggested_indent,
9665 ));
9666 row_delta = suggested_indent.len - current_indent.len;
9667 }
9668 continue;
9669 }
9670
9671 // If current indent is more than suggested indent
9672 // only move cursor to current indent and skip indent
9673 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9674 selection.start = Point::new(cursor.row, current_indent.len);
9675 selection.end = selection.start;
9676 continue;
9677 }
9678 }
9679
9680 // Otherwise, insert a hard or soft tab.
9681 let settings = buffer.language_settings_at(cursor, cx);
9682 let tab_size = if settings.hard_tabs {
9683 IndentSize::tab()
9684 } else {
9685 let tab_size = settings.tab_size.get();
9686 let indent_remainder = snapshot
9687 .text_for_range(Point::new(cursor.row, 0)..cursor)
9688 .flat_map(str::chars)
9689 .fold(row_delta % tab_size, |counter: u32, c| {
9690 if c == '\t' {
9691 0
9692 } else {
9693 (counter + 1) % tab_size
9694 }
9695 });
9696
9697 let chars_to_next_tab_stop = tab_size - indent_remainder;
9698 IndentSize::spaces(chars_to_next_tab_stop)
9699 };
9700 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9701 selection.end = selection.start;
9702 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9703 row_delta += tab_size.len;
9704 }
9705
9706 self.transact(window, cx, |this, window, cx| {
9707 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9708 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9709 s.select(selections)
9710 });
9711 this.refresh_inline_completion(true, false, window, cx);
9712 });
9713 }
9714
9715 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9716 if self.read_only(cx) {
9717 return;
9718 }
9719 if self.mode.is_single_line() {
9720 cx.propagate();
9721 return;
9722 }
9723
9724 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9725 let mut selections = self.selections.all::<Point>(cx);
9726 let mut prev_edited_row = 0;
9727 let mut row_delta = 0;
9728 let mut edits = Vec::new();
9729 let buffer = self.buffer.read(cx);
9730 let snapshot = buffer.snapshot(cx);
9731 for selection in &mut selections {
9732 if selection.start.row != prev_edited_row {
9733 row_delta = 0;
9734 }
9735 prev_edited_row = selection.end.row;
9736
9737 row_delta =
9738 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9739 }
9740
9741 self.transact(window, cx, |this, window, cx| {
9742 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9743 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9744 s.select(selections)
9745 });
9746 });
9747 }
9748
9749 fn indent_selection(
9750 buffer: &MultiBuffer,
9751 snapshot: &MultiBufferSnapshot,
9752 selection: &mut Selection<Point>,
9753 edits: &mut Vec<(Range<Point>, String)>,
9754 delta_for_start_row: u32,
9755 cx: &App,
9756 ) -> u32 {
9757 let settings = buffer.language_settings_at(selection.start, cx);
9758 let tab_size = settings.tab_size.get();
9759 let indent_kind = if settings.hard_tabs {
9760 IndentKind::Tab
9761 } else {
9762 IndentKind::Space
9763 };
9764 let mut start_row = selection.start.row;
9765 let mut end_row = selection.end.row + 1;
9766
9767 // If a selection ends at the beginning of a line, don't indent
9768 // that last line.
9769 if selection.end.column == 0 && selection.end.row > selection.start.row {
9770 end_row -= 1;
9771 }
9772
9773 // Avoid re-indenting a row that has already been indented by a
9774 // previous selection, but still update this selection's column
9775 // to reflect that indentation.
9776 if delta_for_start_row > 0 {
9777 start_row += 1;
9778 selection.start.column += delta_for_start_row;
9779 if selection.end.row == selection.start.row {
9780 selection.end.column += delta_for_start_row;
9781 }
9782 }
9783
9784 let mut delta_for_end_row = 0;
9785 let has_multiple_rows = start_row + 1 != end_row;
9786 for row in start_row..end_row {
9787 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9788 let indent_delta = match (current_indent.kind, indent_kind) {
9789 (IndentKind::Space, IndentKind::Space) => {
9790 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9791 IndentSize::spaces(columns_to_next_tab_stop)
9792 }
9793 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9794 (_, IndentKind::Tab) => IndentSize::tab(),
9795 };
9796
9797 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9798 0
9799 } else {
9800 selection.start.column
9801 };
9802 let row_start = Point::new(row, start);
9803 edits.push((
9804 row_start..row_start,
9805 indent_delta.chars().collect::<String>(),
9806 ));
9807
9808 // Update this selection's endpoints to reflect the indentation.
9809 if row == selection.start.row {
9810 selection.start.column += indent_delta.len;
9811 }
9812 if row == selection.end.row {
9813 selection.end.column += indent_delta.len;
9814 delta_for_end_row = indent_delta.len;
9815 }
9816 }
9817
9818 if selection.start.row == selection.end.row {
9819 delta_for_start_row + delta_for_end_row
9820 } else {
9821 delta_for_end_row
9822 }
9823 }
9824
9825 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9826 if self.read_only(cx) {
9827 return;
9828 }
9829 if self.mode.is_single_line() {
9830 cx.propagate();
9831 return;
9832 }
9833
9834 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9835 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9836 let selections = self.selections.all::<Point>(cx);
9837 let mut deletion_ranges = Vec::new();
9838 let mut last_outdent = None;
9839 {
9840 let buffer = self.buffer.read(cx);
9841 let snapshot = buffer.snapshot(cx);
9842 for selection in &selections {
9843 let settings = buffer.language_settings_at(selection.start, cx);
9844 let tab_size = settings.tab_size.get();
9845 let mut rows = selection.spanned_rows(false, &display_map);
9846
9847 // Avoid re-outdenting a row that has already been outdented by a
9848 // previous selection.
9849 if let Some(last_row) = last_outdent {
9850 if last_row == rows.start {
9851 rows.start = rows.start.next_row();
9852 }
9853 }
9854 let has_multiple_rows = rows.len() > 1;
9855 for row in rows.iter_rows() {
9856 let indent_size = snapshot.indent_size_for_line(row);
9857 if indent_size.len > 0 {
9858 let deletion_len = match indent_size.kind {
9859 IndentKind::Space => {
9860 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9861 if columns_to_prev_tab_stop == 0 {
9862 tab_size
9863 } else {
9864 columns_to_prev_tab_stop
9865 }
9866 }
9867 IndentKind::Tab => 1,
9868 };
9869 let start = if has_multiple_rows
9870 || deletion_len > selection.start.column
9871 || indent_size.len < selection.start.column
9872 {
9873 0
9874 } else {
9875 selection.start.column - deletion_len
9876 };
9877 deletion_ranges.push(
9878 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9879 );
9880 last_outdent = Some(row);
9881 }
9882 }
9883 }
9884 }
9885
9886 self.transact(window, cx, |this, window, cx| {
9887 this.buffer.update(cx, |buffer, cx| {
9888 let empty_str: Arc<str> = Arc::default();
9889 buffer.edit(
9890 deletion_ranges
9891 .into_iter()
9892 .map(|range| (range, empty_str.clone())),
9893 None,
9894 cx,
9895 );
9896 });
9897 let selections = this.selections.all::<usize>(cx);
9898 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9899 s.select(selections)
9900 });
9901 });
9902 }
9903
9904 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9905 if self.read_only(cx) {
9906 return;
9907 }
9908 if self.mode.is_single_line() {
9909 cx.propagate();
9910 return;
9911 }
9912
9913 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9914 let selections = self
9915 .selections
9916 .all::<usize>(cx)
9917 .into_iter()
9918 .map(|s| s.range());
9919
9920 self.transact(window, cx, |this, window, cx| {
9921 this.buffer.update(cx, |buffer, cx| {
9922 buffer.autoindent_ranges(selections, cx);
9923 });
9924 let selections = this.selections.all::<usize>(cx);
9925 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9926 s.select(selections)
9927 });
9928 });
9929 }
9930
9931 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9932 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9933 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9934 let selections = self.selections.all::<Point>(cx);
9935
9936 let mut new_cursors = Vec::new();
9937 let mut edit_ranges = Vec::new();
9938 let mut selections = selections.iter().peekable();
9939 while let Some(selection) = selections.next() {
9940 let mut rows = selection.spanned_rows(false, &display_map);
9941 let goal_display_column = selection.head().to_display_point(&display_map).column();
9942
9943 // Accumulate contiguous regions of rows that we want to delete.
9944 while let Some(next_selection) = selections.peek() {
9945 let next_rows = next_selection.spanned_rows(false, &display_map);
9946 if next_rows.start <= rows.end {
9947 rows.end = next_rows.end;
9948 selections.next().unwrap();
9949 } else {
9950 break;
9951 }
9952 }
9953
9954 let buffer = &display_map.buffer_snapshot;
9955 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9956 let edit_end;
9957 let cursor_buffer_row;
9958 if buffer.max_point().row >= rows.end.0 {
9959 // If there's a line after the range, delete the \n from the end of the row range
9960 // and position the cursor on the next line.
9961 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9962 cursor_buffer_row = rows.end;
9963 } else {
9964 // If there isn't a line after the range, delete the \n from the line before the
9965 // start of the row range and position the cursor there.
9966 edit_start = edit_start.saturating_sub(1);
9967 edit_end = buffer.len();
9968 cursor_buffer_row = rows.start.previous_row();
9969 }
9970
9971 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9972 *cursor.column_mut() =
9973 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9974
9975 new_cursors.push((
9976 selection.id,
9977 buffer.anchor_after(cursor.to_point(&display_map)),
9978 ));
9979 edit_ranges.push(edit_start..edit_end);
9980 }
9981
9982 self.transact(window, cx, |this, window, cx| {
9983 let buffer = this.buffer.update(cx, |buffer, cx| {
9984 let empty_str: Arc<str> = Arc::default();
9985 buffer.edit(
9986 edit_ranges
9987 .into_iter()
9988 .map(|range| (range, empty_str.clone())),
9989 None,
9990 cx,
9991 );
9992 buffer.snapshot(cx)
9993 });
9994 let new_selections = new_cursors
9995 .into_iter()
9996 .map(|(id, cursor)| {
9997 let cursor = cursor.to_point(&buffer);
9998 Selection {
9999 id,
10000 start: cursor,
10001 end: cursor,
10002 reversed: false,
10003 goal: SelectionGoal::None,
10004 }
10005 })
10006 .collect();
10007
10008 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10009 s.select(new_selections);
10010 });
10011 });
10012 }
10013
10014 pub fn join_lines_impl(
10015 &mut self,
10016 insert_whitespace: bool,
10017 window: &mut Window,
10018 cx: &mut Context<Self>,
10019 ) {
10020 if self.read_only(cx) {
10021 return;
10022 }
10023 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10024 for selection in self.selections.all::<Point>(cx) {
10025 let start = MultiBufferRow(selection.start.row);
10026 // Treat single line selections as if they include the next line. Otherwise this action
10027 // would do nothing for single line selections individual cursors.
10028 let end = if selection.start.row == selection.end.row {
10029 MultiBufferRow(selection.start.row + 1)
10030 } else {
10031 MultiBufferRow(selection.end.row)
10032 };
10033
10034 if let Some(last_row_range) = row_ranges.last_mut() {
10035 if start <= last_row_range.end {
10036 last_row_range.end = end;
10037 continue;
10038 }
10039 }
10040 row_ranges.push(start..end);
10041 }
10042
10043 let snapshot = self.buffer.read(cx).snapshot(cx);
10044 let mut cursor_positions = Vec::new();
10045 for row_range in &row_ranges {
10046 let anchor = snapshot.anchor_before(Point::new(
10047 row_range.end.previous_row().0,
10048 snapshot.line_len(row_range.end.previous_row()),
10049 ));
10050 cursor_positions.push(anchor..anchor);
10051 }
10052
10053 self.transact(window, cx, |this, window, cx| {
10054 for row_range in row_ranges.into_iter().rev() {
10055 for row in row_range.iter_rows().rev() {
10056 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10057 let next_line_row = row.next_row();
10058 let indent = snapshot.indent_size_for_line(next_line_row);
10059 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10060
10061 let replace =
10062 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10063 " "
10064 } else {
10065 ""
10066 };
10067
10068 this.buffer.update(cx, |buffer, cx| {
10069 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10070 });
10071 }
10072 }
10073
10074 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10075 s.select_anchor_ranges(cursor_positions)
10076 });
10077 });
10078 }
10079
10080 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10081 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10082 self.join_lines_impl(true, window, cx);
10083 }
10084
10085 pub fn sort_lines_case_sensitive(
10086 &mut self,
10087 _: &SortLinesCaseSensitive,
10088 window: &mut Window,
10089 cx: &mut Context<Self>,
10090 ) {
10091 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10092 }
10093
10094 pub fn sort_lines_case_insensitive(
10095 &mut self,
10096 _: &SortLinesCaseInsensitive,
10097 window: &mut Window,
10098 cx: &mut Context<Self>,
10099 ) {
10100 self.manipulate_immutable_lines(window, cx, |lines| {
10101 lines.sort_by_key(|line| line.to_lowercase())
10102 })
10103 }
10104
10105 pub fn unique_lines_case_insensitive(
10106 &mut self,
10107 _: &UniqueLinesCaseInsensitive,
10108 window: &mut Window,
10109 cx: &mut Context<Self>,
10110 ) {
10111 self.manipulate_immutable_lines(window, cx, |lines| {
10112 let mut seen = HashSet::default();
10113 lines.retain(|line| seen.insert(line.to_lowercase()));
10114 })
10115 }
10116
10117 pub fn unique_lines_case_sensitive(
10118 &mut self,
10119 _: &UniqueLinesCaseSensitive,
10120 window: &mut Window,
10121 cx: &mut Context<Self>,
10122 ) {
10123 self.manipulate_immutable_lines(window, cx, |lines| {
10124 let mut seen = HashSet::default();
10125 lines.retain(|line| seen.insert(*line));
10126 })
10127 }
10128
10129 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10130 let Some(project) = self.project.clone() else {
10131 return;
10132 };
10133 self.reload(project, window, cx)
10134 .detach_and_notify_err(window, cx);
10135 }
10136
10137 pub fn restore_file(
10138 &mut self,
10139 _: &::git::RestoreFile,
10140 window: &mut Window,
10141 cx: &mut Context<Self>,
10142 ) {
10143 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10144 let mut buffer_ids = HashSet::default();
10145 let snapshot = self.buffer().read(cx).snapshot(cx);
10146 for selection in self.selections.all::<usize>(cx) {
10147 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10148 }
10149
10150 let buffer = self.buffer().read(cx);
10151 let ranges = buffer_ids
10152 .into_iter()
10153 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10154 .collect::<Vec<_>>();
10155
10156 self.restore_hunks_in_ranges(ranges, window, cx);
10157 }
10158
10159 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10160 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10161 let selections = self
10162 .selections
10163 .all(cx)
10164 .into_iter()
10165 .map(|s| s.range())
10166 .collect();
10167 self.restore_hunks_in_ranges(selections, window, cx);
10168 }
10169
10170 pub fn restore_hunks_in_ranges(
10171 &mut self,
10172 ranges: Vec<Range<Point>>,
10173 window: &mut Window,
10174 cx: &mut Context<Editor>,
10175 ) {
10176 let mut revert_changes = HashMap::default();
10177 let chunk_by = self
10178 .snapshot(window, cx)
10179 .hunks_for_ranges(ranges)
10180 .into_iter()
10181 .chunk_by(|hunk| hunk.buffer_id);
10182 for (buffer_id, hunks) in &chunk_by {
10183 let hunks = hunks.collect::<Vec<_>>();
10184 for hunk in &hunks {
10185 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10186 }
10187 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10188 }
10189 drop(chunk_by);
10190 if !revert_changes.is_empty() {
10191 self.transact(window, cx, |editor, window, cx| {
10192 editor.restore(revert_changes, window, cx);
10193 });
10194 }
10195 }
10196
10197 pub fn open_active_item_in_terminal(
10198 &mut self,
10199 _: &OpenInTerminal,
10200 window: &mut Window,
10201 cx: &mut Context<Self>,
10202 ) {
10203 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10204 let project_path = buffer.read(cx).project_path(cx)?;
10205 let project = self.project.as_ref()?.read(cx);
10206 let entry = project.entry_for_path(&project_path, cx)?;
10207 let parent = match &entry.canonical_path {
10208 Some(canonical_path) => canonical_path.to_path_buf(),
10209 None => project.absolute_path(&project_path, cx)?,
10210 }
10211 .parent()?
10212 .to_path_buf();
10213 Some(parent)
10214 }) {
10215 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10216 }
10217 }
10218
10219 fn set_breakpoint_context_menu(
10220 &mut self,
10221 display_row: DisplayRow,
10222 position: Option<Anchor>,
10223 clicked_point: gpui::Point<Pixels>,
10224 window: &mut Window,
10225 cx: &mut Context<Self>,
10226 ) {
10227 let source = self
10228 .buffer
10229 .read(cx)
10230 .snapshot(cx)
10231 .anchor_before(Point::new(display_row.0, 0u32));
10232
10233 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10234
10235 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10236 self,
10237 source,
10238 clicked_point,
10239 context_menu,
10240 window,
10241 cx,
10242 );
10243 }
10244
10245 fn add_edit_breakpoint_block(
10246 &mut self,
10247 anchor: Anchor,
10248 breakpoint: &Breakpoint,
10249 edit_action: BreakpointPromptEditAction,
10250 window: &mut Window,
10251 cx: &mut Context<Self>,
10252 ) {
10253 let weak_editor = cx.weak_entity();
10254 let bp_prompt = cx.new(|cx| {
10255 BreakpointPromptEditor::new(
10256 weak_editor,
10257 anchor,
10258 breakpoint.clone(),
10259 edit_action,
10260 window,
10261 cx,
10262 )
10263 });
10264
10265 let height = bp_prompt.update(cx, |this, cx| {
10266 this.prompt
10267 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10268 });
10269 let cloned_prompt = bp_prompt.clone();
10270 let blocks = vec![BlockProperties {
10271 style: BlockStyle::Sticky,
10272 placement: BlockPlacement::Above(anchor),
10273 height: Some(height),
10274 render: Arc::new(move |cx| {
10275 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10276 cloned_prompt.clone().into_any_element()
10277 }),
10278 priority: 0,
10279 render_in_minimap: true,
10280 }];
10281
10282 let focus_handle = bp_prompt.focus_handle(cx);
10283 window.focus(&focus_handle);
10284
10285 let block_ids = self.insert_blocks(blocks, None, cx);
10286 bp_prompt.update(cx, |prompt, _| {
10287 prompt.add_block_ids(block_ids);
10288 });
10289 }
10290
10291 pub(crate) fn breakpoint_at_row(
10292 &self,
10293 row: u32,
10294 window: &mut Window,
10295 cx: &mut Context<Self>,
10296 ) -> Option<(Anchor, Breakpoint)> {
10297 let snapshot = self.snapshot(window, cx);
10298 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10299
10300 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10301 }
10302
10303 pub(crate) fn breakpoint_at_anchor(
10304 &self,
10305 breakpoint_position: Anchor,
10306 snapshot: &EditorSnapshot,
10307 cx: &mut Context<Self>,
10308 ) -> Option<(Anchor, Breakpoint)> {
10309 let project = self.project.clone()?;
10310
10311 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10312 snapshot
10313 .buffer_snapshot
10314 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10315 })?;
10316
10317 let enclosing_excerpt = breakpoint_position.excerpt_id;
10318 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10319 let buffer_snapshot = buffer.read(cx).snapshot();
10320
10321 let row = buffer_snapshot
10322 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10323 .row;
10324
10325 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10326 let anchor_end = snapshot
10327 .buffer_snapshot
10328 .anchor_after(Point::new(row, line_len));
10329
10330 let bp = self
10331 .breakpoint_store
10332 .as_ref()?
10333 .read_with(cx, |breakpoint_store, cx| {
10334 breakpoint_store
10335 .breakpoints(
10336 &buffer,
10337 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10338 &buffer_snapshot,
10339 cx,
10340 )
10341 .next()
10342 .and_then(|(bp, _)| {
10343 let breakpoint_row = buffer_snapshot
10344 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10345 .row;
10346
10347 if breakpoint_row == row {
10348 snapshot
10349 .buffer_snapshot
10350 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10351 .map(|position| (position, bp.bp.clone()))
10352 } else {
10353 None
10354 }
10355 })
10356 });
10357 bp
10358 }
10359
10360 pub fn edit_log_breakpoint(
10361 &mut self,
10362 _: &EditLogBreakpoint,
10363 window: &mut Window,
10364 cx: &mut Context<Self>,
10365 ) {
10366 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10367 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10368 message: None,
10369 state: BreakpointState::Enabled,
10370 condition: None,
10371 hit_condition: None,
10372 });
10373
10374 self.add_edit_breakpoint_block(
10375 anchor,
10376 &breakpoint,
10377 BreakpointPromptEditAction::Log,
10378 window,
10379 cx,
10380 );
10381 }
10382 }
10383
10384 fn breakpoints_at_cursors(
10385 &self,
10386 window: &mut Window,
10387 cx: &mut Context<Self>,
10388 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10389 let snapshot = self.snapshot(window, cx);
10390 let cursors = self
10391 .selections
10392 .disjoint_anchors()
10393 .into_iter()
10394 .map(|selection| {
10395 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10396
10397 let breakpoint_position = self
10398 .breakpoint_at_row(cursor_position.row, window, cx)
10399 .map(|bp| bp.0)
10400 .unwrap_or_else(|| {
10401 snapshot
10402 .display_snapshot
10403 .buffer_snapshot
10404 .anchor_after(Point::new(cursor_position.row, 0))
10405 });
10406
10407 let breakpoint = self
10408 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10409 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10410
10411 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10412 })
10413 // 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.
10414 .collect::<HashMap<Anchor, _>>();
10415
10416 cursors.into_iter().collect()
10417 }
10418
10419 pub fn enable_breakpoint(
10420 &mut self,
10421 _: &crate::actions::EnableBreakpoint,
10422 window: &mut Window,
10423 cx: &mut Context<Self>,
10424 ) {
10425 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10426 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10427 continue;
10428 };
10429 self.edit_breakpoint_at_anchor(
10430 anchor,
10431 breakpoint,
10432 BreakpointEditAction::InvertState,
10433 cx,
10434 );
10435 }
10436 }
10437
10438 pub fn disable_breakpoint(
10439 &mut self,
10440 _: &crate::actions::DisableBreakpoint,
10441 window: &mut Window,
10442 cx: &mut Context<Self>,
10443 ) {
10444 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10445 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10446 continue;
10447 };
10448 self.edit_breakpoint_at_anchor(
10449 anchor,
10450 breakpoint,
10451 BreakpointEditAction::InvertState,
10452 cx,
10453 );
10454 }
10455 }
10456
10457 pub fn toggle_breakpoint(
10458 &mut self,
10459 _: &crate::actions::ToggleBreakpoint,
10460 window: &mut Window,
10461 cx: &mut Context<Self>,
10462 ) {
10463 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10464 if let Some(breakpoint) = breakpoint {
10465 self.edit_breakpoint_at_anchor(
10466 anchor,
10467 breakpoint,
10468 BreakpointEditAction::Toggle,
10469 cx,
10470 );
10471 } else {
10472 self.edit_breakpoint_at_anchor(
10473 anchor,
10474 Breakpoint::new_standard(),
10475 BreakpointEditAction::Toggle,
10476 cx,
10477 );
10478 }
10479 }
10480 }
10481
10482 pub fn edit_breakpoint_at_anchor(
10483 &mut self,
10484 breakpoint_position: Anchor,
10485 breakpoint: Breakpoint,
10486 edit_action: BreakpointEditAction,
10487 cx: &mut Context<Self>,
10488 ) {
10489 let Some(breakpoint_store) = &self.breakpoint_store else {
10490 return;
10491 };
10492
10493 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10494 if breakpoint_position == Anchor::min() {
10495 self.buffer()
10496 .read(cx)
10497 .excerpt_buffer_ids()
10498 .into_iter()
10499 .next()
10500 } else {
10501 None
10502 }
10503 }) else {
10504 return;
10505 };
10506
10507 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10508 return;
10509 };
10510
10511 breakpoint_store.update(cx, |breakpoint_store, cx| {
10512 breakpoint_store.toggle_breakpoint(
10513 buffer,
10514 BreakpointWithPosition {
10515 position: breakpoint_position.text_anchor,
10516 bp: breakpoint,
10517 },
10518 edit_action,
10519 cx,
10520 );
10521 });
10522
10523 cx.notify();
10524 }
10525
10526 #[cfg(any(test, feature = "test-support"))]
10527 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10528 self.breakpoint_store.clone()
10529 }
10530
10531 pub fn prepare_restore_change(
10532 &self,
10533 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10534 hunk: &MultiBufferDiffHunk,
10535 cx: &mut App,
10536 ) -> Option<()> {
10537 if hunk.is_created_file() {
10538 return None;
10539 }
10540 let buffer = self.buffer.read(cx);
10541 let diff = buffer.diff_for(hunk.buffer_id)?;
10542 let buffer = buffer.buffer(hunk.buffer_id)?;
10543 let buffer = buffer.read(cx);
10544 let original_text = diff
10545 .read(cx)
10546 .base_text()
10547 .as_rope()
10548 .slice(hunk.diff_base_byte_range.clone());
10549 let buffer_snapshot = buffer.snapshot();
10550 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10551 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10552 probe
10553 .0
10554 .start
10555 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10556 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10557 }) {
10558 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10559 Some(())
10560 } else {
10561 None
10562 }
10563 }
10564
10565 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10566 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10567 }
10568
10569 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10570 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10571 }
10572
10573 fn manipulate_lines<M>(
10574 &mut self,
10575 window: &mut Window,
10576 cx: &mut Context<Self>,
10577 mut manipulate: M,
10578 ) where
10579 M: FnMut(&str) -> LineManipulationResult,
10580 {
10581 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10582
10583 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10584 let buffer = self.buffer.read(cx).snapshot(cx);
10585
10586 let mut edits = Vec::new();
10587
10588 let selections = self.selections.all::<Point>(cx);
10589 let mut selections = selections.iter().peekable();
10590 let mut contiguous_row_selections = Vec::new();
10591 let mut new_selections = Vec::new();
10592 let mut added_lines = 0;
10593 let mut removed_lines = 0;
10594
10595 while let Some(selection) = selections.next() {
10596 let (start_row, end_row) = consume_contiguous_rows(
10597 &mut contiguous_row_selections,
10598 selection,
10599 &display_map,
10600 &mut selections,
10601 );
10602
10603 let start_point = Point::new(start_row.0, 0);
10604 let end_point = Point::new(
10605 end_row.previous_row().0,
10606 buffer.line_len(end_row.previous_row()),
10607 );
10608 let text = buffer
10609 .text_for_range(start_point..end_point)
10610 .collect::<String>();
10611
10612 let LineManipulationResult {
10613 new_text,
10614 line_count_before,
10615 line_count_after,
10616 } = manipulate(&text);
10617
10618 edits.push((start_point..end_point, new_text));
10619
10620 // Selections must change based on added and removed line count
10621 let start_row =
10622 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10623 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10624 new_selections.push(Selection {
10625 id: selection.id,
10626 start: start_row,
10627 end: end_row,
10628 goal: SelectionGoal::None,
10629 reversed: selection.reversed,
10630 });
10631
10632 if line_count_after > line_count_before {
10633 added_lines += line_count_after - line_count_before;
10634 } else if line_count_before > line_count_after {
10635 removed_lines += line_count_before - line_count_after;
10636 }
10637 }
10638
10639 self.transact(window, cx, |this, window, cx| {
10640 let buffer = this.buffer.update(cx, |buffer, cx| {
10641 buffer.edit(edits, None, cx);
10642 buffer.snapshot(cx)
10643 });
10644
10645 // Recalculate offsets on newly edited buffer
10646 let new_selections = new_selections
10647 .iter()
10648 .map(|s| {
10649 let start_point = Point::new(s.start.0, 0);
10650 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10651 Selection {
10652 id: s.id,
10653 start: buffer.point_to_offset(start_point),
10654 end: buffer.point_to_offset(end_point),
10655 goal: s.goal,
10656 reversed: s.reversed,
10657 }
10658 })
10659 .collect();
10660
10661 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10662 s.select(new_selections);
10663 });
10664
10665 this.request_autoscroll(Autoscroll::fit(), cx);
10666 });
10667 }
10668
10669 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10670 self.manipulate_text(window, cx, |text| {
10671 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10672 if has_upper_case_characters {
10673 text.to_lowercase()
10674 } else {
10675 text.to_uppercase()
10676 }
10677 })
10678 }
10679
10680 fn manipulate_immutable_lines<Fn>(
10681 &mut self,
10682 window: &mut Window,
10683 cx: &mut Context<Self>,
10684 mut callback: Fn,
10685 ) where
10686 Fn: FnMut(&mut Vec<&str>),
10687 {
10688 self.manipulate_lines(window, cx, |text| {
10689 let mut lines: Vec<&str> = text.split('\n').collect();
10690 let line_count_before = lines.len();
10691
10692 callback(&mut lines);
10693
10694 LineManipulationResult {
10695 new_text: lines.join("\n"),
10696 line_count_before,
10697 line_count_after: lines.len(),
10698 }
10699 });
10700 }
10701
10702 fn manipulate_mutable_lines<Fn>(
10703 &mut self,
10704 window: &mut Window,
10705 cx: &mut Context<Self>,
10706 mut callback: Fn,
10707 ) where
10708 Fn: FnMut(&mut Vec<Cow<'_, str>>),
10709 {
10710 self.manipulate_lines(window, cx, |text| {
10711 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
10712 let line_count_before = lines.len();
10713
10714 callback(&mut lines);
10715
10716 LineManipulationResult {
10717 new_text: lines.join("\n"),
10718 line_count_before,
10719 line_count_after: lines.len(),
10720 }
10721 });
10722 }
10723
10724 pub fn convert_indentation_to_spaces(
10725 &mut self,
10726 _: &ConvertIndentationToSpaces,
10727 window: &mut Window,
10728 cx: &mut Context<Self>,
10729 ) {
10730 let settings = self.buffer.read(cx).language_settings(cx);
10731 let tab_size = settings.tab_size.get() as usize;
10732
10733 self.manipulate_mutable_lines(window, cx, |lines| {
10734 // Allocates a reasonably sized scratch buffer once for the whole loop
10735 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10736 // Avoids recomputing spaces that could be inserted many times
10737 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10738 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10739 .collect();
10740
10741 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10742 let mut chars = line.as_ref().chars();
10743 let mut col = 0;
10744 let mut changed = false;
10745
10746 while let Some(ch) = chars.next() {
10747 match ch {
10748 ' ' => {
10749 reindented_line.push(' ');
10750 col += 1;
10751 }
10752 '\t' => {
10753 // \t are converted to spaces depending on the current column
10754 let spaces_len = tab_size - (col % tab_size);
10755 reindented_line.extend(&space_cache[spaces_len - 1]);
10756 col += spaces_len;
10757 changed = true;
10758 }
10759 _ => {
10760 // If we dont append before break, the character is consumed
10761 reindented_line.push(ch);
10762 break;
10763 }
10764 }
10765 }
10766
10767 if !changed {
10768 reindented_line.clear();
10769 continue;
10770 }
10771 // Append the rest of the line and replace old reference with new one
10772 reindented_line.extend(chars);
10773 *line = Cow::Owned(reindented_line.clone());
10774 reindented_line.clear();
10775 }
10776 });
10777 }
10778
10779 pub fn convert_indentation_to_tabs(
10780 &mut self,
10781 _: &ConvertIndentationToTabs,
10782 window: &mut Window,
10783 cx: &mut Context<Self>,
10784 ) {
10785 let settings = self.buffer.read(cx).language_settings(cx);
10786 let tab_size = settings.tab_size.get() as usize;
10787
10788 self.manipulate_mutable_lines(window, cx, |lines| {
10789 // Allocates a reasonably sized buffer once for the whole loop
10790 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10791 // Avoids recomputing spaces that could be inserted many times
10792 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10793 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10794 .collect();
10795
10796 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10797 let mut chars = line.chars();
10798 let mut spaces_count = 0;
10799 let mut first_non_indent_char = None;
10800 let mut changed = false;
10801
10802 while let Some(ch) = chars.next() {
10803 match ch {
10804 ' ' => {
10805 // Keep track of spaces. Append \t when we reach tab_size
10806 spaces_count += 1;
10807 changed = true;
10808 if spaces_count == tab_size {
10809 reindented_line.push('\t');
10810 spaces_count = 0;
10811 }
10812 }
10813 '\t' => {
10814 reindented_line.push('\t');
10815 spaces_count = 0;
10816 }
10817 _ => {
10818 // Dont append it yet, we might have remaining spaces
10819 first_non_indent_char = Some(ch);
10820 break;
10821 }
10822 }
10823 }
10824
10825 if !changed {
10826 reindented_line.clear();
10827 continue;
10828 }
10829 // Remaining spaces that didn't make a full tab stop
10830 if spaces_count > 0 {
10831 reindented_line.extend(&space_cache[spaces_count - 1]);
10832 }
10833 // If we consume an extra character that was not indentation, add it back
10834 if let Some(extra_char) = first_non_indent_char {
10835 reindented_line.push(extra_char);
10836 }
10837 // Append the rest of the line and replace old reference with new one
10838 reindented_line.extend(chars);
10839 *line = Cow::Owned(reindented_line.clone());
10840 reindented_line.clear();
10841 }
10842 });
10843 }
10844
10845 pub fn convert_to_upper_case(
10846 &mut self,
10847 _: &ConvertToUpperCase,
10848 window: &mut Window,
10849 cx: &mut Context<Self>,
10850 ) {
10851 self.manipulate_text(window, cx, |text| text.to_uppercase())
10852 }
10853
10854 pub fn convert_to_lower_case(
10855 &mut self,
10856 _: &ConvertToLowerCase,
10857 window: &mut Window,
10858 cx: &mut Context<Self>,
10859 ) {
10860 self.manipulate_text(window, cx, |text| text.to_lowercase())
10861 }
10862
10863 pub fn convert_to_title_case(
10864 &mut self,
10865 _: &ConvertToTitleCase,
10866 window: &mut Window,
10867 cx: &mut Context<Self>,
10868 ) {
10869 self.manipulate_text(window, cx, |text| {
10870 text.split('\n')
10871 .map(|line| line.to_case(Case::Title))
10872 .join("\n")
10873 })
10874 }
10875
10876 pub fn convert_to_snake_case(
10877 &mut self,
10878 _: &ConvertToSnakeCase,
10879 window: &mut Window,
10880 cx: &mut Context<Self>,
10881 ) {
10882 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10883 }
10884
10885 pub fn convert_to_kebab_case(
10886 &mut self,
10887 _: &ConvertToKebabCase,
10888 window: &mut Window,
10889 cx: &mut Context<Self>,
10890 ) {
10891 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10892 }
10893
10894 pub fn convert_to_upper_camel_case(
10895 &mut self,
10896 _: &ConvertToUpperCamelCase,
10897 window: &mut Window,
10898 cx: &mut Context<Self>,
10899 ) {
10900 self.manipulate_text(window, cx, |text| {
10901 text.split('\n')
10902 .map(|line| line.to_case(Case::UpperCamel))
10903 .join("\n")
10904 })
10905 }
10906
10907 pub fn convert_to_lower_camel_case(
10908 &mut self,
10909 _: &ConvertToLowerCamelCase,
10910 window: &mut Window,
10911 cx: &mut Context<Self>,
10912 ) {
10913 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10914 }
10915
10916 pub fn convert_to_opposite_case(
10917 &mut self,
10918 _: &ConvertToOppositeCase,
10919 window: &mut Window,
10920 cx: &mut Context<Self>,
10921 ) {
10922 self.manipulate_text(window, cx, |text| {
10923 text.chars()
10924 .fold(String::with_capacity(text.len()), |mut t, c| {
10925 if c.is_uppercase() {
10926 t.extend(c.to_lowercase());
10927 } else {
10928 t.extend(c.to_uppercase());
10929 }
10930 t
10931 })
10932 })
10933 }
10934
10935 pub fn convert_to_rot13(
10936 &mut self,
10937 _: &ConvertToRot13,
10938 window: &mut Window,
10939 cx: &mut Context<Self>,
10940 ) {
10941 self.manipulate_text(window, cx, |text| {
10942 text.chars()
10943 .map(|c| match c {
10944 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10945 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10946 _ => c,
10947 })
10948 .collect()
10949 })
10950 }
10951
10952 pub fn convert_to_rot47(
10953 &mut self,
10954 _: &ConvertToRot47,
10955 window: &mut Window,
10956 cx: &mut Context<Self>,
10957 ) {
10958 self.manipulate_text(window, cx, |text| {
10959 text.chars()
10960 .map(|c| {
10961 let code_point = c as u32;
10962 if code_point >= 33 && code_point <= 126 {
10963 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10964 }
10965 c
10966 })
10967 .collect()
10968 })
10969 }
10970
10971 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10972 where
10973 Fn: FnMut(&str) -> String,
10974 {
10975 let buffer = self.buffer.read(cx).snapshot(cx);
10976
10977 let mut new_selections = Vec::new();
10978 let mut edits = Vec::new();
10979 let mut selection_adjustment = 0i32;
10980
10981 for selection in self.selections.all::<usize>(cx) {
10982 let selection_is_empty = selection.is_empty();
10983
10984 let (start, end) = if selection_is_empty {
10985 let (word_range, _) = buffer.surrounding_word(selection.start, false);
10986 (word_range.start, word_range.end)
10987 } else {
10988 (selection.start, selection.end)
10989 };
10990
10991 let text = buffer.text_for_range(start..end).collect::<String>();
10992 let old_length = text.len() as i32;
10993 let text = callback(&text);
10994
10995 new_selections.push(Selection {
10996 start: (start as i32 - selection_adjustment) as usize,
10997 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10998 goal: SelectionGoal::None,
10999 ..selection
11000 });
11001
11002 selection_adjustment += old_length - text.len() as i32;
11003
11004 edits.push((start..end, text));
11005 }
11006
11007 self.transact(window, cx, |this, window, cx| {
11008 this.buffer.update(cx, |buffer, cx| {
11009 buffer.edit(edits, None, cx);
11010 });
11011
11012 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11013 s.select(new_selections);
11014 });
11015
11016 this.request_autoscroll(Autoscroll::fit(), cx);
11017 });
11018 }
11019
11020 pub fn move_selection_on_drop(
11021 &mut self,
11022 selection: &Selection<Anchor>,
11023 target: DisplayPoint,
11024 is_cut: bool,
11025 window: &mut Window,
11026 cx: &mut Context<Self>,
11027 ) {
11028 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11029 let buffer = &display_map.buffer_snapshot;
11030 let mut edits = Vec::new();
11031 let insert_point = display_map
11032 .clip_point(target, Bias::Left)
11033 .to_point(&display_map);
11034 let text = buffer
11035 .text_for_range(selection.start..selection.end)
11036 .collect::<String>();
11037 if is_cut {
11038 edits.push(((selection.start..selection.end), String::new()));
11039 }
11040 let insert_anchor = buffer.anchor_before(insert_point);
11041 edits.push(((insert_anchor..insert_anchor), text));
11042 let last_edit_start = insert_anchor.bias_left(buffer);
11043 let last_edit_end = insert_anchor.bias_right(buffer);
11044 self.transact(window, cx, |this, window, cx| {
11045 this.buffer.update(cx, |buffer, cx| {
11046 buffer.edit(edits, None, cx);
11047 });
11048 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11049 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11050 });
11051 });
11052 }
11053
11054 pub fn clear_selection_drag_state(&mut self) {
11055 self.selection_drag_state = SelectionDragState::None;
11056 }
11057
11058 pub fn duplicate(
11059 &mut self,
11060 upwards: bool,
11061 whole_lines: bool,
11062 window: &mut Window,
11063 cx: &mut Context<Self>,
11064 ) {
11065 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11066
11067 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11068 let buffer = &display_map.buffer_snapshot;
11069 let selections = self.selections.all::<Point>(cx);
11070
11071 let mut edits = Vec::new();
11072 let mut selections_iter = selections.iter().peekable();
11073 while let Some(selection) = selections_iter.next() {
11074 let mut rows = selection.spanned_rows(false, &display_map);
11075 // duplicate line-wise
11076 if whole_lines || selection.start == selection.end {
11077 // Avoid duplicating the same lines twice.
11078 while let Some(next_selection) = selections_iter.peek() {
11079 let next_rows = next_selection.spanned_rows(false, &display_map);
11080 if next_rows.start < rows.end {
11081 rows.end = next_rows.end;
11082 selections_iter.next().unwrap();
11083 } else {
11084 break;
11085 }
11086 }
11087
11088 // Copy the text from the selected row region and splice it either at the start
11089 // or end of the region.
11090 let start = Point::new(rows.start.0, 0);
11091 let end = Point::new(
11092 rows.end.previous_row().0,
11093 buffer.line_len(rows.end.previous_row()),
11094 );
11095 let text = buffer
11096 .text_for_range(start..end)
11097 .chain(Some("\n"))
11098 .collect::<String>();
11099 let insert_location = if upwards {
11100 Point::new(rows.end.0, 0)
11101 } else {
11102 start
11103 };
11104 edits.push((insert_location..insert_location, text));
11105 } else {
11106 // duplicate character-wise
11107 let start = selection.start;
11108 let end = selection.end;
11109 let text = buffer.text_for_range(start..end).collect::<String>();
11110 edits.push((selection.end..selection.end, text));
11111 }
11112 }
11113
11114 self.transact(window, cx, |this, _, cx| {
11115 this.buffer.update(cx, |buffer, cx| {
11116 buffer.edit(edits, None, cx);
11117 });
11118
11119 this.request_autoscroll(Autoscroll::fit(), cx);
11120 });
11121 }
11122
11123 pub fn duplicate_line_up(
11124 &mut self,
11125 _: &DuplicateLineUp,
11126 window: &mut Window,
11127 cx: &mut Context<Self>,
11128 ) {
11129 self.duplicate(true, true, window, cx);
11130 }
11131
11132 pub fn duplicate_line_down(
11133 &mut self,
11134 _: &DuplicateLineDown,
11135 window: &mut Window,
11136 cx: &mut Context<Self>,
11137 ) {
11138 self.duplicate(false, true, window, cx);
11139 }
11140
11141 pub fn duplicate_selection(
11142 &mut self,
11143 _: &DuplicateSelection,
11144 window: &mut Window,
11145 cx: &mut Context<Self>,
11146 ) {
11147 self.duplicate(false, false, window, cx);
11148 }
11149
11150 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11151 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11152 if self.mode.is_single_line() {
11153 cx.propagate();
11154 return;
11155 }
11156
11157 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11158 let buffer = self.buffer.read(cx).snapshot(cx);
11159
11160 let mut edits = Vec::new();
11161 let mut unfold_ranges = Vec::new();
11162 let mut refold_creases = Vec::new();
11163
11164 let selections = self.selections.all::<Point>(cx);
11165 let mut selections = selections.iter().peekable();
11166 let mut contiguous_row_selections = Vec::new();
11167 let mut new_selections = Vec::new();
11168
11169 while let Some(selection) = selections.next() {
11170 // Find all the selections that span a contiguous row range
11171 let (start_row, end_row) = consume_contiguous_rows(
11172 &mut contiguous_row_selections,
11173 selection,
11174 &display_map,
11175 &mut selections,
11176 );
11177
11178 // Move the text spanned by the row range to be before the line preceding the row range
11179 if start_row.0 > 0 {
11180 let range_to_move = Point::new(
11181 start_row.previous_row().0,
11182 buffer.line_len(start_row.previous_row()),
11183 )
11184 ..Point::new(
11185 end_row.previous_row().0,
11186 buffer.line_len(end_row.previous_row()),
11187 );
11188 let insertion_point = display_map
11189 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11190 .0;
11191
11192 // Don't move lines across excerpts
11193 if buffer
11194 .excerpt_containing(insertion_point..range_to_move.end)
11195 .is_some()
11196 {
11197 let text = buffer
11198 .text_for_range(range_to_move.clone())
11199 .flat_map(|s| s.chars())
11200 .skip(1)
11201 .chain(['\n'])
11202 .collect::<String>();
11203
11204 edits.push((
11205 buffer.anchor_after(range_to_move.start)
11206 ..buffer.anchor_before(range_to_move.end),
11207 String::new(),
11208 ));
11209 let insertion_anchor = buffer.anchor_after(insertion_point);
11210 edits.push((insertion_anchor..insertion_anchor, text));
11211
11212 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11213
11214 // Move selections up
11215 new_selections.extend(contiguous_row_selections.drain(..).map(
11216 |mut selection| {
11217 selection.start.row -= row_delta;
11218 selection.end.row -= row_delta;
11219 selection
11220 },
11221 ));
11222
11223 // Move folds up
11224 unfold_ranges.push(range_to_move.clone());
11225 for fold in display_map.folds_in_range(
11226 buffer.anchor_before(range_to_move.start)
11227 ..buffer.anchor_after(range_to_move.end),
11228 ) {
11229 let mut start = fold.range.start.to_point(&buffer);
11230 let mut end = fold.range.end.to_point(&buffer);
11231 start.row -= row_delta;
11232 end.row -= row_delta;
11233 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11234 }
11235 }
11236 }
11237
11238 // If we didn't move line(s), preserve the existing selections
11239 new_selections.append(&mut contiguous_row_selections);
11240 }
11241
11242 self.transact(window, cx, |this, window, cx| {
11243 this.unfold_ranges(&unfold_ranges, true, true, cx);
11244 this.buffer.update(cx, |buffer, cx| {
11245 for (range, text) in edits {
11246 buffer.edit([(range, text)], None, cx);
11247 }
11248 });
11249 this.fold_creases(refold_creases, true, window, cx);
11250 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11251 s.select(new_selections);
11252 })
11253 });
11254 }
11255
11256 pub fn move_line_down(
11257 &mut self,
11258 _: &MoveLineDown,
11259 window: &mut Window,
11260 cx: &mut Context<Self>,
11261 ) {
11262 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11263 if self.mode.is_single_line() {
11264 cx.propagate();
11265 return;
11266 }
11267
11268 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11269 let buffer = self.buffer.read(cx).snapshot(cx);
11270
11271 let mut edits = Vec::new();
11272 let mut unfold_ranges = Vec::new();
11273 let mut refold_creases = Vec::new();
11274
11275 let selections = self.selections.all::<Point>(cx);
11276 let mut selections = selections.iter().peekable();
11277 let mut contiguous_row_selections = Vec::new();
11278 let mut new_selections = Vec::new();
11279
11280 while let Some(selection) = selections.next() {
11281 // Find all the selections that span a contiguous row range
11282 let (start_row, end_row) = consume_contiguous_rows(
11283 &mut contiguous_row_selections,
11284 selection,
11285 &display_map,
11286 &mut selections,
11287 );
11288
11289 // Move the text spanned by the row range to be after the last line of the row range
11290 if end_row.0 <= buffer.max_point().row {
11291 let range_to_move =
11292 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11293 let insertion_point = display_map
11294 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11295 .0;
11296
11297 // Don't move lines across excerpt boundaries
11298 if buffer
11299 .excerpt_containing(range_to_move.start..insertion_point)
11300 .is_some()
11301 {
11302 let mut text = String::from("\n");
11303 text.extend(buffer.text_for_range(range_to_move.clone()));
11304 text.pop(); // Drop trailing newline
11305 edits.push((
11306 buffer.anchor_after(range_to_move.start)
11307 ..buffer.anchor_before(range_to_move.end),
11308 String::new(),
11309 ));
11310 let insertion_anchor = buffer.anchor_after(insertion_point);
11311 edits.push((insertion_anchor..insertion_anchor, text));
11312
11313 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11314
11315 // Move selections down
11316 new_selections.extend(contiguous_row_selections.drain(..).map(
11317 |mut selection| {
11318 selection.start.row += row_delta;
11319 selection.end.row += row_delta;
11320 selection
11321 },
11322 ));
11323
11324 // Move folds down
11325 unfold_ranges.push(range_to_move.clone());
11326 for fold in display_map.folds_in_range(
11327 buffer.anchor_before(range_to_move.start)
11328 ..buffer.anchor_after(range_to_move.end),
11329 ) {
11330 let mut start = fold.range.start.to_point(&buffer);
11331 let mut end = fold.range.end.to_point(&buffer);
11332 start.row += row_delta;
11333 end.row += row_delta;
11334 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11335 }
11336 }
11337 }
11338
11339 // If we didn't move line(s), preserve the existing selections
11340 new_selections.append(&mut contiguous_row_selections);
11341 }
11342
11343 self.transact(window, cx, |this, window, cx| {
11344 this.unfold_ranges(&unfold_ranges, true, true, cx);
11345 this.buffer.update(cx, |buffer, cx| {
11346 for (range, text) in edits {
11347 buffer.edit([(range, text)], None, cx);
11348 }
11349 });
11350 this.fold_creases(refold_creases, true, window, cx);
11351 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11352 s.select(new_selections)
11353 });
11354 });
11355 }
11356
11357 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11358 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11359 let text_layout_details = &self.text_layout_details(window);
11360 self.transact(window, cx, |this, window, cx| {
11361 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11362 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11363 s.move_with(|display_map, selection| {
11364 if !selection.is_empty() {
11365 return;
11366 }
11367
11368 let mut head = selection.head();
11369 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11370 if head.column() == display_map.line_len(head.row()) {
11371 transpose_offset = display_map
11372 .buffer_snapshot
11373 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11374 }
11375
11376 if transpose_offset == 0 {
11377 return;
11378 }
11379
11380 *head.column_mut() += 1;
11381 head = display_map.clip_point(head, Bias::Right);
11382 let goal = SelectionGoal::HorizontalPosition(
11383 display_map
11384 .x_for_display_point(head, text_layout_details)
11385 .into(),
11386 );
11387 selection.collapse_to(head, goal);
11388
11389 let transpose_start = display_map
11390 .buffer_snapshot
11391 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11392 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11393 let transpose_end = display_map
11394 .buffer_snapshot
11395 .clip_offset(transpose_offset + 1, Bias::Right);
11396 if let Some(ch) =
11397 display_map.buffer_snapshot.chars_at(transpose_start).next()
11398 {
11399 edits.push((transpose_start..transpose_offset, String::new()));
11400 edits.push((transpose_end..transpose_end, ch.to_string()));
11401 }
11402 }
11403 });
11404 edits
11405 });
11406 this.buffer
11407 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11408 let selections = this.selections.all::<usize>(cx);
11409 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11410 s.select(selections);
11411 });
11412 });
11413 }
11414
11415 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11416 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11417 if self.mode.is_single_line() {
11418 cx.propagate();
11419 return;
11420 }
11421
11422 self.rewrap_impl(RewrapOptions::default(), cx)
11423 }
11424
11425 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11426 let buffer = self.buffer.read(cx).snapshot(cx);
11427 let selections = self.selections.all::<Point>(cx);
11428
11429 // Shrink and split selections to respect paragraph boundaries.
11430 let ranges = selections.into_iter().flat_map(|selection| {
11431 let language_settings = buffer.language_settings_at(selection.head(), cx);
11432 let language_scope = buffer.language_scope_at(selection.head());
11433
11434 let Some(start_row) = (selection.start.row..=selection.end.row)
11435 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11436 else {
11437 return vec![];
11438 };
11439 let Some(end_row) = (selection.start.row..=selection.end.row)
11440 .rev()
11441 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11442 else {
11443 return vec![];
11444 };
11445
11446 let mut row = start_row;
11447 let mut ranges = Vec::new();
11448 while let Some(blank_row) =
11449 (row..end_row).find(|row| buffer.is_line_blank(MultiBufferRow(*row)))
11450 {
11451 let next_paragraph_start = (blank_row + 1..=end_row)
11452 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11453 .unwrap();
11454 ranges.push((
11455 language_settings.clone(),
11456 language_scope.clone(),
11457 Point::new(row, 0)..Point::new(blank_row - 1, 0),
11458 ));
11459 row = next_paragraph_start;
11460 }
11461 ranges.push((
11462 language_settings.clone(),
11463 language_scope.clone(),
11464 Point::new(row, 0)..Point::new(end_row, 0),
11465 ));
11466
11467 ranges
11468 });
11469
11470 let mut edits = Vec::new();
11471 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11472
11473 for (language_settings, language_scope, range) in ranges {
11474 let mut start_row = range.start.row;
11475 let mut end_row = range.end.row;
11476
11477 // Skip selections that overlap with a range that has already been rewrapped.
11478 let selection_range = start_row..end_row;
11479 if rewrapped_row_ranges
11480 .iter()
11481 .any(|range| range.overlaps(&selection_range))
11482 {
11483 continue;
11484 }
11485
11486 let tab_size = language_settings.tab_size;
11487
11488 // Since not all lines in the selection may be at the same indent
11489 // level, choose the indent size that is the most common between all
11490 // of the lines.
11491 //
11492 // If there is a tie, we use the deepest indent.
11493 let (indent_size, indent_end) = {
11494 let mut indent_size_occurrences = HashMap::default();
11495 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
11496
11497 for row in start_row..=end_row {
11498 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11499 rows_by_indent_size.entry(indent).or_default().push(row);
11500 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
11501 }
11502
11503 let indent_size = indent_size_occurrences
11504 .into_iter()
11505 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
11506 .map(|(indent, _)| indent)
11507 .unwrap_or_default();
11508 let row = rows_by_indent_size[&indent_size][0];
11509 let indent_end = Point::new(row, indent_size.len);
11510
11511 (indent_size, indent_end)
11512 };
11513
11514 let mut line_prefix = indent_size.chars().collect::<String>();
11515
11516 let mut inside_comment = false;
11517 if let Some(comment_prefix) = language_scope.and_then(|language| {
11518 language
11519 .line_comment_prefixes()
11520 .iter()
11521 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11522 .cloned()
11523 }) {
11524 line_prefix.push_str(&comment_prefix);
11525 inside_comment = true;
11526 }
11527
11528 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11529 RewrapBehavior::InComments => inside_comment,
11530 RewrapBehavior::InSelections => !range.is_empty(),
11531 RewrapBehavior::Anywhere => true,
11532 };
11533
11534 let should_rewrap = options.override_language_settings
11535 || allow_rewrap_based_on_language
11536 || self.hard_wrap.is_some();
11537 if !should_rewrap {
11538 continue;
11539 }
11540
11541 if range.is_empty() {
11542 'expand_upwards: while start_row > 0 {
11543 let prev_row = start_row - 1;
11544 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11545 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11546 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11547 {
11548 start_row = prev_row;
11549 } else {
11550 break 'expand_upwards;
11551 }
11552 }
11553
11554 'expand_downwards: while end_row < buffer.max_point().row {
11555 let next_row = end_row + 1;
11556 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11557 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11558 && !buffer.is_line_blank(MultiBufferRow(next_row))
11559 {
11560 end_row = next_row;
11561 } else {
11562 break 'expand_downwards;
11563 }
11564 }
11565 }
11566
11567 let start = Point::new(start_row, 0);
11568 let start_offset = start.to_offset(&buffer);
11569 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11570 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11571 let Some(lines_without_prefixes) = selection_text
11572 .lines()
11573 .map(|line| {
11574 line.strip_prefix(&line_prefix)
11575 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
11576 .with_context(|| {
11577 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11578 })
11579 })
11580 .collect::<Result<Vec<_>, _>>()
11581 .log_err()
11582 else {
11583 continue;
11584 };
11585
11586 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11587 buffer
11588 .language_settings_at(Point::new(start_row, 0), cx)
11589 .preferred_line_length as usize
11590 });
11591 let wrapped_text = wrap_with_prefix(
11592 line_prefix,
11593 lines_without_prefixes.join("\n"),
11594 wrap_column,
11595 tab_size,
11596 options.preserve_existing_whitespace,
11597 );
11598
11599 // TODO: should always use char-based diff while still supporting cursor behavior that
11600 // matches vim.
11601 let mut diff_options = DiffOptions::default();
11602 if options.override_language_settings {
11603 diff_options.max_word_diff_len = 0;
11604 diff_options.max_word_diff_line_count = 0;
11605 } else {
11606 diff_options.max_word_diff_len = usize::MAX;
11607 diff_options.max_word_diff_line_count = usize::MAX;
11608 }
11609
11610 for (old_range, new_text) in
11611 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11612 {
11613 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11614 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11615 edits.push((edit_start..edit_end, new_text));
11616 }
11617
11618 rewrapped_row_ranges.push(start_row..=end_row);
11619 }
11620
11621 self.buffer
11622 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11623 }
11624
11625 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11626 let mut text = String::new();
11627 let buffer = self.buffer.read(cx).snapshot(cx);
11628 let mut selections = self.selections.all::<Point>(cx);
11629 let mut clipboard_selections = Vec::with_capacity(selections.len());
11630 {
11631 let max_point = buffer.max_point();
11632 let mut is_first = true;
11633 for selection in &mut selections {
11634 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11635 if is_entire_line {
11636 selection.start = Point::new(selection.start.row, 0);
11637 if !selection.is_empty() && selection.end.column == 0 {
11638 selection.end = cmp::min(max_point, selection.end);
11639 } else {
11640 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11641 }
11642 selection.goal = SelectionGoal::None;
11643 }
11644 if is_first {
11645 is_first = false;
11646 } else {
11647 text += "\n";
11648 }
11649 let mut len = 0;
11650 for chunk in buffer.text_for_range(selection.start..selection.end) {
11651 text.push_str(chunk);
11652 len += chunk.len();
11653 }
11654 clipboard_selections.push(ClipboardSelection {
11655 len,
11656 is_entire_line,
11657 first_line_indent: buffer
11658 .indent_size_for_line(MultiBufferRow(selection.start.row))
11659 .len,
11660 });
11661 }
11662 }
11663
11664 self.transact(window, cx, |this, window, cx| {
11665 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11666 s.select(selections);
11667 });
11668 this.insert("", window, cx);
11669 });
11670 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11671 }
11672
11673 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11674 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11675 let item = self.cut_common(window, cx);
11676 cx.write_to_clipboard(item);
11677 }
11678
11679 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11680 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11681 self.change_selections(None, window, cx, |s| {
11682 s.move_with(|snapshot, sel| {
11683 if sel.is_empty() {
11684 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11685 }
11686 });
11687 });
11688 let item = self.cut_common(window, cx);
11689 cx.set_global(KillRing(item))
11690 }
11691
11692 pub fn kill_ring_yank(
11693 &mut self,
11694 _: &KillRingYank,
11695 window: &mut Window,
11696 cx: &mut Context<Self>,
11697 ) {
11698 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11699 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11700 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11701 (kill_ring.text().to_string(), kill_ring.metadata_json())
11702 } else {
11703 return;
11704 }
11705 } else {
11706 return;
11707 };
11708 self.do_paste(&text, metadata, false, window, cx);
11709 }
11710
11711 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11712 self.do_copy(true, cx);
11713 }
11714
11715 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11716 self.do_copy(false, cx);
11717 }
11718
11719 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11720 let selections = self.selections.all::<Point>(cx);
11721 let buffer = self.buffer.read(cx).read(cx);
11722 let mut text = String::new();
11723
11724 let mut clipboard_selections = Vec::with_capacity(selections.len());
11725 {
11726 let max_point = buffer.max_point();
11727 let mut is_first = true;
11728 for selection in &selections {
11729 let mut start = selection.start;
11730 let mut end = selection.end;
11731 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11732 if is_entire_line {
11733 start = Point::new(start.row, 0);
11734 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11735 }
11736
11737 let mut trimmed_selections = Vec::new();
11738 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11739 let row = MultiBufferRow(start.row);
11740 let first_indent = buffer.indent_size_for_line(row);
11741 if first_indent.len == 0 || start.column > first_indent.len {
11742 trimmed_selections.push(start..end);
11743 } else {
11744 trimmed_selections.push(
11745 Point::new(row.0, first_indent.len)
11746 ..Point::new(row.0, buffer.line_len(row)),
11747 );
11748 for row in start.row + 1..=end.row {
11749 let mut line_len = buffer.line_len(MultiBufferRow(row));
11750 if row == end.row {
11751 line_len = end.column;
11752 }
11753 if line_len == 0 {
11754 trimmed_selections
11755 .push(Point::new(row, 0)..Point::new(row, line_len));
11756 continue;
11757 }
11758 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11759 if row_indent_size.len >= first_indent.len {
11760 trimmed_selections.push(
11761 Point::new(row, first_indent.len)..Point::new(row, line_len),
11762 );
11763 } else {
11764 trimmed_selections.clear();
11765 trimmed_selections.push(start..end);
11766 break;
11767 }
11768 }
11769 }
11770 } else {
11771 trimmed_selections.push(start..end);
11772 }
11773
11774 for trimmed_range in trimmed_selections {
11775 if is_first {
11776 is_first = false;
11777 } else {
11778 text += "\n";
11779 }
11780 let mut len = 0;
11781 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11782 text.push_str(chunk);
11783 len += chunk.len();
11784 }
11785 clipboard_selections.push(ClipboardSelection {
11786 len,
11787 is_entire_line,
11788 first_line_indent: buffer
11789 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11790 .len,
11791 });
11792 }
11793 }
11794 }
11795
11796 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11797 text,
11798 clipboard_selections,
11799 ));
11800 }
11801
11802 pub fn do_paste(
11803 &mut self,
11804 text: &String,
11805 clipboard_selections: Option<Vec<ClipboardSelection>>,
11806 handle_entire_lines: bool,
11807 window: &mut Window,
11808 cx: &mut Context<Self>,
11809 ) {
11810 if self.read_only(cx) {
11811 return;
11812 }
11813
11814 let clipboard_text = Cow::Borrowed(text);
11815
11816 self.transact(window, cx, |this, window, cx| {
11817 if let Some(mut clipboard_selections) = clipboard_selections {
11818 let old_selections = this.selections.all::<usize>(cx);
11819 let all_selections_were_entire_line =
11820 clipboard_selections.iter().all(|s| s.is_entire_line);
11821 let first_selection_indent_column =
11822 clipboard_selections.first().map(|s| s.first_line_indent);
11823 if clipboard_selections.len() != old_selections.len() {
11824 clipboard_selections.drain(..);
11825 }
11826 let cursor_offset = this.selections.last::<usize>(cx).head();
11827 let mut auto_indent_on_paste = true;
11828
11829 this.buffer.update(cx, |buffer, cx| {
11830 let snapshot = buffer.read(cx);
11831 auto_indent_on_paste = snapshot
11832 .language_settings_at(cursor_offset, cx)
11833 .auto_indent_on_paste;
11834
11835 let mut start_offset = 0;
11836 let mut edits = Vec::new();
11837 let mut original_indent_columns = Vec::new();
11838 for (ix, selection) in old_selections.iter().enumerate() {
11839 let to_insert;
11840 let entire_line;
11841 let original_indent_column;
11842 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11843 let end_offset = start_offset + clipboard_selection.len;
11844 to_insert = &clipboard_text[start_offset..end_offset];
11845 entire_line = clipboard_selection.is_entire_line;
11846 start_offset = end_offset + 1;
11847 original_indent_column = Some(clipboard_selection.first_line_indent);
11848 } else {
11849 to_insert = clipboard_text.as_str();
11850 entire_line = all_selections_were_entire_line;
11851 original_indent_column = first_selection_indent_column
11852 }
11853
11854 // If the corresponding selection was empty when this slice of the
11855 // clipboard text was written, then the entire line containing the
11856 // selection was copied. If this selection is also currently empty,
11857 // then paste the line before the current line of the buffer.
11858 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11859 let column = selection.start.to_point(&snapshot).column as usize;
11860 let line_start = selection.start - column;
11861 line_start..line_start
11862 } else {
11863 selection.range()
11864 };
11865
11866 edits.push((range, to_insert));
11867 original_indent_columns.push(original_indent_column);
11868 }
11869 drop(snapshot);
11870
11871 buffer.edit(
11872 edits,
11873 if auto_indent_on_paste {
11874 Some(AutoindentMode::Block {
11875 original_indent_columns,
11876 })
11877 } else {
11878 None
11879 },
11880 cx,
11881 );
11882 });
11883
11884 let selections = this.selections.all::<usize>(cx);
11885 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11886 s.select(selections)
11887 });
11888 } else {
11889 this.insert(&clipboard_text, window, cx);
11890 }
11891 });
11892 }
11893
11894 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11895 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11896 if let Some(item) = cx.read_from_clipboard() {
11897 let entries = item.entries();
11898
11899 match entries.first() {
11900 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11901 // of all the pasted entries.
11902 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11903 .do_paste(
11904 clipboard_string.text(),
11905 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11906 true,
11907 window,
11908 cx,
11909 ),
11910 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11911 }
11912 }
11913 }
11914
11915 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11916 if self.read_only(cx) {
11917 return;
11918 }
11919
11920 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11921
11922 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11923 if let Some((selections, _)) =
11924 self.selection_history.transaction(transaction_id).cloned()
11925 {
11926 self.change_selections(None, window, cx, |s| {
11927 s.select_anchors(selections.to_vec());
11928 });
11929 } else {
11930 log::error!(
11931 "No entry in selection_history found for undo. \
11932 This may correspond to a bug where undo does not update the selection. \
11933 If this is occurring, please add details to \
11934 https://github.com/zed-industries/zed/issues/22692"
11935 );
11936 }
11937 self.request_autoscroll(Autoscroll::fit(), cx);
11938 self.unmark_text(window, cx);
11939 self.refresh_inline_completion(true, false, window, cx);
11940 cx.emit(EditorEvent::Edited { transaction_id });
11941 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11942 }
11943 }
11944
11945 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11946 if self.read_only(cx) {
11947 return;
11948 }
11949
11950 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11951
11952 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11953 if let Some((_, Some(selections))) =
11954 self.selection_history.transaction(transaction_id).cloned()
11955 {
11956 self.change_selections(None, window, cx, |s| {
11957 s.select_anchors(selections.to_vec());
11958 });
11959 } else {
11960 log::error!(
11961 "No entry in selection_history found for redo. \
11962 This may correspond to a bug where undo does not update the selection. \
11963 If this is occurring, please add details to \
11964 https://github.com/zed-industries/zed/issues/22692"
11965 );
11966 }
11967 self.request_autoscroll(Autoscroll::fit(), cx);
11968 self.unmark_text(window, cx);
11969 self.refresh_inline_completion(true, false, window, cx);
11970 cx.emit(EditorEvent::Edited { transaction_id });
11971 }
11972 }
11973
11974 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11975 self.buffer
11976 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11977 }
11978
11979 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11980 self.buffer
11981 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11982 }
11983
11984 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11985 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11986 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11987 s.move_with(|map, selection| {
11988 let cursor = if selection.is_empty() {
11989 movement::left(map, selection.start)
11990 } else {
11991 selection.start
11992 };
11993 selection.collapse_to(cursor, SelectionGoal::None);
11994 });
11995 })
11996 }
11997
11998 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11999 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12000 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12001 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12002 })
12003 }
12004
12005 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12006 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12007 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12008 s.move_with(|map, selection| {
12009 let cursor = if selection.is_empty() {
12010 movement::right(map, selection.end)
12011 } else {
12012 selection.end
12013 };
12014 selection.collapse_to(cursor, SelectionGoal::None)
12015 });
12016 })
12017 }
12018
12019 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12020 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12021 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12022 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12023 })
12024 }
12025
12026 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12027 if self.take_rename(true, window, cx).is_some() {
12028 return;
12029 }
12030
12031 if self.mode.is_single_line() {
12032 cx.propagate();
12033 return;
12034 }
12035
12036 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12037
12038 let text_layout_details = &self.text_layout_details(window);
12039 let selection_count = self.selections.count();
12040 let first_selection = self.selections.first_anchor();
12041
12042 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12043 s.move_with(|map, selection| {
12044 if !selection.is_empty() {
12045 selection.goal = SelectionGoal::None;
12046 }
12047 let (cursor, goal) = movement::up(
12048 map,
12049 selection.start,
12050 selection.goal,
12051 false,
12052 text_layout_details,
12053 );
12054 selection.collapse_to(cursor, goal);
12055 });
12056 });
12057
12058 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12059 {
12060 cx.propagate();
12061 }
12062 }
12063
12064 pub fn move_up_by_lines(
12065 &mut self,
12066 action: &MoveUpByLines,
12067 window: &mut Window,
12068 cx: &mut Context<Self>,
12069 ) {
12070 if self.take_rename(true, window, cx).is_some() {
12071 return;
12072 }
12073
12074 if self.mode.is_single_line() {
12075 cx.propagate();
12076 return;
12077 }
12078
12079 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12080
12081 let text_layout_details = &self.text_layout_details(window);
12082
12083 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12084 s.move_with(|map, selection| {
12085 if !selection.is_empty() {
12086 selection.goal = SelectionGoal::None;
12087 }
12088 let (cursor, goal) = movement::up_by_rows(
12089 map,
12090 selection.start,
12091 action.lines,
12092 selection.goal,
12093 false,
12094 text_layout_details,
12095 );
12096 selection.collapse_to(cursor, goal);
12097 });
12098 })
12099 }
12100
12101 pub fn move_down_by_lines(
12102 &mut self,
12103 action: &MoveDownByLines,
12104 window: &mut Window,
12105 cx: &mut Context<Self>,
12106 ) {
12107 if self.take_rename(true, window, cx).is_some() {
12108 return;
12109 }
12110
12111 if self.mode.is_single_line() {
12112 cx.propagate();
12113 return;
12114 }
12115
12116 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12117
12118 let text_layout_details = &self.text_layout_details(window);
12119
12120 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12121 s.move_with(|map, selection| {
12122 if !selection.is_empty() {
12123 selection.goal = SelectionGoal::None;
12124 }
12125 let (cursor, goal) = movement::down_by_rows(
12126 map,
12127 selection.start,
12128 action.lines,
12129 selection.goal,
12130 false,
12131 text_layout_details,
12132 );
12133 selection.collapse_to(cursor, goal);
12134 });
12135 })
12136 }
12137
12138 pub fn select_down_by_lines(
12139 &mut self,
12140 action: &SelectDownByLines,
12141 window: &mut Window,
12142 cx: &mut Context<Self>,
12143 ) {
12144 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12145 let text_layout_details = &self.text_layout_details(window);
12146 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12147 s.move_heads_with(|map, head, goal| {
12148 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12149 })
12150 })
12151 }
12152
12153 pub fn select_up_by_lines(
12154 &mut self,
12155 action: &SelectUpByLines,
12156 window: &mut Window,
12157 cx: &mut Context<Self>,
12158 ) {
12159 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12160 let text_layout_details = &self.text_layout_details(window);
12161 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12162 s.move_heads_with(|map, head, goal| {
12163 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12164 })
12165 })
12166 }
12167
12168 pub fn select_page_up(
12169 &mut self,
12170 _: &SelectPageUp,
12171 window: &mut Window,
12172 cx: &mut Context<Self>,
12173 ) {
12174 let Some(row_count) = self.visible_row_count() else {
12175 return;
12176 };
12177
12178 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12179
12180 let text_layout_details = &self.text_layout_details(window);
12181
12182 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12183 s.move_heads_with(|map, head, goal| {
12184 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12185 })
12186 })
12187 }
12188
12189 pub fn move_page_up(
12190 &mut self,
12191 action: &MovePageUp,
12192 window: &mut Window,
12193 cx: &mut Context<Self>,
12194 ) {
12195 if self.take_rename(true, window, cx).is_some() {
12196 return;
12197 }
12198
12199 if self
12200 .context_menu
12201 .borrow_mut()
12202 .as_mut()
12203 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12204 .unwrap_or(false)
12205 {
12206 return;
12207 }
12208
12209 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12210 cx.propagate();
12211 return;
12212 }
12213
12214 let Some(row_count) = self.visible_row_count() else {
12215 return;
12216 };
12217
12218 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12219
12220 let autoscroll = if action.center_cursor {
12221 Autoscroll::center()
12222 } else {
12223 Autoscroll::fit()
12224 };
12225
12226 let text_layout_details = &self.text_layout_details(window);
12227
12228 self.change_selections(Some(autoscroll), window, cx, |s| {
12229 s.move_with(|map, selection| {
12230 if !selection.is_empty() {
12231 selection.goal = SelectionGoal::None;
12232 }
12233 let (cursor, goal) = movement::up_by_rows(
12234 map,
12235 selection.end,
12236 row_count,
12237 selection.goal,
12238 false,
12239 text_layout_details,
12240 );
12241 selection.collapse_to(cursor, goal);
12242 });
12243 });
12244 }
12245
12246 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12247 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12248 let text_layout_details = &self.text_layout_details(window);
12249 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12250 s.move_heads_with(|map, head, goal| {
12251 movement::up(map, head, goal, false, text_layout_details)
12252 })
12253 })
12254 }
12255
12256 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12257 self.take_rename(true, window, cx);
12258
12259 if self.mode.is_single_line() {
12260 cx.propagate();
12261 return;
12262 }
12263
12264 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12265
12266 let text_layout_details = &self.text_layout_details(window);
12267 let selection_count = self.selections.count();
12268 let first_selection = self.selections.first_anchor();
12269
12270 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12271 s.move_with(|map, selection| {
12272 if !selection.is_empty() {
12273 selection.goal = SelectionGoal::None;
12274 }
12275 let (cursor, goal) = movement::down(
12276 map,
12277 selection.end,
12278 selection.goal,
12279 false,
12280 text_layout_details,
12281 );
12282 selection.collapse_to(cursor, goal);
12283 });
12284 });
12285
12286 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12287 {
12288 cx.propagate();
12289 }
12290 }
12291
12292 pub fn select_page_down(
12293 &mut self,
12294 _: &SelectPageDown,
12295 window: &mut Window,
12296 cx: &mut Context<Self>,
12297 ) {
12298 let Some(row_count) = self.visible_row_count() else {
12299 return;
12300 };
12301
12302 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12303
12304 let text_layout_details = &self.text_layout_details(window);
12305
12306 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12307 s.move_heads_with(|map, head, goal| {
12308 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12309 })
12310 })
12311 }
12312
12313 pub fn move_page_down(
12314 &mut self,
12315 action: &MovePageDown,
12316 window: &mut Window,
12317 cx: &mut Context<Self>,
12318 ) {
12319 if self.take_rename(true, window, cx).is_some() {
12320 return;
12321 }
12322
12323 if self
12324 .context_menu
12325 .borrow_mut()
12326 .as_mut()
12327 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12328 .unwrap_or(false)
12329 {
12330 return;
12331 }
12332
12333 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12334 cx.propagate();
12335 return;
12336 }
12337
12338 let Some(row_count) = self.visible_row_count() else {
12339 return;
12340 };
12341
12342 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12343
12344 let autoscroll = if action.center_cursor {
12345 Autoscroll::center()
12346 } else {
12347 Autoscroll::fit()
12348 };
12349
12350 let text_layout_details = &self.text_layout_details(window);
12351 self.change_selections(Some(autoscroll), window, cx, |s| {
12352 s.move_with(|map, selection| {
12353 if !selection.is_empty() {
12354 selection.goal = SelectionGoal::None;
12355 }
12356 let (cursor, goal) = movement::down_by_rows(
12357 map,
12358 selection.end,
12359 row_count,
12360 selection.goal,
12361 false,
12362 text_layout_details,
12363 );
12364 selection.collapse_to(cursor, goal);
12365 });
12366 });
12367 }
12368
12369 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12370 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12371 let text_layout_details = &self.text_layout_details(window);
12372 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12373 s.move_heads_with(|map, head, goal| {
12374 movement::down(map, head, goal, false, text_layout_details)
12375 })
12376 });
12377 }
12378
12379 pub fn context_menu_first(
12380 &mut self,
12381 _: &ContextMenuFirst,
12382 window: &mut Window,
12383 cx: &mut Context<Self>,
12384 ) {
12385 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12386 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12387 }
12388 }
12389
12390 pub fn context_menu_prev(
12391 &mut self,
12392 _: &ContextMenuPrevious,
12393 window: &mut Window,
12394 cx: &mut Context<Self>,
12395 ) {
12396 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12397 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12398 }
12399 }
12400
12401 pub fn context_menu_next(
12402 &mut self,
12403 _: &ContextMenuNext,
12404 window: &mut Window,
12405 cx: &mut Context<Self>,
12406 ) {
12407 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12408 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12409 }
12410 }
12411
12412 pub fn context_menu_last(
12413 &mut self,
12414 _: &ContextMenuLast,
12415 window: &mut Window,
12416 cx: &mut Context<Self>,
12417 ) {
12418 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12419 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12420 }
12421 }
12422
12423 pub fn move_to_previous_word_start(
12424 &mut self,
12425 _: &MoveToPreviousWordStart,
12426 window: &mut Window,
12427 cx: &mut Context<Self>,
12428 ) {
12429 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12430 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12431 s.move_cursors_with(|map, head, _| {
12432 (
12433 movement::previous_word_start(map, head),
12434 SelectionGoal::None,
12435 )
12436 });
12437 })
12438 }
12439
12440 pub fn move_to_previous_subword_start(
12441 &mut self,
12442 _: &MoveToPreviousSubwordStart,
12443 window: &mut Window,
12444 cx: &mut Context<Self>,
12445 ) {
12446 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12447 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12448 s.move_cursors_with(|map, head, _| {
12449 (
12450 movement::previous_subword_start(map, head),
12451 SelectionGoal::None,
12452 )
12453 });
12454 })
12455 }
12456
12457 pub fn select_to_previous_word_start(
12458 &mut self,
12459 _: &SelectToPreviousWordStart,
12460 window: &mut Window,
12461 cx: &mut Context<Self>,
12462 ) {
12463 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12464 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12465 s.move_heads_with(|map, head, _| {
12466 (
12467 movement::previous_word_start(map, head),
12468 SelectionGoal::None,
12469 )
12470 });
12471 })
12472 }
12473
12474 pub fn select_to_previous_subword_start(
12475 &mut self,
12476 _: &SelectToPreviousSubwordStart,
12477 window: &mut Window,
12478 cx: &mut Context<Self>,
12479 ) {
12480 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12481 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12482 s.move_heads_with(|map, head, _| {
12483 (
12484 movement::previous_subword_start(map, head),
12485 SelectionGoal::None,
12486 )
12487 });
12488 })
12489 }
12490
12491 pub fn delete_to_previous_word_start(
12492 &mut self,
12493 action: &DeleteToPreviousWordStart,
12494 window: &mut Window,
12495 cx: &mut Context<Self>,
12496 ) {
12497 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12498 self.transact(window, cx, |this, window, cx| {
12499 this.select_autoclose_pair(window, cx);
12500 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12501 s.move_with(|map, selection| {
12502 if selection.is_empty() {
12503 let cursor = if action.ignore_newlines {
12504 movement::previous_word_start(map, selection.head())
12505 } else {
12506 movement::previous_word_start_or_newline(map, selection.head())
12507 };
12508 selection.set_head(cursor, SelectionGoal::None);
12509 }
12510 });
12511 });
12512 this.insert("", window, cx);
12513 });
12514 }
12515
12516 pub fn delete_to_previous_subword_start(
12517 &mut self,
12518 _: &DeleteToPreviousSubwordStart,
12519 window: &mut Window,
12520 cx: &mut Context<Self>,
12521 ) {
12522 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12523 self.transact(window, cx, |this, window, cx| {
12524 this.select_autoclose_pair(window, cx);
12525 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12526 s.move_with(|map, selection| {
12527 if selection.is_empty() {
12528 let cursor = movement::previous_subword_start(map, selection.head());
12529 selection.set_head(cursor, SelectionGoal::None);
12530 }
12531 });
12532 });
12533 this.insert("", window, cx);
12534 });
12535 }
12536
12537 pub fn move_to_next_word_end(
12538 &mut self,
12539 _: &MoveToNextWordEnd,
12540 window: &mut Window,
12541 cx: &mut Context<Self>,
12542 ) {
12543 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12544 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12545 s.move_cursors_with(|map, head, _| {
12546 (movement::next_word_end(map, head), SelectionGoal::None)
12547 });
12548 })
12549 }
12550
12551 pub fn move_to_next_subword_end(
12552 &mut self,
12553 _: &MoveToNextSubwordEnd,
12554 window: &mut Window,
12555 cx: &mut Context<Self>,
12556 ) {
12557 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12558 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12559 s.move_cursors_with(|map, head, _| {
12560 (movement::next_subword_end(map, head), SelectionGoal::None)
12561 });
12562 })
12563 }
12564
12565 pub fn select_to_next_word_end(
12566 &mut self,
12567 _: &SelectToNextWordEnd,
12568 window: &mut Window,
12569 cx: &mut Context<Self>,
12570 ) {
12571 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12572 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12573 s.move_heads_with(|map, head, _| {
12574 (movement::next_word_end(map, head), SelectionGoal::None)
12575 });
12576 })
12577 }
12578
12579 pub fn select_to_next_subword_end(
12580 &mut self,
12581 _: &SelectToNextSubwordEnd,
12582 window: &mut Window,
12583 cx: &mut Context<Self>,
12584 ) {
12585 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12586 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12587 s.move_heads_with(|map, head, _| {
12588 (movement::next_subword_end(map, head), SelectionGoal::None)
12589 });
12590 })
12591 }
12592
12593 pub fn delete_to_next_word_end(
12594 &mut self,
12595 action: &DeleteToNextWordEnd,
12596 window: &mut Window,
12597 cx: &mut Context<Self>,
12598 ) {
12599 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12600 self.transact(window, cx, |this, window, cx| {
12601 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12602 s.move_with(|map, selection| {
12603 if selection.is_empty() {
12604 let cursor = if action.ignore_newlines {
12605 movement::next_word_end(map, selection.head())
12606 } else {
12607 movement::next_word_end_or_newline(map, selection.head())
12608 };
12609 selection.set_head(cursor, SelectionGoal::None);
12610 }
12611 });
12612 });
12613 this.insert("", window, cx);
12614 });
12615 }
12616
12617 pub fn delete_to_next_subword_end(
12618 &mut self,
12619 _: &DeleteToNextSubwordEnd,
12620 window: &mut Window,
12621 cx: &mut Context<Self>,
12622 ) {
12623 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12624 self.transact(window, cx, |this, window, cx| {
12625 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12626 s.move_with(|map, selection| {
12627 if selection.is_empty() {
12628 let cursor = movement::next_subword_end(map, selection.head());
12629 selection.set_head(cursor, SelectionGoal::None);
12630 }
12631 });
12632 });
12633 this.insert("", window, cx);
12634 });
12635 }
12636
12637 pub fn move_to_beginning_of_line(
12638 &mut self,
12639 action: &MoveToBeginningOfLine,
12640 window: &mut Window,
12641 cx: &mut Context<Self>,
12642 ) {
12643 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12644 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12645 s.move_cursors_with(|map, head, _| {
12646 (
12647 movement::indented_line_beginning(
12648 map,
12649 head,
12650 action.stop_at_soft_wraps,
12651 action.stop_at_indent,
12652 ),
12653 SelectionGoal::None,
12654 )
12655 });
12656 })
12657 }
12658
12659 pub fn select_to_beginning_of_line(
12660 &mut self,
12661 action: &SelectToBeginningOfLine,
12662 window: &mut Window,
12663 cx: &mut Context<Self>,
12664 ) {
12665 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12666 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12667 s.move_heads_with(|map, head, _| {
12668 (
12669 movement::indented_line_beginning(
12670 map,
12671 head,
12672 action.stop_at_soft_wraps,
12673 action.stop_at_indent,
12674 ),
12675 SelectionGoal::None,
12676 )
12677 });
12678 });
12679 }
12680
12681 pub fn delete_to_beginning_of_line(
12682 &mut self,
12683 action: &DeleteToBeginningOfLine,
12684 window: &mut Window,
12685 cx: &mut Context<Self>,
12686 ) {
12687 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12688 self.transact(window, cx, |this, window, cx| {
12689 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12690 s.move_with(|_, selection| {
12691 selection.reversed = true;
12692 });
12693 });
12694
12695 this.select_to_beginning_of_line(
12696 &SelectToBeginningOfLine {
12697 stop_at_soft_wraps: false,
12698 stop_at_indent: action.stop_at_indent,
12699 },
12700 window,
12701 cx,
12702 );
12703 this.backspace(&Backspace, window, cx);
12704 });
12705 }
12706
12707 pub fn move_to_end_of_line(
12708 &mut self,
12709 action: &MoveToEndOfLine,
12710 window: &mut Window,
12711 cx: &mut Context<Self>,
12712 ) {
12713 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12714 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12715 s.move_cursors_with(|map, head, _| {
12716 (
12717 movement::line_end(map, head, action.stop_at_soft_wraps),
12718 SelectionGoal::None,
12719 )
12720 });
12721 })
12722 }
12723
12724 pub fn select_to_end_of_line(
12725 &mut self,
12726 action: &SelectToEndOfLine,
12727 window: &mut Window,
12728 cx: &mut Context<Self>,
12729 ) {
12730 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12731 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12732 s.move_heads_with(|map, head, _| {
12733 (
12734 movement::line_end(map, head, action.stop_at_soft_wraps),
12735 SelectionGoal::None,
12736 )
12737 });
12738 })
12739 }
12740
12741 pub fn delete_to_end_of_line(
12742 &mut self,
12743 _: &DeleteToEndOfLine,
12744 window: &mut Window,
12745 cx: &mut Context<Self>,
12746 ) {
12747 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12748 self.transact(window, cx, |this, window, cx| {
12749 this.select_to_end_of_line(
12750 &SelectToEndOfLine {
12751 stop_at_soft_wraps: false,
12752 },
12753 window,
12754 cx,
12755 );
12756 this.delete(&Delete, window, cx);
12757 });
12758 }
12759
12760 pub fn cut_to_end_of_line(
12761 &mut self,
12762 _: &CutToEndOfLine,
12763 window: &mut Window,
12764 cx: &mut Context<Self>,
12765 ) {
12766 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12767 self.transact(window, cx, |this, window, cx| {
12768 this.select_to_end_of_line(
12769 &SelectToEndOfLine {
12770 stop_at_soft_wraps: false,
12771 },
12772 window,
12773 cx,
12774 );
12775 this.cut(&Cut, window, cx);
12776 });
12777 }
12778
12779 pub fn move_to_start_of_paragraph(
12780 &mut self,
12781 _: &MoveToStartOfParagraph,
12782 window: &mut Window,
12783 cx: &mut Context<Self>,
12784 ) {
12785 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12786 cx.propagate();
12787 return;
12788 }
12789 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12790 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12791 s.move_with(|map, selection| {
12792 selection.collapse_to(
12793 movement::start_of_paragraph(map, selection.head(), 1),
12794 SelectionGoal::None,
12795 )
12796 });
12797 })
12798 }
12799
12800 pub fn move_to_end_of_paragraph(
12801 &mut self,
12802 _: &MoveToEndOfParagraph,
12803 window: &mut Window,
12804 cx: &mut Context<Self>,
12805 ) {
12806 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12807 cx.propagate();
12808 return;
12809 }
12810 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12811 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12812 s.move_with(|map, selection| {
12813 selection.collapse_to(
12814 movement::end_of_paragraph(map, selection.head(), 1),
12815 SelectionGoal::None,
12816 )
12817 });
12818 })
12819 }
12820
12821 pub fn select_to_start_of_paragraph(
12822 &mut self,
12823 _: &SelectToStartOfParagraph,
12824 window: &mut Window,
12825 cx: &mut Context<Self>,
12826 ) {
12827 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12828 cx.propagate();
12829 return;
12830 }
12831 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12832 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12833 s.move_heads_with(|map, head, _| {
12834 (
12835 movement::start_of_paragraph(map, head, 1),
12836 SelectionGoal::None,
12837 )
12838 });
12839 })
12840 }
12841
12842 pub fn select_to_end_of_paragraph(
12843 &mut self,
12844 _: &SelectToEndOfParagraph,
12845 window: &mut Window,
12846 cx: &mut Context<Self>,
12847 ) {
12848 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12849 cx.propagate();
12850 return;
12851 }
12852 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12853 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12854 s.move_heads_with(|map, head, _| {
12855 (
12856 movement::end_of_paragraph(map, head, 1),
12857 SelectionGoal::None,
12858 )
12859 });
12860 })
12861 }
12862
12863 pub fn move_to_start_of_excerpt(
12864 &mut self,
12865 _: &MoveToStartOfExcerpt,
12866 window: &mut Window,
12867 cx: &mut Context<Self>,
12868 ) {
12869 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12870 cx.propagate();
12871 return;
12872 }
12873 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12874 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12875 s.move_with(|map, selection| {
12876 selection.collapse_to(
12877 movement::start_of_excerpt(
12878 map,
12879 selection.head(),
12880 workspace::searchable::Direction::Prev,
12881 ),
12882 SelectionGoal::None,
12883 )
12884 });
12885 })
12886 }
12887
12888 pub fn move_to_start_of_next_excerpt(
12889 &mut self,
12890 _: &MoveToStartOfNextExcerpt,
12891 window: &mut Window,
12892 cx: &mut Context<Self>,
12893 ) {
12894 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12895 cx.propagate();
12896 return;
12897 }
12898
12899 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12900 s.move_with(|map, selection| {
12901 selection.collapse_to(
12902 movement::start_of_excerpt(
12903 map,
12904 selection.head(),
12905 workspace::searchable::Direction::Next,
12906 ),
12907 SelectionGoal::None,
12908 )
12909 });
12910 })
12911 }
12912
12913 pub fn move_to_end_of_excerpt(
12914 &mut self,
12915 _: &MoveToEndOfExcerpt,
12916 window: &mut Window,
12917 cx: &mut Context<Self>,
12918 ) {
12919 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12920 cx.propagate();
12921 return;
12922 }
12923 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12924 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12925 s.move_with(|map, selection| {
12926 selection.collapse_to(
12927 movement::end_of_excerpt(
12928 map,
12929 selection.head(),
12930 workspace::searchable::Direction::Next,
12931 ),
12932 SelectionGoal::None,
12933 )
12934 });
12935 })
12936 }
12937
12938 pub fn move_to_end_of_previous_excerpt(
12939 &mut self,
12940 _: &MoveToEndOfPreviousExcerpt,
12941 window: &mut Window,
12942 cx: &mut Context<Self>,
12943 ) {
12944 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12945 cx.propagate();
12946 return;
12947 }
12948 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12949 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12950 s.move_with(|map, selection| {
12951 selection.collapse_to(
12952 movement::end_of_excerpt(
12953 map,
12954 selection.head(),
12955 workspace::searchable::Direction::Prev,
12956 ),
12957 SelectionGoal::None,
12958 )
12959 });
12960 })
12961 }
12962
12963 pub fn select_to_start_of_excerpt(
12964 &mut self,
12965 _: &SelectToStartOfExcerpt,
12966 window: &mut Window,
12967 cx: &mut Context<Self>,
12968 ) {
12969 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12970 cx.propagate();
12971 return;
12972 }
12973 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12974 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12975 s.move_heads_with(|map, head, _| {
12976 (
12977 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12978 SelectionGoal::None,
12979 )
12980 });
12981 })
12982 }
12983
12984 pub fn select_to_start_of_next_excerpt(
12985 &mut self,
12986 _: &SelectToStartOfNextExcerpt,
12987 window: &mut Window,
12988 cx: &mut Context<Self>,
12989 ) {
12990 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12991 cx.propagate();
12992 return;
12993 }
12994 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12995 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12996 s.move_heads_with(|map, head, _| {
12997 (
12998 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12999 SelectionGoal::None,
13000 )
13001 });
13002 })
13003 }
13004
13005 pub fn select_to_end_of_excerpt(
13006 &mut self,
13007 _: &SelectToEndOfExcerpt,
13008 window: &mut Window,
13009 cx: &mut Context<Self>,
13010 ) {
13011 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13012 cx.propagate();
13013 return;
13014 }
13015 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13016 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13017 s.move_heads_with(|map, head, _| {
13018 (
13019 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13020 SelectionGoal::None,
13021 )
13022 });
13023 })
13024 }
13025
13026 pub fn select_to_end_of_previous_excerpt(
13027 &mut self,
13028 _: &SelectToEndOfPreviousExcerpt,
13029 window: &mut Window,
13030 cx: &mut Context<Self>,
13031 ) {
13032 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13033 cx.propagate();
13034 return;
13035 }
13036 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13037 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13038 s.move_heads_with(|map, head, _| {
13039 (
13040 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13041 SelectionGoal::None,
13042 )
13043 });
13044 })
13045 }
13046
13047 pub fn move_to_beginning(
13048 &mut self,
13049 _: &MoveToBeginning,
13050 window: &mut Window,
13051 cx: &mut Context<Self>,
13052 ) {
13053 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13054 cx.propagate();
13055 return;
13056 }
13057 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13058 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13059 s.select_ranges(vec![0..0]);
13060 });
13061 }
13062
13063 pub fn select_to_beginning(
13064 &mut self,
13065 _: &SelectToBeginning,
13066 window: &mut Window,
13067 cx: &mut Context<Self>,
13068 ) {
13069 let mut selection = self.selections.last::<Point>(cx);
13070 selection.set_head(Point::zero(), SelectionGoal::None);
13071 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13072 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13073 s.select(vec![selection]);
13074 });
13075 }
13076
13077 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13078 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13079 cx.propagate();
13080 return;
13081 }
13082 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13083 let cursor = self.buffer.read(cx).read(cx).len();
13084 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13085 s.select_ranges(vec![cursor..cursor])
13086 });
13087 }
13088
13089 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13090 self.nav_history = nav_history;
13091 }
13092
13093 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13094 self.nav_history.as_ref()
13095 }
13096
13097 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13098 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
13099 }
13100
13101 fn push_to_nav_history(
13102 &mut self,
13103 cursor_anchor: Anchor,
13104 new_position: Option<Point>,
13105 is_deactivate: bool,
13106 cx: &mut Context<Self>,
13107 ) {
13108 if let Some(nav_history) = self.nav_history.as_mut() {
13109 let buffer = self.buffer.read(cx).read(cx);
13110 let cursor_position = cursor_anchor.to_point(&buffer);
13111 let scroll_state = self.scroll_manager.anchor();
13112 let scroll_top_row = scroll_state.top_row(&buffer);
13113 drop(buffer);
13114
13115 if let Some(new_position) = new_position {
13116 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13117 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
13118 return;
13119 }
13120 }
13121
13122 nav_history.push(
13123 Some(NavigationData {
13124 cursor_anchor,
13125 cursor_position,
13126 scroll_anchor: scroll_state,
13127 scroll_top_row,
13128 }),
13129 cx,
13130 );
13131 cx.emit(EditorEvent::PushedToNavHistory {
13132 anchor: cursor_anchor,
13133 is_deactivate,
13134 })
13135 }
13136 }
13137
13138 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13139 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13140 let buffer = self.buffer.read(cx).snapshot(cx);
13141 let mut selection = self.selections.first::<usize>(cx);
13142 selection.set_head(buffer.len(), SelectionGoal::None);
13143 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13144 s.select(vec![selection]);
13145 });
13146 }
13147
13148 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13149 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13150 let end = self.buffer.read(cx).read(cx).len();
13151 self.change_selections(None, window, cx, |s| {
13152 s.select_ranges(vec![0..end]);
13153 });
13154 }
13155
13156 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13157 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13158 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13159 let mut selections = self.selections.all::<Point>(cx);
13160 let max_point = display_map.buffer_snapshot.max_point();
13161 for selection in &mut selections {
13162 let rows = selection.spanned_rows(true, &display_map);
13163 selection.start = Point::new(rows.start.0, 0);
13164 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13165 selection.reversed = false;
13166 }
13167 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13168 s.select(selections);
13169 });
13170 }
13171
13172 pub fn split_selection_into_lines(
13173 &mut self,
13174 _: &SplitSelectionIntoLines,
13175 window: &mut Window,
13176 cx: &mut Context<Self>,
13177 ) {
13178 let selections = self
13179 .selections
13180 .all::<Point>(cx)
13181 .into_iter()
13182 .map(|selection| selection.start..selection.end)
13183 .collect::<Vec<_>>();
13184 self.unfold_ranges(&selections, true, true, cx);
13185
13186 let mut new_selection_ranges = Vec::new();
13187 {
13188 let buffer = self.buffer.read(cx).read(cx);
13189 for selection in selections {
13190 for row in selection.start.row..selection.end.row {
13191 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13192 new_selection_ranges.push(cursor..cursor);
13193 }
13194
13195 let is_multiline_selection = selection.start.row != selection.end.row;
13196 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13197 // so this action feels more ergonomic when paired with other selection operations
13198 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13199 if !should_skip_last {
13200 new_selection_ranges.push(selection.end..selection.end);
13201 }
13202 }
13203 }
13204 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13205 s.select_ranges(new_selection_ranges);
13206 });
13207 }
13208
13209 pub fn add_selection_above(
13210 &mut self,
13211 _: &AddSelectionAbove,
13212 window: &mut Window,
13213 cx: &mut Context<Self>,
13214 ) {
13215 self.add_selection(true, window, cx);
13216 }
13217
13218 pub fn add_selection_below(
13219 &mut self,
13220 _: &AddSelectionBelow,
13221 window: &mut Window,
13222 cx: &mut Context<Self>,
13223 ) {
13224 self.add_selection(false, window, cx);
13225 }
13226
13227 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13228 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13229
13230 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13231 let all_selections = self.selections.all::<Point>(cx);
13232 let text_layout_details = self.text_layout_details(window);
13233
13234 let (mut columnar_selections, new_selections_to_columnarize) = {
13235 if let Some(state) = self.add_selections_state.as_ref() {
13236 let columnar_selection_ids: HashSet<_> = state
13237 .groups
13238 .iter()
13239 .flat_map(|group| group.stack.iter())
13240 .copied()
13241 .collect();
13242
13243 all_selections
13244 .into_iter()
13245 .partition(|s| columnar_selection_ids.contains(&s.id))
13246 } else {
13247 (Vec::new(), all_selections)
13248 }
13249 };
13250
13251 let mut state = self
13252 .add_selections_state
13253 .take()
13254 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13255
13256 for selection in new_selections_to_columnarize {
13257 let range = selection.display_range(&display_map).sorted();
13258 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13259 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13260 let positions = start_x.min(end_x)..start_x.max(end_x);
13261 let mut stack = Vec::new();
13262 for row in range.start.row().0..=range.end.row().0 {
13263 if let Some(selection) = self.selections.build_columnar_selection(
13264 &display_map,
13265 DisplayRow(row),
13266 &positions,
13267 selection.reversed,
13268 &text_layout_details,
13269 ) {
13270 stack.push(selection.id);
13271 columnar_selections.push(selection);
13272 }
13273 }
13274 if !stack.is_empty() {
13275 if above {
13276 stack.reverse();
13277 }
13278 state.groups.push(AddSelectionsGroup { above, stack });
13279 }
13280 }
13281
13282 let mut final_selections = Vec::new();
13283 let end_row = if above {
13284 DisplayRow(0)
13285 } else {
13286 display_map.max_point().row()
13287 };
13288
13289 let mut last_added_item_per_group = HashMap::default();
13290 for group in state.groups.iter_mut() {
13291 if let Some(last_id) = group.stack.last() {
13292 last_added_item_per_group.insert(*last_id, group);
13293 }
13294 }
13295
13296 for selection in columnar_selections {
13297 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13298 if above == group.above {
13299 let range = selection.display_range(&display_map).sorted();
13300 debug_assert_eq!(range.start.row(), range.end.row());
13301 let mut row = range.start.row();
13302 let positions =
13303 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13304 px(start)..px(end)
13305 } else {
13306 let start_x =
13307 display_map.x_for_display_point(range.start, &text_layout_details);
13308 let end_x =
13309 display_map.x_for_display_point(range.end, &text_layout_details);
13310 start_x.min(end_x)..start_x.max(end_x)
13311 };
13312
13313 let mut maybe_new_selection = None;
13314 while row != end_row {
13315 if above {
13316 row.0 -= 1;
13317 } else {
13318 row.0 += 1;
13319 }
13320 if let Some(new_selection) = self.selections.build_columnar_selection(
13321 &display_map,
13322 row,
13323 &positions,
13324 selection.reversed,
13325 &text_layout_details,
13326 ) {
13327 maybe_new_selection = Some(new_selection);
13328 break;
13329 }
13330 }
13331
13332 if let Some(new_selection) = maybe_new_selection {
13333 group.stack.push(new_selection.id);
13334 if above {
13335 final_selections.push(new_selection);
13336 final_selections.push(selection);
13337 } else {
13338 final_selections.push(selection);
13339 final_selections.push(new_selection);
13340 }
13341 } else {
13342 final_selections.push(selection);
13343 }
13344 } else {
13345 group.stack.pop();
13346 }
13347 } else {
13348 final_selections.push(selection);
13349 }
13350 }
13351
13352 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13353 s.select(final_selections);
13354 });
13355
13356 let final_selection_ids: HashSet<_> = self
13357 .selections
13358 .all::<Point>(cx)
13359 .iter()
13360 .map(|s| s.id)
13361 .collect();
13362 state.groups.retain_mut(|group| {
13363 // selections might get merged above so we remove invalid items from stacks
13364 group.stack.retain(|id| final_selection_ids.contains(id));
13365
13366 // single selection in stack can be treated as initial state
13367 group.stack.len() > 1
13368 });
13369
13370 if !state.groups.is_empty() {
13371 self.add_selections_state = Some(state);
13372 }
13373 }
13374
13375 fn select_match_ranges(
13376 &mut self,
13377 range: Range<usize>,
13378 reversed: bool,
13379 replace_newest: bool,
13380 auto_scroll: Option<Autoscroll>,
13381 window: &mut Window,
13382 cx: &mut Context<Editor>,
13383 ) {
13384 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
13385 self.change_selections(auto_scroll, window, cx, |s| {
13386 if replace_newest {
13387 s.delete(s.newest_anchor().id);
13388 }
13389 if reversed {
13390 s.insert_range(range.end..range.start);
13391 } else {
13392 s.insert_range(range);
13393 }
13394 });
13395 }
13396
13397 pub fn select_next_match_internal(
13398 &mut self,
13399 display_map: &DisplaySnapshot,
13400 replace_newest: bool,
13401 autoscroll: Option<Autoscroll>,
13402 window: &mut Window,
13403 cx: &mut Context<Self>,
13404 ) -> Result<()> {
13405 let buffer = &display_map.buffer_snapshot;
13406 let mut selections = self.selections.all::<usize>(cx);
13407 if let Some(mut select_next_state) = self.select_next_state.take() {
13408 let query = &select_next_state.query;
13409 if !select_next_state.done {
13410 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13411 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13412 let mut next_selected_range = None;
13413
13414 let bytes_after_last_selection =
13415 buffer.bytes_in_range(last_selection.end..buffer.len());
13416 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13417 let query_matches = query
13418 .stream_find_iter(bytes_after_last_selection)
13419 .map(|result| (last_selection.end, result))
13420 .chain(
13421 query
13422 .stream_find_iter(bytes_before_first_selection)
13423 .map(|result| (0, result)),
13424 );
13425
13426 for (start_offset, query_match) in query_matches {
13427 let query_match = query_match.unwrap(); // can only fail due to I/O
13428 let offset_range =
13429 start_offset + query_match.start()..start_offset + query_match.end();
13430
13431 if !select_next_state.wordwise
13432 || (!buffer.is_inside_word(offset_range.start, false)
13433 && !buffer.is_inside_word(offset_range.end, false))
13434 {
13435 // TODO: This is n^2, because we might check all the selections
13436 if !selections
13437 .iter()
13438 .any(|selection| selection.range().overlaps(&offset_range))
13439 {
13440 next_selected_range = Some(offset_range);
13441 break;
13442 }
13443 }
13444 }
13445
13446 if let Some(next_selected_range) = next_selected_range {
13447 self.select_match_ranges(
13448 next_selected_range,
13449 last_selection.reversed,
13450 replace_newest,
13451 autoscroll,
13452 window,
13453 cx,
13454 );
13455 } else {
13456 select_next_state.done = true;
13457 }
13458 }
13459
13460 self.select_next_state = Some(select_next_state);
13461 } else {
13462 let mut only_carets = true;
13463 let mut same_text_selected = true;
13464 let mut selected_text = None;
13465
13466 let mut selections_iter = selections.iter().peekable();
13467 while let Some(selection) = selections_iter.next() {
13468 if selection.start != selection.end {
13469 only_carets = false;
13470 }
13471
13472 if same_text_selected {
13473 if selected_text.is_none() {
13474 selected_text =
13475 Some(buffer.text_for_range(selection.range()).collect::<String>());
13476 }
13477
13478 if let Some(next_selection) = selections_iter.peek() {
13479 if next_selection.range().len() == selection.range().len() {
13480 let next_selected_text = buffer
13481 .text_for_range(next_selection.range())
13482 .collect::<String>();
13483 if Some(next_selected_text) != selected_text {
13484 same_text_selected = false;
13485 selected_text = None;
13486 }
13487 } else {
13488 same_text_selected = false;
13489 selected_text = None;
13490 }
13491 }
13492 }
13493 }
13494
13495 if only_carets {
13496 for selection in &mut selections {
13497 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13498 selection.start = word_range.start;
13499 selection.end = word_range.end;
13500 selection.goal = SelectionGoal::None;
13501 selection.reversed = false;
13502 self.select_match_ranges(
13503 selection.start..selection.end,
13504 selection.reversed,
13505 replace_newest,
13506 autoscroll,
13507 window,
13508 cx,
13509 );
13510 }
13511
13512 if selections.len() == 1 {
13513 let selection = selections
13514 .last()
13515 .expect("ensured that there's only one selection");
13516 let query = buffer
13517 .text_for_range(selection.start..selection.end)
13518 .collect::<String>();
13519 let is_empty = query.is_empty();
13520 let select_state = SelectNextState {
13521 query: AhoCorasick::new(&[query])?,
13522 wordwise: true,
13523 done: is_empty,
13524 };
13525 self.select_next_state = Some(select_state);
13526 } else {
13527 self.select_next_state = None;
13528 }
13529 } else if let Some(selected_text) = selected_text {
13530 self.select_next_state = Some(SelectNextState {
13531 query: AhoCorasick::new(&[selected_text])?,
13532 wordwise: false,
13533 done: false,
13534 });
13535 self.select_next_match_internal(
13536 display_map,
13537 replace_newest,
13538 autoscroll,
13539 window,
13540 cx,
13541 )?;
13542 }
13543 }
13544 Ok(())
13545 }
13546
13547 pub fn select_all_matches(
13548 &mut self,
13549 _action: &SelectAllMatches,
13550 window: &mut Window,
13551 cx: &mut Context<Self>,
13552 ) -> Result<()> {
13553 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13554
13555 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13556
13557 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13558 let Some(select_next_state) = self.select_next_state.as_mut() else {
13559 return Ok(());
13560 };
13561 if select_next_state.done {
13562 return Ok(());
13563 }
13564
13565 let mut new_selections = Vec::new();
13566
13567 let reversed = self.selections.oldest::<usize>(cx).reversed;
13568 let buffer = &display_map.buffer_snapshot;
13569 let query_matches = select_next_state
13570 .query
13571 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13572
13573 for query_match in query_matches.into_iter() {
13574 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13575 let offset_range = if reversed {
13576 query_match.end()..query_match.start()
13577 } else {
13578 query_match.start()..query_match.end()
13579 };
13580
13581 if !select_next_state.wordwise
13582 || (!buffer.is_inside_word(offset_range.start, false)
13583 && !buffer.is_inside_word(offset_range.end, false))
13584 {
13585 new_selections.push(offset_range.start..offset_range.end);
13586 }
13587 }
13588
13589 select_next_state.done = true;
13590
13591 if new_selections.is_empty() {
13592 log::error!("bug: new_selections is empty in select_all_matches");
13593 return Ok(());
13594 }
13595
13596 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13597 self.change_selections(None, window, cx, |selections| {
13598 selections.select_ranges(new_selections)
13599 });
13600
13601 Ok(())
13602 }
13603
13604 pub fn select_next(
13605 &mut self,
13606 action: &SelectNext,
13607 window: &mut Window,
13608 cx: &mut Context<Self>,
13609 ) -> Result<()> {
13610 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13611 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13612 self.select_next_match_internal(
13613 &display_map,
13614 action.replace_newest,
13615 Some(Autoscroll::newest()),
13616 window,
13617 cx,
13618 )?;
13619 Ok(())
13620 }
13621
13622 pub fn select_previous(
13623 &mut self,
13624 action: &SelectPrevious,
13625 window: &mut Window,
13626 cx: &mut Context<Self>,
13627 ) -> Result<()> {
13628 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13629 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13630 let buffer = &display_map.buffer_snapshot;
13631 let mut selections = self.selections.all::<usize>(cx);
13632 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13633 let query = &select_prev_state.query;
13634 if !select_prev_state.done {
13635 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13636 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13637 let mut next_selected_range = None;
13638 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13639 let bytes_before_last_selection =
13640 buffer.reversed_bytes_in_range(0..last_selection.start);
13641 let bytes_after_first_selection =
13642 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13643 let query_matches = query
13644 .stream_find_iter(bytes_before_last_selection)
13645 .map(|result| (last_selection.start, result))
13646 .chain(
13647 query
13648 .stream_find_iter(bytes_after_first_selection)
13649 .map(|result| (buffer.len(), result)),
13650 );
13651 for (end_offset, query_match) in query_matches {
13652 let query_match = query_match.unwrap(); // can only fail due to I/O
13653 let offset_range =
13654 end_offset - query_match.end()..end_offset - query_match.start();
13655
13656 if !select_prev_state.wordwise
13657 || (!buffer.is_inside_word(offset_range.start, false)
13658 && !buffer.is_inside_word(offset_range.end, false))
13659 {
13660 next_selected_range = Some(offset_range);
13661 break;
13662 }
13663 }
13664
13665 if let Some(next_selected_range) = next_selected_range {
13666 self.select_match_ranges(
13667 next_selected_range,
13668 last_selection.reversed,
13669 action.replace_newest,
13670 Some(Autoscroll::newest()),
13671 window,
13672 cx,
13673 );
13674 } else {
13675 select_prev_state.done = true;
13676 }
13677 }
13678
13679 self.select_prev_state = Some(select_prev_state);
13680 } else {
13681 let mut only_carets = true;
13682 let mut same_text_selected = true;
13683 let mut selected_text = None;
13684
13685 let mut selections_iter = selections.iter().peekable();
13686 while let Some(selection) = selections_iter.next() {
13687 if selection.start != selection.end {
13688 only_carets = false;
13689 }
13690
13691 if same_text_selected {
13692 if selected_text.is_none() {
13693 selected_text =
13694 Some(buffer.text_for_range(selection.range()).collect::<String>());
13695 }
13696
13697 if let Some(next_selection) = selections_iter.peek() {
13698 if next_selection.range().len() == selection.range().len() {
13699 let next_selected_text = buffer
13700 .text_for_range(next_selection.range())
13701 .collect::<String>();
13702 if Some(next_selected_text) != selected_text {
13703 same_text_selected = false;
13704 selected_text = None;
13705 }
13706 } else {
13707 same_text_selected = false;
13708 selected_text = None;
13709 }
13710 }
13711 }
13712 }
13713
13714 if only_carets {
13715 for selection in &mut selections {
13716 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13717 selection.start = word_range.start;
13718 selection.end = word_range.end;
13719 selection.goal = SelectionGoal::None;
13720 selection.reversed = false;
13721 self.select_match_ranges(
13722 selection.start..selection.end,
13723 selection.reversed,
13724 action.replace_newest,
13725 Some(Autoscroll::newest()),
13726 window,
13727 cx,
13728 );
13729 }
13730 if selections.len() == 1 {
13731 let selection = selections
13732 .last()
13733 .expect("ensured that there's only one selection");
13734 let query = buffer
13735 .text_for_range(selection.start..selection.end)
13736 .collect::<String>();
13737 let is_empty = query.is_empty();
13738 let select_state = SelectNextState {
13739 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13740 wordwise: true,
13741 done: is_empty,
13742 };
13743 self.select_prev_state = Some(select_state);
13744 } else {
13745 self.select_prev_state = None;
13746 }
13747 } else if let Some(selected_text) = selected_text {
13748 self.select_prev_state = Some(SelectNextState {
13749 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13750 wordwise: false,
13751 done: false,
13752 });
13753 self.select_previous(action, window, cx)?;
13754 }
13755 }
13756 Ok(())
13757 }
13758
13759 pub fn find_next_match(
13760 &mut self,
13761 _: &FindNextMatch,
13762 window: &mut Window,
13763 cx: &mut Context<Self>,
13764 ) -> Result<()> {
13765 let selections = self.selections.disjoint_anchors();
13766 match selections.first() {
13767 Some(first) if selections.len() >= 2 => {
13768 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13769 s.select_ranges([first.range()]);
13770 });
13771 }
13772 _ => self.select_next(
13773 &SelectNext {
13774 replace_newest: true,
13775 },
13776 window,
13777 cx,
13778 )?,
13779 }
13780 Ok(())
13781 }
13782
13783 pub fn find_previous_match(
13784 &mut self,
13785 _: &FindPreviousMatch,
13786 window: &mut Window,
13787 cx: &mut Context<Self>,
13788 ) -> Result<()> {
13789 let selections = self.selections.disjoint_anchors();
13790 match selections.last() {
13791 Some(last) if selections.len() >= 2 => {
13792 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13793 s.select_ranges([last.range()]);
13794 });
13795 }
13796 _ => self.select_previous(
13797 &SelectPrevious {
13798 replace_newest: true,
13799 },
13800 window,
13801 cx,
13802 )?,
13803 }
13804 Ok(())
13805 }
13806
13807 pub fn toggle_comments(
13808 &mut self,
13809 action: &ToggleComments,
13810 window: &mut Window,
13811 cx: &mut Context<Self>,
13812 ) {
13813 if self.read_only(cx) {
13814 return;
13815 }
13816 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13817 let text_layout_details = &self.text_layout_details(window);
13818 self.transact(window, cx, |this, window, cx| {
13819 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13820 let mut edits = Vec::new();
13821 let mut selection_edit_ranges = Vec::new();
13822 let mut last_toggled_row = None;
13823 let snapshot = this.buffer.read(cx).read(cx);
13824 let empty_str: Arc<str> = Arc::default();
13825 let mut suffixes_inserted = Vec::new();
13826 let ignore_indent = action.ignore_indent;
13827
13828 fn comment_prefix_range(
13829 snapshot: &MultiBufferSnapshot,
13830 row: MultiBufferRow,
13831 comment_prefix: &str,
13832 comment_prefix_whitespace: &str,
13833 ignore_indent: bool,
13834 ) -> Range<Point> {
13835 let indent_size = if ignore_indent {
13836 0
13837 } else {
13838 snapshot.indent_size_for_line(row).len
13839 };
13840
13841 let start = Point::new(row.0, indent_size);
13842
13843 let mut line_bytes = snapshot
13844 .bytes_in_range(start..snapshot.max_point())
13845 .flatten()
13846 .copied();
13847
13848 // If this line currently begins with the line comment prefix, then record
13849 // the range containing the prefix.
13850 if line_bytes
13851 .by_ref()
13852 .take(comment_prefix.len())
13853 .eq(comment_prefix.bytes())
13854 {
13855 // Include any whitespace that matches the comment prefix.
13856 let matching_whitespace_len = line_bytes
13857 .zip(comment_prefix_whitespace.bytes())
13858 .take_while(|(a, b)| a == b)
13859 .count() as u32;
13860 let end = Point::new(
13861 start.row,
13862 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13863 );
13864 start..end
13865 } else {
13866 start..start
13867 }
13868 }
13869
13870 fn comment_suffix_range(
13871 snapshot: &MultiBufferSnapshot,
13872 row: MultiBufferRow,
13873 comment_suffix: &str,
13874 comment_suffix_has_leading_space: bool,
13875 ) -> Range<Point> {
13876 let end = Point::new(row.0, snapshot.line_len(row));
13877 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13878
13879 let mut line_end_bytes = snapshot
13880 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13881 .flatten()
13882 .copied();
13883
13884 let leading_space_len = if suffix_start_column > 0
13885 && line_end_bytes.next() == Some(b' ')
13886 && comment_suffix_has_leading_space
13887 {
13888 1
13889 } else {
13890 0
13891 };
13892
13893 // If this line currently begins with the line comment prefix, then record
13894 // the range containing the prefix.
13895 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13896 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13897 start..end
13898 } else {
13899 end..end
13900 }
13901 }
13902
13903 // TODO: Handle selections that cross excerpts
13904 for selection in &mut selections {
13905 let start_column = snapshot
13906 .indent_size_for_line(MultiBufferRow(selection.start.row))
13907 .len;
13908 let language = if let Some(language) =
13909 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13910 {
13911 language
13912 } else {
13913 continue;
13914 };
13915
13916 selection_edit_ranges.clear();
13917
13918 // If multiple selections contain a given row, avoid processing that
13919 // row more than once.
13920 let mut start_row = MultiBufferRow(selection.start.row);
13921 if last_toggled_row == Some(start_row) {
13922 start_row = start_row.next_row();
13923 }
13924 let end_row =
13925 if selection.end.row > selection.start.row && selection.end.column == 0 {
13926 MultiBufferRow(selection.end.row - 1)
13927 } else {
13928 MultiBufferRow(selection.end.row)
13929 };
13930 last_toggled_row = Some(end_row);
13931
13932 if start_row > end_row {
13933 continue;
13934 }
13935
13936 // If the language has line comments, toggle those.
13937 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13938
13939 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13940 if ignore_indent {
13941 full_comment_prefixes = full_comment_prefixes
13942 .into_iter()
13943 .map(|s| Arc::from(s.trim_end()))
13944 .collect();
13945 }
13946
13947 if !full_comment_prefixes.is_empty() {
13948 let first_prefix = full_comment_prefixes
13949 .first()
13950 .expect("prefixes is non-empty");
13951 let prefix_trimmed_lengths = full_comment_prefixes
13952 .iter()
13953 .map(|p| p.trim_end_matches(' ').len())
13954 .collect::<SmallVec<[usize; 4]>>();
13955
13956 let mut all_selection_lines_are_comments = true;
13957
13958 for row in start_row.0..=end_row.0 {
13959 let row = MultiBufferRow(row);
13960 if start_row < end_row && snapshot.is_line_blank(row) {
13961 continue;
13962 }
13963
13964 let prefix_range = full_comment_prefixes
13965 .iter()
13966 .zip(prefix_trimmed_lengths.iter().copied())
13967 .map(|(prefix, trimmed_prefix_len)| {
13968 comment_prefix_range(
13969 snapshot.deref(),
13970 row,
13971 &prefix[..trimmed_prefix_len],
13972 &prefix[trimmed_prefix_len..],
13973 ignore_indent,
13974 )
13975 })
13976 .max_by_key(|range| range.end.column - range.start.column)
13977 .expect("prefixes is non-empty");
13978
13979 if prefix_range.is_empty() {
13980 all_selection_lines_are_comments = false;
13981 }
13982
13983 selection_edit_ranges.push(prefix_range);
13984 }
13985
13986 if all_selection_lines_are_comments {
13987 edits.extend(
13988 selection_edit_ranges
13989 .iter()
13990 .cloned()
13991 .map(|range| (range, empty_str.clone())),
13992 );
13993 } else {
13994 let min_column = selection_edit_ranges
13995 .iter()
13996 .map(|range| range.start.column)
13997 .min()
13998 .unwrap_or(0);
13999 edits.extend(selection_edit_ranges.iter().map(|range| {
14000 let position = Point::new(range.start.row, min_column);
14001 (position..position, first_prefix.clone())
14002 }));
14003 }
14004 } else if let Some((full_comment_prefix, comment_suffix)) =
14005 language.block_comment_delimiters()
14006 {
14007 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14008 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14009 let prefix_range = comment_prefix_range(
14010 snapshot.deref(),
14011 start_row,
14012 comment_prefix,
14013 comment_prefix_whitespace,
14014 ignore_indent,
14015 );
14016 let suffix_range = comment_suffix_range(
14017 snapshot.deref(),
14018 end_row,
14019 comment_suffix.trim_start_matches(' '),
14020 comment_suffix.starts_with(' '),
14021 );
14022
14023 if prefix_range.is_empty() || suffix_range.is_empty() {
14024 edits.push((
14025 prefix_range.start..prefix_range.start,
14026 full_comment_prefix.clone(),
14027 ));
14028 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14029 suffixes_inserted.push((end_row, comment_suffix.len()));
14030 } else {
14031 edits.push((prefix_range, empty_str.clone()));
14032 edits.push((suffix_range, empty_str.clone()));
14033 }
14034 } else {
14035 continue;
14036 }
14037 }
14038
14039 drop(snapshot);
14040 this.buffer.update(cx, |buffer, cx| {
14041 buffer.edit(edits, None, cx);
14042 });
14043
14044 // Adjust selections so that they end before any comment suffixes that
14045 // were inserted.
14046 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14047 let mut selections = this.selections.all::<Point>(cx);
14048 let snapshot = this.buffer.read(cx).read(cx);
14049 for selection in &mut selections {
14050 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14051 match row.cmp(&MultiBufferRow(selection.end.row)) {
14052 Ordering::Less => {
14053 suffixes_inserted.next();
14054 continue;
14055 }
14056 Ordering::Greater => break,
14057 Ordering::Equal => {
14058 if selection.end.column == snapshot.line_len(row) {
14059 if selection.is_empty() {
14060 selection.start.column -= suffix_len as u32;
14061 }
14062 selection.end.column -= suffix_len as u32;
14063 }
14064 break;
14065 }
14066 }
14067 }
14068 }
14069
14070 drop(snapshot);
14071 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14072 s.select(selections)
14073 });
14074
14075 let selections = this.selections.all::<Point>(cx);
14076 let selections_on_single_row = selections.windows(2).all(|selections| {
14077 selections[0].start.row == selections[1].start.row
14078 && selections[0].end.row == selections[1].end.row
14079 && selections[0].start.row == selections[0].end.row
14080 });
14081 let selections_selecting = selections
14082 .iter()
14083 .any(|selection| selection.start != selection.end);
14084 let advance_downwards = action.advance_downwards
14085 && selections_on_single_row
14086 && !selections_selecting
14087 && !matches!(this.mode, EditorMode::SingleLine { .. });
14088
14089 if advance_downwards {
14090 let snapshot = this.buffer.read(cx).snapshot(cx);
14091
14092 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14093 s.move_cursors_with(|display_snapshot, display_point, _| {
14094 let mut point = display_point.to_point(display_snapshot);
14095 point.row += 1;
14096 point = snapshot.clip_point(point, Bias::Left);
14097 let display_point = point.to_display_point(display_snapshot);
14098 let goal = SelectionGoal::HorizontalPosition(
14099 display_snapshot
14100 .x_for_display_point(display_point, text_layout_details)
14101 .into(),
14102 );
14103 (display_point, goal)
14104 })
14105 });
14106 }
14107 });
14108 }
14109
14110 pub fn select_enclosing_symbol(
14111 &mut self,
14112 _: &SelectEnclosingSymbol,
14113 window: &mut Window,
14114 cx: &mut Context<Self>,
14115 ) {
14116 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14117
14118 let buffer = self.buffer.read(cx).snapshot(cx);
14119 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14120
14121 fn update_selection(
14122 selection: &Selection<usize>,
14123 buffer_snap: &MultiBufferSnapshot,
14124 ) -> Option<Selection<usize>> {
14125 let cursor = selection.head();
14126 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14127 for symbol in symbols.iter().rev() {
14128 let start = symbol.range.start.to_offset(buffer_snap);
14129 let end = symbol.range.end.to_offset(buffer_snap);
14130 let new_range = start..end;
14131 if start < selection.start || end > selection.end {
14132 return Some(Selection {
14133 id: selection.id,
14134 start: new_range.start,
14135 end: new_range.end,
14136 goal: SelectionGoal::None,
14137 reversed: selection.reversed,
14138 });
14139 }
14140 }
14141 None
14142 }
14143
14144 let mut selected_larger_symbol = false;
14145 let new_selections = old_selections
14146 .iter()
14147 .map(|selection| match update_selection(selection, &buffer) {
14148 Some(new_selection) => {
14149 if new_selection.range() != selection.range() {
14150 selected_larger_symbol = true;
14151 }
14152 new_selection
14153 }
14154 None => selection.clone(),
14155 })
14156 .collect::<Vec<_>>();
14157
14158 if selected_larger_symbol {
14159 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14160 s.select(new_selections);
14161 });
14162 }
14163 }
14164
14165 pub fn select_larger_syntax_node(
14166 &mut self,
14167 _: &SelectLargerSyntaxNode,
14168 window: &mut Window,
14169 cx: &mut Context<Self>,
14170 ) {
14171 let Some(visible_row_count) = self.visible_row_count() else {
14172 return;
14173 };
14174 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14175 if old_selections.is_empty() {
14176 return;
14177 }
14178
14179 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14180
14181 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14182 let buffer = self.buffer.read(cx).snapshot(cx);
14183
14184 let mut selected_larger_node = false;
14185 let mut new_selections = old_selections
14186 .iter()
14187 .map(|selection| {
14188 let old_range = selection.start..selection.end;
14189
14190 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14191 // manually select word at selection
14192 if ["string_content", "inline"].contains(&node.kind()) {
14193 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14194 // ignore if word is already selected
14195 if !word_range.is_empty() && old_range != word_range {
14196 let (last_word_range, _) =
14197 buffer.surrounding_word(old_range.end, false);
14198 // only select word if start and end point belongs to same word
14199 if word_range == last_word_range {
14200 selected_larger_node = true;
14201 return Selection {
14202 id: selection.id,
14203 start: word_range.start,
14204 end: word_range.end,
14205 goal: SelectionGoal::None,
14206 reversed: selection.reversed,
14207 };
14208 }
14209 }
14210 }
14211 }
14212
14213 let mut new_range = old_range.clone();
14214 while let Some((_node, containing_range)) =
14215 buffer.syntax_ancestor(new_range.clone())
14216 {
14217 new_range = match containing_range {
14218 MultiOrSingleBufferOffsetRange::Single(_) => break,
14219 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14220 };
14221 if !display_map.intersects_fold(new_range.start)
14222 && !display_map.intersects_fold(new_range.end)
14223 {
14224 break;
14225 }
14226 }
14227
14228 selected_larger_node |= new_range != old_range;
14229 Selection {
14230 id: selection.id,
14231 start: new_range.start,
14232 end: new_range.end,
14233 goal: SelectionGoal::None,
14234 reversed: selection.reversed,
14235 }
14236 })
14237 .collect::<Vec<_>>();
14238
14239 if !selected_larger_node {
14240 return; // don't put this call in the history
14241 }
14242
14243 // scroll based on transformation done to the last selection created by the user
14244 let (last_old, last_new) = old_selections
14245 .last()
14246 .zip(new_selections.last().cloned())
14247 .expect("old_selections isn't empty");
14248
14249 // revert selection
14250 let is_selection_reversed = {
14251 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14252 new_selections.last_mut().expect("checked above").reversed =
14253 should_newest_selection_be_reversed;
14254 should_newest_selection_be_reversed
14255 };
14256
14257 if selected_larger_node {
14258 self.select_syntax_node_history.disable_clearing = true;
14259 self.change_selections(None, window, cx, |s| {
14260 s.select(new_selections.clone());
14261 });
14262 self.select_syntax_node_history.disable_clearing = false;
14263 }
14264
14265 let start_row = last_new.start.to_display_point(&display_map).row().0;
14266 let end_row = last_new.end.to_display_point(&display_map).row().0;
14267 let selection_height = end_row - start_row + 1;
14268 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14269
14270 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14271 let scroll_behavior = if fits_on_the_screen {
14272 self.request_autoscroll(Autoscroll::fit(), cx);
14273 SelectSyntaxNodeScrollBehavior::FitSelection
14274 } else if is_selection_reversed {
14275 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14276 SelectSyntaxNodeScrollBehavior::CursorTop
14277 } else {
14278 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14279 SelectSyntaxNodeScrollBehavior::CursorBottom
14280 };
14281
14282 self.select_syntax_node_history.push((
14283 old_selections,
14284 scroll_behavior,
14285 is_selection_reversed,
14286 ));
14287 }
14288
14289 pub fn select_smaller_syntax_node(
14290 &mut self,
14291 _: &SelectSmallerSyntaxNode,
14292 window: &mut Window,
14293 cx: &mut Context<Self>,
14294 ) {
14295 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14296
14297 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14298 self.select_syntax_node_history.pop()
14299 {
14300 if let Some(selection) = selections.last_mut() {
14301 selection.reversed = is_selection_reversed;
14302 }
14303
14304 self.select_syntax_node_history.disable_clearing = true;
14305 self.change_selections(None, window, cx, |s| {
14306 s.select(selections.to_vec());
14307 });
14308 self.select_syntax_node_history.disable_clearing = false;
14309
14310 match scroll_behavior {
14311 SelectSyntaxNodeScrollBehavior::CursorTop => {
14312 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14313 }
14314 SelectSyntaxNodeScrollBehavior::FitSelection => {
14315 self.request_autoscroll(Autoscroll::fit(), cx);
14316 }
14317 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14318 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14319 }
14320 }
14321 }
14322 }
14323
14324 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14325 if !EditorSettings::get_global(cx).gutter.runnables {
14326 self.clear_tasks();
14327 return Task::ready(());
14328 }
14329 let project = self.project.as_ref().map(Entity::downgrade);
14330 let task_sources = self.lsp_task_sources(cx);
14331 let multi_buffer = self.buffer.downgrade();
14332 cx.spawn_in(window, async move |editor, cx| {
14333 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14334 let Some(project) = project.and_then(|p| p.upgrade()) else {
14335 return;
14336 };
14337 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14338 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14339 }) else {
14340 return;
14341 };
14342
14343 let hide_runnables = project
14344 .update(cx, |project, cx| {
14345 // Do not display any test indicators in non-dev server remote projects.
14346 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14347 })
14348 .unwrap_or(true);
14349 if hide_runnables {
14350 return;
14351 }
14352 let new_rows =
14353 cx.background_spawn({
14354 let snapshot = display_snapshot.clone();
14355 async move {
14356 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14357 }
14358 })
14359 .await;
14360 let Ok(lsp_tasks) =
14361 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14362 else {
14363 return;
14364 };
14365 let lsp_tasks = lsp_tasks.await;
14366
14367 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14368 lsp_tasks
14369 .into_iter()
14370 .flat_map(|(kind, tasks)| {
14371 tasks.into_iter().filter_map(move |(location, task)| {
14372 Some((kind.clone(), location?, task))
14373 })
14374 })
14375 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14376 let buffer = location.target.buffer;
14377 let buffer_snapshot = buffer.read(cx).snapshot();
14378 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14379 |(excerpt_id, snapshot, _)| {
14380 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14381 display_snapshot
14382 .buffer_snapshot
14383 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14384 } else {
14385 None
14386 }
14387 },
14388 );
14389 if let Some(offset) = offset {
14390 let task_buffer_range =
14391 location.target.range.to_point(&buffer_snapshot);
14392 let context_buffer_range =
14393 task_buffer_range.to_offset(&buffer_snapshot);
14394 let context_range = BufferOffset(context_buffer_range.start)
14395 ..BufferOffset(context_buffer_range.end);
14396
14397 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14398 .or_insert_with(|| RunnableTasks {
14399 templates: Vec::new(),
14400 offset,
14401 column: task_buffer_range.start.column,
14402 extra_variables: HashMap::default(),
14403 context_range,
14404 })
14405 .templates
14406 .push((kind, task.original_task().clone()));
14407 }
14408
14409 acc
14410 })
14411 }) else {
14412 return;
14413 };
14414
14415 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14416 buffer.language_settings(cx).tasks.prefer_lsp
14417 }) else {
14418 return;
14419 };
14420
14421 let rows = Self::runnable_rows(
14422 project,
14423 display_snapshot,
14424 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14425 new_rows,
14426 cx.clone(),
14427 )
14428 .await;
14429 editor
14430 .update(cx, |editor, _| {
14431 editor.clear_tasks();
14432 for (key, mut value) in rows {
14433 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14434 value.templates.extend(lsp_tasks.templates);
14435 }
14436
14437 editor.insert_tasks(key, value);
14438 }
14439 for (key, value) in lsp_tasks_by_rows {
14440 editor.insert_tasks(key, value);
14441 }
14442 })
14443 .ok();
14444 })
14445 }
14446 fn fetch_runnable_ranges(
14447 snapshot: &DisplaySnapshot,
14448 range: Range<Anchor>,
14449 ) -> Vec<language::RunnableRange> {
14450 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14451 }
14452
14453 fn runnable_rows(
14454 project: Entity<Project>,
14455 snapshot: DisplaySnapshot,
14456 prefer_lsp: bool,
14457 runnable_ranges: Vec<RunnableRange>,
14458 cx: AsyncWindowContext,
14459 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14460 cx.spawn(async move |cx| {
14461 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14462 for mut runnable in runnable_ranges {
14463 let Some(tasks) = cx
14464 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14465 .ok()
14466 else {
14467 continue;
14468 };
14469 let mut tasks = tasks.await;
14470
14471 if prefer_lsp {
14472 tasks.retain(|(task_kind, _)| {
14473 !matches!(task_kind, TaskSourceKind::Language { .. })
14474 });
14475 }
14476 if tasks.is_empty() {
14477 continue;
14478 }
14479
14480 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14481 let Some(row) = snapshot
14482 .buffer_snapshot
14483 .buffer_line_for_row(MultiBufferRow(point.row))
14484 .map(|(_, range)| range.start.row)
14485 else {
14486 continue;
14487 };
14488
14489 let context_range =
14490 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14491 runnable_rows.push((
14492 (runnable.buffer_id, row),
14493 RunnableTasks {
14494 templates: tasks,
14495 offset: snapshot
14496 .buffer_snapshot
14497 .anchor_before(runnable.run_range.start),
14498 context_range,
14499 column: point.column,
14500 extra_variables: runnable.extra_captures,
14501 },
14502 ));
14503 }
14504 runnable_rows
14505 })
14506 }
14507
14508 fn templates_with_tags(
14509 project: &Entity<Project>,
14510 runnable: &mut Runnable,
14511 cx: &mut App,
14512 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14513 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14514 let (worktree_id, file) = project
14515 .buffer_for_id(runnable.buffer, cx)
14516 .and_then(|buffer| buffer.read(cx).file())
14517 .map(|file| (file.worktree_id(cx), file.clone()))
14518 .unzip();
14519
14520 (
14521 project.task_store().read(cx).task_inventory().cloned(),
14522 worktree_id,
14523 file,
14524 )
14525 });
14526
14527 let tags = mem::take(&mut runnable.tags);
14528 let language = runnable.language.clone();
14529 cx.spawn(async move |cx| {
14530 let mut templates_with_tags = Vec::new();
14531 if let Some(inventory) = inventory {
14532 for RunnableTag(tag) in tags {
14533 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14534 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14535 }) else {
14536 return templates_with_tags;
14537 };
14538 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14539 move |(_, template)| {
14540 template.tags.iter().any(|source_tag| source_tag == &tag)
14541 },
14542 ));
14543 }
14544 }
14545 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14546
14547 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14548 // Strongest source wins; if we have worktree tag binding, prefer that to
14549 // global and language bindings;
14550 // if we have a global binding, prefer that to language binding.
14551 let first_mismatch = templates_with_tags
14552 .iter()
14553 .position(|(tag_source, _)| tag_source != leading_tag_source);
14554 if let Some(index) = first_mismatch {
14555 templates_with_tags.truncate(index);
14556 }
14557 }
14558
14559 templates_with_tags
14560 })
14561 }
14562
14563 pub fn move_to_enclosing_bracket(
14564 &mut self,
14565 _: &MoveToEnclosingBracket,
14566 window: &mut Window,
14567 cx: &mut Context<Self>,
14568 ) {
14569 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14570 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14571 s.move_offsets_with(|snapshot, selection| {
14572 let Some(enclosing_bracket_ranges) =
14573 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14574 else {
14575 return;
14576 };
14577
14578 let mut best_length = usize::MAX;
14579 let mut best_inside = false;
14580 let mut best_in_bracket_range = false;
14581 let mut best_destination = None;
14582 for (open, close) in enclosing_bracket_ranges {
14583 let close = close.to_inclusive();
14584 let length = close.end() - open.start;
14585 let inside = selection.start >= open.end && selection.end <= *close.start();
14586 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14587 || close.contains(&selection.head());
14588
14589 // If best is next to a bracket and current isn't, skip
14590 if !in_bracket_range && best_in_bracket_range {
14591 continue;
14592 }
14593
14594 // Prefer smaller lengths unless best is inside and current isn't
14595 if length > best_length && (best_inside || !inside) {
14596 continue;
14597 }
14598
14599 best_length = length;
14600 best_inside = inside;
14601 best_in_bracket_range = in_bracket_range;
14602 best_destination = Some(
14603 if close.contains(&selection.start) && close.contains(&selection.end) {
14604 if inside { open.end } else { open.start }
14605 } else if inside {
14606 *close.start()
14607 } else {
14608 *close.end()
14609 },
14610 );
14611 }
14612
14613 if let Some(destination) = best_destination {
14614 selection.collapse_to(destination, SelectionGoal::None);
14615 }
14616 })
14617 });
14618 }
14619
14620 pub fn undo_selection(
14621 &mut self,
14622 _: &UndoSelection,
14623 window: &mut Window,
14624 cx: &mut Context<Self>,
14625 ) {
14626 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14627 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14628 self.selection_history.mode = SelectionHistoryMode::Undoing;
14629 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14630 this.end_selection(window, cx);
14631 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14632 s.select_anchors(entry.selections.to_vec())
14633 });
14634 });
14635 self.selection_history.mode = SelectionHistoryMode::Normal;
14636
14637 self.select_next_state = entry.select_next_state;
14638 self.select_prev_state = entry.select_prev_state;
14639 self.add_selections_state = entry.add_selections_state;
14640 }
14641 }
14642
14643 pub fn redo_selection(
14644 &mut self,
14645 _: &RedoSelection,
14646 window: &mut Window,
14647 cx: &mut Context<Self>,
14648 ) {
14649 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14650 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14651 self.selection_history.mode = SelectionHistoryMode::Redoing;
14652 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14653 this.end_selection(window, cx);
14654 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14655 s.select_anchors(entry.selections.to_vec())
14656 });
14657 });
14658 self.selection_history.mode = SelectionHistoryMode::Normal;
14659
14660 self.select_next_state = entry.select_next_state;
14661 self.select_prev_state = entry.select_prev_state;
14662 self.add_selections_state = entry.add_selections_state;
14663 }
14664 }
14665
14666 pub fn expand_excerpts(
14667 &mut self,
14668 action: &ExpandExcerpts,
14669 _: &mut Window,
14670 cx: &mut Context<Self>,
14671 ) {
14672 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14673 }
14674
14675 pub fn expand_excerpts_down(
14676 &mut self,
14677 action: &ExpandExcerptsDown,
14678 _: &mut Window,
14679 cx: &mut Context<Self>,
14680 ) {
14681 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14682 }
14683
14684 pub fn expand_excerpts_up(
14685 &mut self,
14686 action: &ExpandExcerptsUp,
14687 _: &mut Window,
14688 cx: &mut Context<Self>,
14689 ) {
14690 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14691 }
14692
14693 pub fn expand_excerpts_for_direction(
14694 &mut self,
14695 lines: u32,
14696 direction: ExpandExcerptDirection,
14697
14698 cx: &mut Context<Self>,
14699 ) {
14700 let selections = self.selections.disjoint_anchors();
14701
14702 let lines = if lines == 0 {
14703 EditorSettings::get_global(cx).expand_excerpt_lines
14704 } else {
14705 lines
14706 };
14707
14708 self.buffer.update(cx, |buffer, cx| {
14709 let snapshot = buffer.snapshot(cx);
14710 let mut excerpt_ids = selections
14711 .iter()
14712 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14713 .collect::<Vec<_>>();
14714 excerpt_ids.sort();
14715 excerpt_ids.dedup();
14716 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14717 })
14718 }
14719
14720 pub fn expand_excerpt(
14721 &mut self,
14722 excerpt: ExcerptId,
14723 direction: ExpandExcerptDirection,
14724 window: &mut Window,
14725 cx: &mut Context<Self>,
14726 ) {
14727 let current_scroll_position = self.scroll_position(cx);
14728 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14729 let mut should_scroll_up = false;
14730
14731 if direction == ExpandExcerptDirection::Down {
14732 let multi_buffer = self.buffer.read(cx);
14733 let snapshot = multi_buffer.snapshot(cx);
14734 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14735 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14736 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14737 let buffer_snapshot = buffer.read(cx).snapshot();
14738 let excerpt_end_row =
14739 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14740 let last_row = buffer_snapshot.max_point().row;
14741 let lines_below = last_row.saturating_sub(excerpt_end_row);
14742 should_scroll_up = lines_below >= lines_to_expand;
14743 }
14744 }
14745 }
14746 }
14747
14748 self.buffer.update(cx, |buffer, cx| {
14749 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14750 });
14751
14752 if should_scroll_up {
14753 let new_scroll_position =
14754 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14755 self.set_scroll_position(new_scroll_position, window, cx);
14756 }
14757 }
14758
14759 pub fn go_to_singleton_buffer_point(
14760 &mut self,
14761 point: Point,
14762 window: &mut Window,
14763 cx: &mut Context<Self>,
14764 ) {
14765 self.go_to_singleton_buffer_range(point..point, window, cx);
14766 }
14767
14768 pub fn go_to_singleton_buffer_range(
14769 &mut self,
14770 range: Range<Point>,
14771 window: &mut Window,
14772 cx: &mut Context<Self>,
14773 ) {
14774 let multibuffer = self.buffer().read(cx);
14775 let Some(buffer) = multibuffer.as_singleton() else {
14776 return;
14777 };
14778 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14779 return;
14780 };
14781 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14782 return;
14783 };
14784 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
14785 s.select_anchor_ranges([start..end])
14786 });
14787 }
14788
14789 pub fn go_to_diagnostic(
14790 &mut self,
14791 _: &GoToDiagnostic,
14792 window: &mut Window,
14793 cx: &mut Context<Self>,
14794 ) {
14795 if !self.diagnostics_enabled() {
14796 return;
14797 }
14798 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14799 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14800 }
14801
14802 pub fn go_to_prev_diagnostic(
14803 &mut self,
14804 _: &GoToPreviousDiagnostic,
14805 window: &mut Window,
14806 cx: &mut Context<Self>,
14807 ) {
14808 if !self.diagnostics_enabled() {
14809 return;
14810 }
14811 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14812 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14813 }
14814
14815 pub fn go_to_diagnostic_impl(
14816 &mut self,
14817 direction: Direction,
14818 window: &mut Window,
14819 cx: &mut Context<Self>,
14820 ) {
14821 let buffer = self.buffer.read(cx).snapshot(cx);
14822 let selection = self.selections.newest::<usize>(cx);
14823
14824 let mut active_group_id = None;
14825 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14826 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14827 active_group_id = Some(active_group.group_id);
14828 }
14829 }
14830
14831 fn filtered(
14832 snapshot: EditorSnapshot,
14833 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14834 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14835 diagnostics
14836 .filter(|entry| entry.range.start != entry.range.end)
14837 .filter(|entry| !entry.diagnostic.is_unnecessary)
14838 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14839 }
14840
14841 let snapshot = self.snapshot(window, cx);
14842 let before = filtered(
14843 snapshot.clone(),
14844 buffer
14845 .diagnostics_in_range(0..selection.start)
14846 .filter(|entry| entry.range.start <= selection.start),
14847 );
14848 let after = filtered(
14849 snapshot,
14850 buffer
14851 .diagnostics_in_range(selection.start..buffer.len())
14852 .filter(|entry| entry.range.start >= selection.start),
14853 );
14854
14855 let mut found: Option<DiagnosticEntry<usize>> = None;
14856 if direction == Direction::Prev {
14857 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14858 {
14859 for diagnostic in prev_diagnostics.into_iter().rev() {
14860 if diagnostic.range.start != selection.start
14861 || active_group_id
14862 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14863 {
14864 found = Some(diagnostic);
14865 break 'outer;
14866 }
14867 }
14868 }
14869 } else {
14870 for diagnostic in after.chain(before) {
14871 if diagnostic.range.start != selection.start
14872 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14873 {
14874 found = Some(diagnostic);
14875 break;
14876 }
14877 }
14878 }
14879 let Some(next_diagnostic) = found else {
14880 return;
14881 };
14882
14883 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14884 return;
14885 };
14886 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14887 s.select_ranges(vec![
14888 next_diagnostic.range.start..next_diagnostic.range.start,
14889 ])
14890 });
14891 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14892 self.refresh_inline_completion(false, true, window, cx);
14893 }
14894
14895 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14896 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14897 let snapshot = self.snapshot(window, cx);
14898 let selection = self.selections.newest::<Point>(cx);
14899 self.go_to_hunk_before_or_after_position(
14900 &snapshot,
14901 selection.head(),
14902 Direction::Next,
14903 window,
14904 cx,
14905 );
14906 }
14907
14908 pub fn go_to_hunk_before_or_after_position(
14909 &mut self,
14910 snapshot: &EditorSnapshot,
14911 position: Point,
14912 direction: Direction,
14913 window: &mut Window,
14914 cx: &mut Context<Editor>,
14915 ) {
14916 let row = if direction == Direction::Next {
14917 self.hunk_after_position(snapshot, position)
14918 .map(|hunk| hunk.row_range.start)
14919 } else {
14920 self.hunk_before_position(snapshot, position)
14921 };
14922
14923 if let Some(row) = row {
14924 let destination = Point::new(row.0, 0);
14925 let autoscroll = Autoscroll::center();
14926
14927 self.unfold_ranges(&[destination..destination], false, false, cx);
14928 self.change_selections(Some(autoscroll), window, cx, |s| {
14929 s.select_ranges([destination..destination]);
14930 });
14931 }
14932 }
14933
14934 fn hunk_after_position(
14935 &mut self,
14936 snapshot: &EditorSnapshot,
14937 position: Point,
14938 ) -> Option<MultiBufferDiffHunk> {
14939 snapshot
14940 .buffer_snapshot
14941 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14942 .find(|hunk| hunk.row_range.start.0 > position.row)
14943 .or_else(|| {
14944 snapshot
14945 .buffer_snapshot
14946 .diff_hunks_in_range(Point::zero()..position)
14947 .find(|hunk| hunk.row_range.end.0 < position.row)
14948 })
14949 }
14950
14951 fn go_to_prev_hunk(
14952 &mut self,
14953 _: &GoToPreviousHunk,
14954 window: &mut Window,
14955 cx: &mut Context<Self>,
14956 ) {
14957 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14958 let snapshot = self.snapshot(window, cx);
14959 let selection = self.selections.newest::<Point>(cx);
14960 self.go_to_hunk_before_or_after_position(
14961 &snapshot,
14962 selection.head(),
14963 Direction::Prev,
14964 window,
14965 cx,
14966 );
14967 }
14968
14969 fn hunk_before_position(
14970 &mut self,
14971 snapshot: &EditorSnapshot,
14972 position: Point,
14973 ) -> Option<MultiBufferRow> {
14974 snapshot
14975 .buffer_snapshot
14976 .diff_hunk_before(position)
14977 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14978 }
14979
14980 fn go_to_next_change(
14981 &mut self,
14982 _: &GoToNextChange,
14983 window: &mut Window,
14984 cx: &mut Context<Self>,
14985 ) {
14986 if let Some(selections) = self
14987 .change_list
14988 .next_change(1, Direction::Next)
14989 .map(|s| s.to_vec())
14990 {
14991 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14992 let map = s.display_map();
14993 s.select_display_ranges(selections.iter().map(|a| {
14994 let point = a.to_display_point(&map);
14995 point..point
14996 }))
14997 })
14998 }
14999 }
15000
15001 fn go_to_previous_change(
15002 &mut self,
15003 _: &GoToPreviousChange,
15004 window: &mut Window,
15005 cx: &mut Context<Self>,
15006 ) {
15007 if let Some(selections) = self
15008 .change_list
15009 .next_change(1, Direction::Prev)
15010 .map(|s| s.to_vec())
15011 {
15012 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15013 let map = s.display_map();
15014 s.select_display_ranges(selections.iter().map(|a| {
15015 let point = a.to_display_point(&map);
15016 point..point
15017 }))
15018 })
15019 }
15020 }
15021
15022 fn go_to_line<T: 'static>(
15023 &mut self,
15024 position: Anchor,
15025 highlight_color: Option<Hsla>,
15026 window: &mut Window,
15027 cx: &mut Context<Self>,
15028 ) {
15029 let snapshot = self.snapshot(window, cx).display_snapshot;
15030 let position = position.to_point(&snapshot.buffer_snapshot);
15031 let start = snapshot
15032 .buffer_snapshot
15033 .clip_point(Point::new(position.row, 0), Bias::Left);
15034 let end = start + Point::new(1, 0);
15035 let start = snapshot.buffer_snapshot.anchor_before(start);
15036 let end = snapshot.buffer_snapshot.anchor_before(end);
15037
15038 self.highlight_rows::<T>(
15039 start..end,
15040 highlight_color
15041 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15042 Default::default(),
15043 cx,
15044 );
15045
15046 if self.buffer.read(cx).is_singleton() {
15047 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15048 }
15049 }
15050
15051 pub fn go_to_definition(
15052 &mut self,
15053 _: &GoToDefinition,
15054 window: &mut Window,
15055 cx: &mut Context<Self>,
15056 ) -> Task<Result<Navigated>> {
15057 let definition =
15058 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15059 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15060 cx.spawn_in(window, async move |editor, cx| {
15061 if definition.await? == Navigated::Yes {
15062 return Ok(Navigated::Yes);
15063 }
15064 match fallback_strategy {
15065 GoToDefinitionFallback::None => Ok(Navigated::No),
15066 GoToDefinitionFallback::FindAllReferences => {
15067 match editor.update_in(cx, |editor, window, cx| {
15068 editor.find_all_references(&FindAllReferences, window, cx)
15069 })? {
15070 Some(references) => references.await,
15071 None => Ok(Navigated::No),
15072 }
15073 }
15074 }
15075 })
15076 }
15077
15078 pub fn go_to_declaration(
15079 &mut self,
15080 _: &GoToDeclaration,
15081 window: &mut Window,
15082 cx: &mut Context<Self>,
15083 ) -> Task<Result<Navigated>> {
15084 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15085 }
15086
15087 pub fn go_to_declaration_split(
15088 &mut self,
15089 _: &GoToDeclaration,
15090 window: &mut Window,
15091 cx: &mut Context<Self>,
15092 ) -> Task<Result<Navigated>> {
15093 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15094 }
15095
15096 pub fn go_to_implementation(
15097 &mut self,
15098 _: &GoToImplementation,
15099 window: &mut Window,
15100 cx: &mut Context<Self>,
15101 ) -> Task<Result<Navigated>> {
15102 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15103 }
15104
15105 pub fn go_to_implementation_split(
15106 &mut self,
15107 _: &GoToImplementationSplit,
15108 window: &mut Window,
15109 cx: &mut Context<Self>,
15110 ) -> Task<Result<Navigated>> {
15111 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15112 }
15113
15114 pub fn go_to_type_definition(
15115 &mut self,
15116 _: &GoToTypeDefinition,
15117 window: &mut Window,
15118 cx: &mut Context<Self>,
15119 ) -> Task<Result<Navigated>> {
15120 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15121 }
15122
15123 pub fn go_to_definition_split(
15124 &mut self,
15125 _: &GoToDefinitionSplit,
15126 window: &mut Window,
15127 cx: &mut Context<Self>,
15128 ) -> Task<Result<Navigated>> {
15129 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15130 }
15131
15132 pub fn go_to_type_definition_split(
15133 &mut self,
15134 _: &GoToTypeDefinitionSplit,
15135 window: &mut Window,
15136 cx: &mut Context<Self>,
15137 ) -> Task<Result<Navigated>> {
15138 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15139 }
15140
15141 fn go_to_definition_of_kind(
15142 &mut self,
15143 kind: GotoDefinitionKind,
15144 split: bool,
15145 window: &mut Window,
15146 cx: &mut Context<Self>,
15147 ) -> Task<Result<Navigated>> {
15148 let Some(provider) = self.semantics_provider.clone() else {
15149 return Task::ready(Ok(Navigated::No));
15150 };
15151 let head = self.selections.newest::<usize>(cx).head();
15152 let buffer = self.buffer.read(cx);
15153 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
15154 text_anchor
15155 } else {
15156 return Task::ready(Ok(Navigated::No));
15157 };
15158
15159 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15160 return Task::ready(Ok(Navigated::No));
15161 };
15162
15163 cx.spawn_in(window, async move |editor, cx| {
15164 let definitions = definitions.await?;
15165 let navigated = editor
15166 .update_in(cx, |editor, window, cx| {
15167 editor.navigate_to_hover_links(
15168 Some(kind),
15169 definitions
15170 .into_iter()
15171 .filter(|location| {
15172 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15173 })
15174 .map(HoverLink::Text)
15175 .collect::<Vec<_>>(),
15176 split,
15177 window,
15178 cx,
15179 )
15180 })?
15181 .await?;
15182 anyhow::Ok(navigated)
15183 })
15184 }
15185
15186 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15187 let selection = self.selections.newest_anchor();
15188 let head = selection.head();
15189 let tail = selection.tail();
15190
15191 let Some((buffer, start_position)) =
15192 self.buffer.read(cx).text_anchor_for_position(head, cx)
15193 else {
15194 return;
15195 };
15196
15197 let end_position = if head != tail {
15198 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15199 return;
15200 };
15201 Some(pos)
15202 } else {
15203 None
15204 };
15205
15206 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15207 let url = if let Some(end_pos) = end_position {
15208 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15209 } else {
15210 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15211 };
15212
15213 if let Some(url) = url {
15214 editor.update(cx, |_, cx| {
15215 cx.open_url(&url);
15216 })
15217 } else {
15218 Ok(())
15219 }
15220 });
15221
15222 url_finder.detach();
15223 }
15224
15225 pub fn open_selected_filename(
15226 &mut self,
15227 _: &OpenSelectedFilename,
15228 window: &mut Window,
15229 cx: &mut Context<Self>,
15230 ) {
15231 let Some(workspace) = self.workspace() else {
15232 return;
15233 };
15234
15235 let position = self.selections.newest_anchor().head();
15236
15237 let Some((buffer, buffer_position)) =
15238 self.buffer.read(cx).text_anchor_for_position(position, cx)
15239 else {
15240 return;
15241 };
15242
15243 let project = self.project.clone();
15244
15245 cx.spawn_in(window, async move |_, cx| {
15246 let result = find_file(&buffer, project, buffer_position, cx).await;
15247
15248 if let Some((_, path)) = result {
15249 workspace
15250 .update_in(cx, |workspace, window, cx| {
15251 workspace.open_resolved_path(path, window, cx)
15252 })?
15253 .await?;
15254 }
15255 anyhow::Ok(())
15256 })
15257 .detach();
15258 }
15259
15260 pub(crate) fn navigate_to_hover_links(
15261 &mut self,
15262 kind: Option<GotoDefinitionKind>,
15263 mut definitions: Vec<HoverLink>,
15264 split: bool,
15265 window: &mut Window,
15266 cx: &mut Context<Editor>,
15267 ) -> Task<Result<Navigated>> {
15268 // If there is one definition, just open it directly
15269 if definitions.len() == 1 {
15270 let definition = definitions.pop().unwrap();
15271
15272 enum TargetTaskResult {
15273 Location(Option<Location>),
15274 AlreadyNavigated,
15275 }
15276
15277 let target_task = match definition {
15278 HoverLink::Text(link) => {
15279 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
15280 }
15281 HoverLink::InlayHint(lsp_location, server_id) => {
15282 let computation =
15283 self.compute_target_location(lsp_location, server_id, window, cx);
15284 cx.background_spawn(async move {
15285 let location = computation.await?;
15286 Ok(TargetTaskResult::Location(location))
15287 })
15288 }
15289 HoverLink::Url(url) => {
15290 cx.open_url(&url);
15291 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
15292 }
15293 HoverLink::File(path) => {
15294 if let Some(workspace) = self.workspace() {
15295 cx.spawn_in(window, async move |_, cx| {
15296 workspace
15297 .update_in(cx, |workspace, window, cx| {
15298 workspace.open_resolved_path(path, window, cx)
15299 })?
15300 .await
15301 .map(|_| TargetTaskResult::AlreadyNavigated)
15302 })
15303 } else {
15304 Task::ready(Ok(TargetTaskResult::Location(None)))
15305 }
15306 }
15307 };
15308 cx.spawn_in(window, async move |editor, cx| {
15309 let target = match target_task.await.context("target resolution task")? {
15310 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
15311 TargetTaskResult::Location(None) => return Ok(Navigated::No),
15312 TargetTaskResult::Location(Some(target)) => target,
15313 };
15314
15315 editor.update_in(cx, |editor, window, cx| {
15316 let Some(workspace) = editor.workspace() else {
15317 return Navigated::No;
15318 };
15319 let pane = workspace.read(cx).active_pane().clone();
15320
15321 let range = target.range.to_point(target.buffer.read(cx));
15322 let range = editor.range_for_match(&range);
15323 let range = collapse_multiline_range(range);
15324
15325 if !split
15326 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15327 {
15328 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15329 } else {
15330 window.defer(cx, move |window, cx| {
15331 let target_editor: Entity<Self> =
15332 workspace.update(cx, |workspace, cx| {
15333 let pane = if split {
15334 workspace.adjacent_pane(window, cx)
15335 } else {
15336 workspace.active_pane().clone()
15337 };
15338
15339 workspace.open_project_item(
15340 pane,
15341 target.buffer.clone(),
15342 true,
15343 true,
15344 window,
15345 cx,
15346 )
15347 });
15348 target_editor.update(cx, |target_editor, cx| {
15349 // When selecting a definition in a different buffer, disable the nav history
15350 // to avoid creating a history entry at the previous cursor location.
15351 pane.update(cx, |pane, _| pane.disable_history());
15352 target_editor.go_to_singleton_buffer_range(range, window, cx);
15353 pane.update(cx, |pane, _| pane.enable_history());
15354 });
15355 });
15356 }
15357 Navigated::Yes
15358 })
15359 })
15360 } else if !definitions.is_empty() {
15361 cx.spawn_in(window, async move |editor, cx| {
15362 let (title, location_tasks, workspace) = editor
15363 .update_in(cx, |editor, window, cx| {
15364 let tab_kind = match kind {
15365 Some(GotoDefinitionKind::Implementation) => "Implementations",
15366 _ => "Definitions",
15367 };
15368 let title = definitions
15369 .iter()
15370 .find_map(|definition| match definition {
15371 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15372 let buffer = origin.buffer.read(cx);
15373 format!(
15374 "{} for {}",
15375 tab_kind,
15376 buffer
15377 .text_for_range(origin.range.clone())
15378 .collect::<String>()
15379 )
15380 }),
15381 HoverLink::InlayHint(_, _) => None,
15382 HoverLink::Url(_) => None,
15383 HoverLink::File(_) => None,
15384 })
15385 .unwrap_or(tab_kind.to_string());
15386 let location_tasks = definitions
15387 .into_iter()
15388 .map(|definition| match definition {
15389 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15390 HoverLink::InlayHint(lsp_location, server_id) => editor
15391 .compute_target_location(lsp_location, server_id, window, cx),
15392 HoverLink::Url(_) => Task::ready(Ok(None)),
15393 HoverLink::File(_) => Task::ready(Ok(None)),
15394 })
15395 .collect::<Vec<_>>();
15396 (title, location_tasks, editor.workspace().clone())
15397 })
15398 .context("location tasks preparation")?;
15399
15400 let locations: Vec<Location> = future::join_all(location_tasks)
15401 .await
15402 .into_iter()
15403 .filter_map(|location| location.transpose())
15404 .collect::<Result<_>>()
15405 .context("location tasks")?;
15406
15407 if locations.is_empty() {
15408 return Ok(Navigated::No);
15409 }
15410
15411 let Some(workspace) = workspace else {
15412 return Ok(Navigated::No);
15413 };
15414
15415 let opened = workspace
15416 .update_in(cx, |workspace, window, cx| {
15417 Self::open_locations_in_multibuffer(
15418 workspace,
15419 locations,
15420 title,
15421 split,
15422 MultibufferSelectionMode::First,
15423 window,
15424 cx,
15425 )
15426 })
15427 .ok();
15428
15429 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15430 })
15431 } else {
15432 Task::ready(Ok(Navigated::No))
15433 }
15434 }
15435
15436 fn compute_target_location(
15437 &self,
15438 lsp_location: lsp::Location,
15439 server_id: LanguageServerId,
15440 window: &mut Window,
15441 cx: &mut Context<Self>,
15442 ) -> Task<anyhow::Result<Option<Location>>> {
15443 let Some(project) = self.project.clone() else {
15444 return Task::ready(Ok(None));
15445 };
15446
15447 cx.spawn_in(window, async move |editor, cx| {
15448 let location_task = editor.update(cx, |_, cx| {
15449 project.update(cx, |project, cx| {
15450 let language_server_name = project
15451 .language_server_statuses(cx)
15452 .find(|(id, _)| server_id == *id)
15453 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15454 language_server_name.map(|language_server_name| {
15455 project.open_local_buffer_via_lsp(
15456 lsp_location.uri.clone(),
15457 server_id,
15458 language_server_name,
15459 cx,
15460 )
15461 })
15462 })
15463 })?;
15464 let location = match location_task {
15465 Some(task) => Some({
15466 let target_buffer_handle = task.await.context("open local buffer")?;
15467 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15468 let target_start = target_buffer
15469 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15470 let target_end = target_buffer
15471 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15472 target_buffer.anchor_after(target_start)
15473 ..target_buffer.anchor_before(target_end)
15474 })?;
15475 Location {
15476 buffer: target_buffer_handle,
15477 range,
15478 }
15479 }),
15480 None => None,
15481 };
15482 Ok(location)
15483 })
15484 }
15485
15486 pub fn find_all_references(
15487 &mut self,
15488 _: &FindAllReferences,
15489 window: &mut Window,
15490 cx: &mut Context<Self>,
15491 ) -> Option<Task<Result<Navigated>>> {
15492 let selection = self.selections.newest::<usize>(cx);
15493 let multi_buffer = self.buffer.read(cx);
15494 let head = selection.head();
15495
15496 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15497 let head_anchor = multi_buffer_snapshot.anchor_at(
15498 head,
15499 if head < selection.tail() {
15500 Bias::Right
15501 } else {
15502 Bias::Left
15503 },
15504 );
15505
15506 match self
15507 .find_all_references_task_sources
15508 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15509 {
15510 Ok(_) => {
15511 log::info!(
15512 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15513 );
15514 return None;
15515 }
15516 Err(i) => {
15517 self.find_all_references_task_sources.insert(i, head_anchor);
15518 }
15519 }
15520
15521 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15522 let workspace = self.workspace()?;
15523 let project = workspace.read(cx).project().clone();
15524 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15525 Some(cx.spawn_in(window, async move |editor, cx| {
15526 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15527 if let Ok(i) = editor
15528 .find_all_references_task_sources
15529 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15530 {
15531 editor.find_all_references_task_sources.remove(i);
15532 }
15533 });
15534
15535 let locations = references.await?;
15536 if locations.is_empty() {
15537 return anyhow::Ok(Navigated::No);
15538 }
15539
15540 workspace.update_in(cx, |workspace, window, cx| {
15541 let title = locations
15542 .first()
15543 .as_ref()
15544 .map(|location| {
15545 let buffer = location.buffer.read(cx);
15546 format!(
15547 "References to `{}`",
15548 buffer
15549 .text_for_range(location.range.clone())
15550 .collect::<String>()
15551 )
15552 })
15553 .unwrap();
15554 Self::open_locations_in_multibuffer(
15555 workspace,
15556 locations,
15557 title,
15558 false,
15559 MultibufferSelectionMode::First,
15560 window,
15561 cx,
15562 );
15563 Navigated::Yes
15564 })
15565 }))
15566 }
15567
15568 /// Opens a multibuffer with the given project locations in it
15569 pub fn open_locations_in_multibuffer(
15570 workspace: &mut Workspace,
15571 mut locations: Vec<Location>,
15572 title: String,
15573 split: bool,
15574 multibuffer_selection_mode: MultibufferSelectionMode,
15575 window: &mut Window,
15576 cx: &mut Context<Workspace>,
15577 ) {
15578 if locations.is_empty() {
15579 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
15580 return;
15581 }
15582
15583 // If there are multiple definitions, open them in a multibuffer
15584 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15585 let mut locations = locations.into_iter().peekable();
15586 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15587 let capability = workspace.project().read(cx).capability();
15588
15589 let excerpt_buffer = cx.new(|cx| {
15590 let mut multibuffer = MultiBuffer::new(capability);
15591 while let Some(location) = locations.next() {
15592 let buffer = location.buffer.read(cx);
15593 let mut ranges_for_buffer = Vec::new();
15594 let range = location.range.to_point(buffer);
15595 ranges_for_buffer.push(range.clone());
15596
15597 while let Some(next_location) = locations.peek() {
15598 if next_location.buffer == location.buffer {
15599 ranges_for_buffer.push(next_location.range.to_point(buffer));
15600 locations.next();
15601 } else {
15602 break;
15603 }
15604 }
15605
15606 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15607 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15608 PathKey::for_buffer(&location.buffer, cx),
15609 location.buffer.clone(),
15610 ranges_for_buffer,
15611 DEFAULT_MULTIBUFFER_CONTEXT,
15612 cx,
15613 );
15614 ranges.extend(new_ranges)
15615 }
15616
15617 multibuffer.with_title(title)
15618 });
15619
15620 let editor = cx.new(|cx| {
15621 Editor::for_multibuffer(
15622 excerpt_buffer,
15623 Some(workspace.project().clone()),
15624 window,
15625 cx,
15626 )
15627 });
15628 editor.update(cx, |editor, cx| {
15629 match multibuffer_selection_mode {
15630 MultibufferSelectionMode::First => {
15631 if let Some(first_range) = ranges.first() {
15632 editor.change_selections(None, window, cx, |selections| {
15633 selections.clear_disjoint();
15634 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
15635 });
15636 }
15637 editor.highlight_background::<Self>(
15638 &ranges,
15639 |theme| theme.colors().editor_highlighted_line_background,
15640 cx,
15641 );
15642 }
15643 MultibufferSelectionMode::All => {
15644 editor.change_selections(None, window, cx, |selections| {
15645 selections.clear_disjoint();
15646 selections.select_anchor_ranges(ranges);
15647 });
15648 }
15649 }
15650 editor.register_buffers_with_language_servers(cx);
15651 });
15652
15653 let item = Box::new(editor);
15654 let item_id = item.item_id();
15655
15656 if split {
15657 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15658 } else {
15659 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15660 let (preview_item_id, preview_item_idx) =
15661 workspace.active_pane().read_with(cx, |pane, _| {
15662 (pane.preview_item_id(), pane.preview_item_idx())
15663 });
15664
15665 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15666
15667 if let Some(preview_item_id) = preview_item_id {
15668 workspace.active_pane().update(cx, |pane, cx| {
15669 pane.remove_item(preview_item_id, false, false, window, cx);
15670 });
15671 }
15672 } else {
15673 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15674 }
15675 }
15676 workspace.active_pane().update(cx, |pane, cx| {
15677 pane.set_preview_item_id(Some(item_id), cx);
15678 });
15679 }
15680
15681 pub fn rename(
15682 &mut self,
15683 _: &Rename,
15684 window: &mut Window,
15685 cx: &mut Context<Self>,
15686 ) -> Option<Task<Result<()>>> {
15687 use language::ToOffset as _;
15688
15689 let provider = self.semantics_provider.clone()?;
15690 let selection = self.selections.newest_anchor().clone();
15691 let (cursor_buffer, cursor_buffer_position) = self
15692 .buffer
15693 .read(cx)
15694 .text_anchor_for_position(selection.head(), cx)?;
15695 let (tail_buffer, cursor_buffer_position_end) = self
15696 .buffer
15697 .read(cx)
15698 .text_anchor_for_position(selection.tail(), cx)?;
15699 if tail_buffer != cursor_buffer {
15700 return None;
15701 }
15702
15703 let snapshot = cursor_buffer.read(cx).snapshot();
15704 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15705 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15706 let prepare_rename = provider
15707 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15708 .unwrap_or_else(|| Task::ready(Ok(None)));
15709 drop(snapshot);
15710
15711 Some(cx.spawn_in(window, async move |this, cx| {
15712 let rename_range = if let Some(range) = prepare_rename.await? {
15713 Some(range)
15714 } else {
15715 this.update(cx, |this, cx| {
15716 let buffer = this.buffer.read(cx).snapshot(cx);
15717 let mut buffer_highlights = this
15718 .document_highlights_for_position(selection.head(), &buffer)
15719 .filter(|highlight| {
15720 highlight.start.excerpt_id == selection.head().excerpt_id
15721 && highlight.end.excerpt_id == selection.head().excerpt_id
15722 });
15723 buffer_highlights
15724 .next()
15725 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15726 })?
15727 };
15728 if let Some(rename_range) = rename_range {
15729 this.update_in(cx, |this, window, cx| {
15730 let snapshot = cursor_buffer.read(cx).snapshot();
15731 let rename_buffer_range = rename_range.to_offset(&snapshot);
15732 let cursor_offset_in_rename_range =
15733 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15734 let cursor_offset_in_rename_range_end =
15735 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15736
15737 this.take_rename(false, window, cx);
15738 let buffer = this.buffer.read(cx).read(cx);
15739 let cursor_offset = selection.head().to_offset(&buffer);
15740 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15741 let rename_end = rename_start + rename_buffer_range.len();
15742 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15743 let mut old_highlight_id = None;
15744 let old_name: Arc<str> = buffer
15745 .chunks(rename_start..rename_end, true)
15746 .map(|chunk| {
15747 if old_highlight_id.is_none() {
15748 old_highlight_id = chunk.syntax_highlight_id;
15749 }
15750 chunk.text
15751 })
15752 .collect::<String>()
15753 .into();
15754
15755 drop(buffer);
15756
15757 // Position the selection in the rename editor so that it matches the current selection.
15758 this.show_local_selections = false;
15759 let rename_editor = cx.new(|cx| {
15760 let mut editor = Editor::single_line(window, cx);
15761 editor.buffer.update(cx, |buffer, cx| {
15762 buffer.edit([(0..0, old_name.clone())], None, cx)
15763 });
15764 let rename_selection_range = match cursor_offset_in_rename_range
15765 .cmp(&cursor_offset_in_rename_range_end)
15766 {
15767 Ordering::Equal => {
15768 editor.select_all(&SelectAll, window, cx);
15769 return editor;
15770 }
15771 Ordering::Less => {
15772 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15773 }
15774 Ordering::Greater => {
15775 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15776 }
15777 };
15778 if rename_selection_range.end > old_name.len() {
15779 editor.select_all(&SelectAll, window, cx);
15780 } else {
15781 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15782 s.select_ranges([rename_selection_range]);
15783 });
15784 }
15785 editor
15786 });
15787 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15788 if e == &EditorEvent::Focused {
15789 cx.emit(EditorEvent::FocusedIn)
15790 }
15791 })
15792 .detach();
15793
15794 let write_highlights =
15795 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
15796 let read_highlights =
15797 this.clear_background_highlights::<DocumentHighlightRead>(cx);
15798 let ranges = write_highlights
15799 .iter()
15800 .flat_map(|(_, ranges)| ranges.iter())
15801 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
15802 .cloned()
15803 .collect();
15804
15805 this.highlight_text::<Rename>(
15806 ranges,
15807 HighlightStyle {
15808 fade_out: Some(0.6),
15809 ..Default::default()
15810 },
15811 cx,
15812 );
15813 let rename_focus_handle = rename_editor.focus_handle(cx);
15814 window.focus(&rename_focus_handle);
15815 let block_id = this.insert_blocks(
15816 [BlockProperties {
15817 style: BlockStyle::Flex,
15818 placement: BlockPlacement::Below(range.start),
15819 height: Some(1),
15820 render: Arc::new({
15821 let rename_editor = rename_editor.clone();
15822 move |cx: &mut BlockContext| {
15823 let mut text_style = cx.editor_style.text.clone();
15824 if let Some(highlight_style) = old_highlight_id
15825 .and_then(|h| h.style(&cx.editor_style.syntax))
15826 {
15827 text_style = text_style.highlight(highlight_style);
15828 }
15829 div()
15830 .block_mouse_except_scroll()
15831 .pl(cx.anchor_x)
15832 .child(EditorElement::new(
15833 &rename_editor,
15834 EditorStyle {
15835 background: cx.theme().system().transparent,
15836 local_player: cx.editor_style.local_player,
15837 text: text_style,
15838 scrollbar_width: cx.editor_style.scrollbar_width,
15839 syntax: cx.editor_style.syntax.clone(),
15840 status: cx.editor_style.status.clone(),
15841 inlay_hints_style: HighlightStyle {
15842 font_weight: Some(FontWeight::BOLD),
15843 ..make_inlay_hints_style(cx.app)
15844 },
15845 inline_completion_styles: make_suggestion_styles(
15846 cx.app,
15847 ),
15848 ..EditorStyle::default()
15849 },
15850 ))
15851 .into_any_element()
15852 }
15853 }),
15854 priority: 0,
15855 render_in_minimap: true,
15856 }],
15857 Some(Autoscroll::fit()),
15858 cx,
15859 )[0];
15860 this.pending_rename = Some(RenameState {
15861 range,
15862 old_name,
15863 editor: rename_editor,
15864 block_id,
15865 });
15866 })?;
15867 }
15868
15869 Ok(())
15870 }))
15871 }
15872
15873 pub fn confirm_rename(
15874 &mut self,
15875 _: &ConfirmRename,
15876 window: &mut Window,
15877 cx: &mut Context<Self>,
15878 ) -> Option<Task<Result<()>>> {
15879 let rename = self.take_rename(false, window, cx)?;
15880 let workspace = self.workspace()?.downgrade();
15881 let (buffer, start) = self
15882 .buffer
15883 .read(cx)
15884 .text_anchor_for_position(rename.range.start, cx)?;
15885 let (end_buffer, _) = self
15886 .buffer
15887 .read(cx)
15888 .text_anchor_for_position(rename.range.end, cx)?;
15889 if buffer != end_buffer {
15890 return None;
15891 }
15892
15893 let old_name = rename.old_name;
15894 let new_name = rename.editor.read(cx).text(cx);
15895
15896 let rename = self.semantics_provider.as_ref()?.perform_rename(
15897 &buffer,
15898 start,
15899 new_name.clone(),
15900 cx,
15901 )?;
15902
15903 Some(cx.spawn_in(window, async move |editor, cx| {
15904 let project_transaction = rename.await?;
15905 Self::open_project_transaction(
15906 &editor,
15907 workspace,
15908 project_transaction,
15909 format!("Rename: {} → {}", old_name, new_name),
15910 cx,
15911 )
15912 .await?;
15913
15914 editor.update(cx, |editor, cx| {
15915 editor.refresh_document_highlights(cx);
15916 })?;
15917 Ok(())
15918 }))
15919 }
15920
15921 fn take_rename(
15922 &mut self,
15923 moving_cursor: bool,
15924 window: &mut Window,
15925 cx: &mut Context<Self>,
15926 ) -> Option<RenameState> {
15927 let rename = self.pending_rename.take()?;
15928 if rename.editor.focus_handle(cx).is_focused(window) {
15929 window.focus(&self.focus_handle);
15930 }
15931
15932 self.remove_blocks(
15933 [rename.block_id].into_iter().collect(),
15934 Some(Autoscroll::fit()),
15935 cx,
15936 );
15937 self.clear_highlights::<Rename>(cx);
15938 self.show_local_selections = true;
15939
15940 if moving_cursor {
15941 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15942 editor.selections.newest::<usize>(cx).head()
15943 });
15944
15945 // Update the selection to match the position of the selection inside
15946 // the rename editor.
15947 let snapshot = self.buffer.read(cx).read(cx);
15948 let rename_range = rename.range.to_offset(&snapshot);
15949 let cursor_in_editor = snapshot
15950 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15951 .min(rename_range.end);
15952 drop(snapshot);
15953
15954 self.change_selections(None, window, cx, |s| {
15955 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15956 });
15957 } else {
15958 self.refresh_document_highlights(cx);
15959 }
15960
15961 Some(rename)
15962 }
15963
15964 pub fn pending_rename(&self) -> Option<&RenameState> {
15965 self.pending_rename.as_ref()
15966 }
15967
15968 fn format(
15969 &mut self,
15970 _: &Format,
15971 window: &mut Window,
15972 cx: &mut Context<Self>,
15973 ) -> Option<Task<Result<()>>> {
15974 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15975
15976 let project = match &self.project {
15977 Some(project) => project.clone(),
15978 None => return None,
15979 };
15980
15981 Some(self.perform_format(
15982 project,
15983 FormatTrigger::Manual,
15984 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
15985 window,
15986 cx,
15987 ))
15988 }
15989
15990 fn format_selections(
15991 &mut self,
15992 _: &FormatSelections,
15993 window: &mut Window,
15994 cx: &mut Context<Self>,
15995 ) -> Option<Task<Result<()>>> {
15996 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15997
15998 let project = match &self.project {
15999 Some(project) => project.clone(),
16000 None => return None,
16001 };
16002
16003 let ranges = self
16004 .selections
16005 .all_adjusted(cx)
16006 .into_iter()
16007 .map(|selection| selection.range())
16008 .collect_vec();
16009
16010 Some(self.perform_format(
16011 project,
16012 FormatTrigger::Manual,
16013 FormatTarget::Ranges(ranges),
16014 window,
16015 cx,
16016 ))
16017 }
16018
16019 fn perform_format(
16020 &mut self,
16021 project: Entity<Project>,
16022 trigger: FormatTrigger,
16023 target: FormatTarget,
16024 window: &mut Window,
16025 cx: &mut Context<Self>,
16026 ) -> Task<Result<()>> {
16027 let buffer = self.buffer.clone();
16028 let (buffers, target) = match target {
16029 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16030 FormatTarget::Ranges(selection_ranges) => {
16031 let multi_buffer = buffer.read(cx);
16032 let snapshot = multi_buffer.read(cx);
16033 let mut buffers = HashSet::default();
16034 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16035 BTreeMap::new();
16036 for selection_range in selection_ranges {
16037 for (buffer, buffer_range, _) in
16038 snapshot.range_to_buffer_ranges(selection_range)
16039 {
16040 let buffer_id = buffer.remote_id();
16041 let start = buffer.anchor_before(buffer_range.start);
16042 let end = buffer.anchor_after(buffer_range.end);
16043 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16044 buffer_id_to_ranges
16045 .entry(buffer_id)
16046 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16047 .or_insert_with(|| vec![start..end]);
16048 }
16049 }
16050 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16051 }
16052 };
16053
16054 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16055 let selections_prev = transaction_id_prev
16056 .and_then(|transaction_id_prev| {
16057 // default to selections as they were after the last edit, if we have them,
16058 // instead of how they are now.
16059 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16060 // will take you back to where you made the last edit, instead of staying where you scrolled
16061 self.selection_history
16062 .transaction(transaction_id_prev)
16063 .map(|t| t.0.clone())
16064 })
16065 .unwrap_or_else(|| {
16066 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16067 self.selections.disjoint_anchors()
16068 });
16069
16070 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16071 let format = project.update(cx, |project, cx| {
16072 project.format(buffers, target, true, trigger, cx)
16073 });
16074
16075 cx.spawn_in(window, async move |editor, cx| {
16076 let transaction = futures::select_biased! {
16077 transaction = format.log_err().fuse() => transaction,
16078 () = timeout => {
16079 log::warn!("timed out waiting for formatting");
16080 None
16081 }
16082 };
16083
16084 buffer
16085 .update(cx, |buffer, cx| {
16086 if let Some(transaction) = transaction {
16087 if !buffer.is_singleton() {
16088 buffer.push_transaction(&transaction.0, cx);
16089 }
16090 }
16091 cx.notify();
16092 })
16093 .ok();
16094
16095 if let Some(transaction_id_now) =
16096 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16097 {
16098 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16099 if has_new_transaction {
16100 _ = editor.update(cx, |editor, _| {
16101 editor
16102 .selection_history
16103 .insert_transaction(transaction_id_now, selections_prev);
16104 });
16105 }
16106 }
16107
16108 Ok(())
16109 })
16110 }
16111
16112 fn organize_imports(
16113 &mut self,
16114 _: &OrganizeImports,
16115 window: &mut Window,
16116 cx: &mut Context<Self>,
16117 ) -> Option<Task<Result<()>>> {
16118 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16119 let project = match &self.project {
16120 Some(project) => project.clone(),
16121 None => return None,
16122 };
16123 Some(self.perform_code_action_kind(
16124 project,
16125 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16126 window,
16127 cx,
16128 ))
16129 }
16130
16131 fn perform_code_action_kind(
16132 &mut self,
16133 project: Entity<Project>,
16134 kind: CodeActionKind,
16135 window: &mut Window,
16136 cx: &mut Context<Self>,
16137 ) -> Task<Result<()>> {
16138 let buffer = self.buffer.clone();
16139 let buffers = buffer.read(cx).all_buffers();
16140 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16141 let apply_action = project.update(cx, |project, cx| {
16142 project.apply_code_action_kind(buffers, kind, true, cx)
16143 });
16144 cx.spawn_in(window, async move |_, cx| {
16145 let transaction = futures::select_biased! {
16146 () = timeout => {
16147 log::warn!("timed out waiting for executing code action");
16148 None
16149 }
16150 transaction = apply_action.log_err().fuse() => transaction,
16151 };
16152 buffer
16153 .update(cx, |buffer, cx| {
16154 // check if we need this
16155 if let Some(transaction) = transaction {
16156 if !buffer.is_singleton() {
16157 buffer.push_transaction(&transaction.0, cx);
16158 }
16159 }
16160 cx.notify();
16161 })
16162 .ok();
16163 Ok(())
16164 })
16165 }
16166
16167 fn restart_language_server(
16168 &mut self,
16169 _: &RestartLanguageServer,
16170 _: &mut Window,
16171 cx: &mut Context<Self>,
16172 ) {
16173 if let Some(project) = self.project.clone() {
16174 self.buffer.update(cx, |multi_buffer, cx| {
16175 project.update(cx, |project, cx| {
16176 project.restart_language_servers_for_buffers(
16177 multi_buffer.all_buffers().into_iter().collect(),
16178 cx,
16179 );
16180 });
16181 })
16182 }
16183 }
16184
16185 fn stop_language_server(
16186 &mut self,
16187 _: &StopLanguageServer,
16188 _: &mut Window,
16189 cx: &mut Context<Self>,
16190 ) {
16191 if let Some(project) = self.project.clone() {
16192 self.buffer.update(cx, |multi_buffer, cx| {
16193 project.update(cx, |project, cx| {
16194 project.stop_language_servers_for_buffers(
16195 multi_buffer.all_buffers().into_iter().collect(),
16196 cx,
16197 );
16198 cx.emit(project::Event::RefreshInlayHints);
16199 });
16200 });
16201 }
16202 }
16203
16204 fn cancel_language_server_work(
16205 workspace: &mut Workspace,
16206 _: &actions::CancelLanguageServerWork,
16207 _: &mut Window,
16208 cx: &mut Context<Workspace>,
16209 ) {
16210 let project = workspace.project();
16211 let buffers = workspace
16212 .active_item(cx)
16213 .and_then(|item| item.act_as::<Editor>(cx))
16214 .map_or(HashSet::default(), |editor| {
16215 editor.read(cx).buffer.read(cx).all_buffers()
16216 });
16217 project.update(cx, |project, cx| {
16218 project.cancel_language_server_work_for_buffers(buffers, cx);
16219 });
16220 }
16221
16222 fn show_character_palette(
16223 &mut self,
16224 _: &ShowCharacterPalette,
16225 window: &mut Window,
16226 _: &mut Context<Self>,
16227 ) {
16228 window.show_character_palette();
16229 }
16230
16231 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16232 if !self.diagnostics_enabled() {
16233 return;
16234 }
16235
16236 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16237 let buffer = self.buffer.read(cx).snapshot(cx);
16238 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16239 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16240 let is_valid = buffer
16241 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16242 .any(|entry| {
16243 entry.diagnostic.is_primary
16244 && !entry.range.is_empty()
16245 && entry.range.start == primary_range_start
16246 && entry.diagnostic.message == active_diagnostics.active_message
16247 });
16248
16249 if !is_valid {
16250 self.dismiss_diagnostics(cx);
16251 }
16252 }
16253 }
16254
16255 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16256 match &self.active_diagnostics {
16257 ActiveDiagnostic::Group(group) => Some(group),
16258 _ => None,
16259 }
16260 }
16261
16262 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16263 if !self.diagnostics_enabled() {
16264 return;
16265 }
16266 self.dismiss_diagnostics(cx);
16267 self.active_diagnostics = ActiveDiagnostic::All;
16268 }
16269
16270 fn activate_diagnostics(
16271 &mut self,
16272 buffer_id: BufferId,
16273 diagnostic: DiagnosticEntry<usize>,
16274 window: &mut Window,
16275 cx: &mut Context<Self>,
16276 ) {
16277 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16278 return;
16279 }
16280 self.dismiss_diagnostics(cx);
16281 let snapshot = self.snapshot(window, cx);
16282 let buffer = self.buffer.read(cx).snapshot(cx);
16283 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16284 return;
16285 };
16286
16287 let diagnostic_group = buffer
16288 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16289 .collect::<Vec<_>>();
16290
16291 let blocks =
16292 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16293
16294 let blocks = self.display_map.update(cx, |display_map, cx| {
16295 display_map.insert_blocks(blocks, cx).into_iter().collect()
16296 });
16297 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16298 active_range: buffer.anchor_before(diagnostic.range.start)
16299 ..buffer.anchor_after(diagnostic.range.end),
16300 active_message: diagnostic.diagnostic.message.clone(),
16301 group_id: diagnostic.diagnostic.group_id,
16302 blocks,
16303 });
16304 cx.notify();
16305 }
16306
16307 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16308 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16309 return;
16310 };
16311
16312 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16313 if let ActiveDiagnostic::Group(group) = prev {
16314 self.display_map.update(cx, |display_map, cx| {
16315 display_map.remove_blocks(group.blocks, cx);
16316 });
16317 cx.notify();
16318 }
16319 }
16320
16321 /// Disable inline diagnostics rendering for this editor.
16322 pub fn disable_inline_diagnostics(&mut self) {
16323 self.inline_diagnostics_enabled = false;
16324 self.inline_diagnostics_update = Task::ready(());
16325 self.inline_diagnostics.clear();
16326 }
16327
16328 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16329 self.diagnostics_enabled = false;
16330 self.dismiss_diagnostics(cx);
16331 self.inline_diagnostics_update = Task::ready(());
16332 self.inline_diagnostics.clear();
16333 }
16334
16335 pub fn diagnostics_enabled(&self) -> bool {
16336 self.diagnostics_enabled && self.mode.is_full()
16337 }
16338
16339 pub fn inline_diagnostics_enabled(&self) -> bool {
16340 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16341 }
16342
16343 pub fn show_inline_diagnostics(&self) -> bool {
16344 self.show_inline_diagnostics
16345 }
16346
16347 pub fn toggle_inline_diagnostics(
16348 &mut self,
16349 _: &ToggleInlineDiagnostics,
16350 window: &mut Window,
16351 cx: &mut Context<Editor>,
16352 ) {
16353 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16354 self.refresh_inline_diagnostics(false, window, cx);
16355 }
16356
16357 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16358 self.diagnostics_max_severity = severity;
16359 self.display_map.update(cx, |display_map, _| {
16360 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16361 });
16362 }
16363
16364 pub fn toggle_diagnostics(
16365 &mut self,
16366 _: &ToggleDiagnostics,
16367 window: &mut Window,
16368 cx: &mut Context<Editor>,
16369 ) {
16370 if !self.diagnostics_enabled() {
16371 return;
16372 }
16373
16374 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16375 EditorSettings::get_global(cx)
16376 .diagnostics_max_severity
16377 .filter(|severity| severity != &DiagnosticSeverity::Off)
16378 .unwrap_or(DiagnosticSeverity::Hint)
16379 } else {
16380 DiagnosticSeverity::Off
16381 };
16382 self.set_max_diagnostics_severity(new_severity, cx);
16383 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16384 self.active_diagnostics = ActiveDiagnostic::None;
16385 self.inline_diagnostics_update = Task::ready(());
16386 self.inline_diagnostics.clear();
16387 } else {
16388 self.refresh_inline_diagnostics(false, window, cx);
16389 }
16390
16391 cx.notify();
16392 }
16393
16394 pub fn toggle_minimap(
16395 &mut self,
16396 _: &ToggleMinimap,
16397 window: &mut Window,
16398 cx: &mut Context<Editor>,
16399 ) {
16400 if self.supports_minimap(cx) {
16401 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16402 }
16403 }
16404
16405 fn refresh_inline_diagnostics(
16406 &mut self,
16407 debounce: bool,
16408 window: &mut Window,
16409 cx: &mut Context<Self>,
16410 ) {
16411 let max_severity = ProjectSettings::get_global(cx)
16412 .diagnostics
16413 .inline
16414 .max_severity
16415 .unwrap_or(self.diagnostics_max_severity);
16416
16417 if !self.inline_diagnostics_enabled()
16418 || !self.show_inline_diagnostics
16419 || max_severity == DiagnosticSeverity::Off
16420 {
16421 self.inline_diagnostics_update = Task::ready(());
16422 self.inline_diagnostics.clear();
16423 return;
16424 }
16425
16426 let debounce_ms = ProjectSettings::get_global(cx)
16427 .diagnostics
16428 .inline
16429 .update_debounce_ms;
16430 let debounce = if debounce && debounce_ms > 0 {
16431 Some(Duration::from_millis(debounce_ms))
16432 } else {
16433 None
16434 };
16435 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16436 if let Some(debounce) = debounce {
16437 cx.background_executor().timer(debounce).await;
16438 }
16439 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16440 editor
16441 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16442 .ok()
16443 }) else {
16444 return;
16445 };
16446
16447 let new_inline_diagnostics = cx
16448 .background_spawn(async move {
16449 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16450 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16451 let message = diagnostic_entry
16452 .diagnostic
16453 .message
16454 .split_once('\n')
16455 .map(|(line, _)| line)
16456 .map(SharedString::new)
16457 .unwrap_or_else(|| {
16458 SharedString::from(diagnostic_entry.diagnostic.message)
16459 });
16460 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16461 let (Ok(i) | Err(i)) = inline_diagnostics
16462 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16463 inline_diagnostics.insert(
16464 i,
16465 (
16466 start_anchor,
16467 InlineDiagnostic {
16468 message,
16469 group_id: diagnostic_entry.diagnostic.group_id,
16470 start: diagnostic_entry.range.start.to_point(&snapshot),
16471 is_primary: diagnostic_entry.diagnostic.is_primary,
16472 severity: diagnostic_entry.diagnostic.severity,
16473 },
16474 ),
16475 );
16476 }
16477 inline_diagnostics
16478 })
16479 .await;
16480
16481 editor
16482 .update(cx, |editor, cx| {
16483 editor.inline_diagnostics = new_inline_diagnostics;
16484 cx.notify();
16485 })
16486 .ok();
16487 });
16488 }
16489
16490 fn pull_diagnostics(
16491 &mut self,
16492 buffer_id: Option<BufferId>,
16493 window: &Window,
16494 cx: &mut Context<Self>,
16495 ) -> Option<()> {
16496 if !self.mode().is_full() {
16497 return None;
16498 }
16499 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16500 .diagnostics
16501 .lsp_pull_diagnostics;
16502 if !pull_diagnostics_settings.enabled {
16503 return None;
16504 }
16505 let project = self.project.as_ref()?.downgrade();
16506 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16507 let mut buffers = self.buffer.read(cx).all_buffers();
16508 if let Some(buffer_id) = buffer_id {
16509 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16510 }
16511
16512 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16513 cx.background_executor().timer(debounce).await;
16514
16515 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16516 buffers
16517 .into_iter()
16518 .filter_map(|buffer| {
16519 project
16520 .update(cx, |project, cx| {
16521 project.lsp_store().update(cx, |lsp_store, cx| {
16522 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
16523 })
16524 })
16525 .ok()
16526 })
16527 .collect::<FuturesUnordered<_>>()
16528 }) else {
16529 return;
16530 };
16531
16532 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16533 match pull_task {
16534 Ok(()) => {
16535 if editor
16536 .update_in(cx, |editor, window, cx| {
16537 editor.update_diagnostics_state(window, cx);
16538 })
16539 .is_err()
16540 {
16541 return;
16542 }
16543 }
16544 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16545 }
16546 }
16547 });
16548
16549 Some(())
16550 }
16551
16552 pub fn set_selections_from_remote(
16553 &mut self,
16554 selections: Vec<Selection<Anchor>>,
16555 pending_selection: Option<Selection<Anchor>>,
16556 window: &mut Window,
16557 cx: &mut Context<Self>,
16558 ) {
16559 let old_cursor_position = self.selections.newest_anchor().head();
16560 self.selections.change_with(cx, |s| {
16561 s.select_anchors(selections);
16562 if let Some(pending_selection) = pending_selection {
16563 s.set_pending(pending_selection, SelectMode::Character);
16564 } else {
16565 s.clear_pending();
16566 }
16567 });
16568 self.selections_did_change(
16569 false,
16570 &old_cursor_position,
16571 SelectionEffects::default(),
16572 window,
16573 cx,
16574 );
16575 }
16576
16577 pub fn transact(
16578 &mut self,
16579 window: &mut Window,
16580 cx: &mut Context<Self>,
16581 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16582 ) -> Option<TransactionId> {
16583 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16584 this.start_transaction_at(Instant::now(), window, cx);
16585 update(this, window, cx);
16586 this.end_transaction_at(Instant::now(), cx)
16587 })
16588 }
16589
16590 pub fn start_transaction_at(
16591 &mut self,
16592 now: Instant,
16593 window: &mut Window,
16594 cx: &mut Context<Self>,
16595 ) {
16596 self.end_selection(window, cx);
16597 if let Some(tx_id) = self
16598 .buffer
16599 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16600 {
16601 self.selection_history
16602 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16603 cx.emit(EditorEvent::TransactionBegun {
16604 transaction_id: tx_id,
16605 })
16606 }
16607 }
16608
16609 pub fn end_transaction_at(
16610 &mut self,
16611 now: Instant,
16612 cx: &mut Context<Self>,
16613 ) -> Option<TransactionId> {
16614 if let Some(transaction_id) = self
16615 .buffer
16616 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16617 {
16618 if let Some((_, end_selections)) =
16619 self.selection_history.transaction_mut(transaction_id)
16620 {
16621 *end_selections = Some(self.selections.disjoint_anchors());
16622 } else {
16623 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16624 }
16625
16626 cx.emit(EditorEvent::Edited { transaction_id });
16627 Some(transaction_id)
16628 } else {
16629 None
16630 }
16631 }
16632
16633 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16634 if self.selection_mark_mode {
16635 self.change_selections(None, window, cx, |s| {
16636 s.move_with(|_, sel| {
16637 sel.collapse_to(sel.head(), SelectionGoal::None);
16638 });
16639 })
16640 }
16641 self.selection_mark_mode = true;
16642 cx.notify();
16643 }
16644
16645 pub fn swap_selection_ends(
16646 &mut self,
16647 _: &actions::SwapSelectionEnds,
16648 window: &mut Window,
16649 cx: &mut Context<Self>,
16650 ) {
16651 self.change_selections(None, window, cx, |s| {
16652 s.move_with(|_, sel| {
16653 if sel.start != sel.end {
16654 sel.reversed = !sel.reversed
16655 }
16656 });
16657 });
16658 self.request_autoscroll(Autoscroll::newest(), cx);
16659 cx.notify();
16660 }
16661
16662 pub fn toggle_fold(
16663 &mut self,
16664 _: &actions::ToggleFold,
16665 window: &mut Window,
16666 cx: &mut Context<Self>,
16667 ) {
16668 if self.is_singleton(cx) {
16669 let selection = self.selections.newest::<Point>(cx);
16670
16671 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16672 let range = if selection.is_empty() {
16673 let point = selection.head().to_display_point(&display_map);
16674 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16675 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16676 .to_point(&display_map);
16677 start..end
16678 } else {
16679 selection.range()
16680 };
16681 if display_map.folds_in_range(range).next().is_some() {
16682 self.unfold_lines(&Default::default(), window, cx)
16683 } else {
16684 self.fold(&Default::default(), window, cx)
16685 }
16686 } else {
16687 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16688 let buffer_ids: HashSet<_> = self
16689 .selections
16690 .disjoint_anchor_ranges()
16691 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16692 .collect();
16693
16694 let should_unfold = buffer_ids
16695 .iter()
16696 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16697
16698 for buffer_id in buffer_ids {
16699 if should_unfold {
16700 self.unfold_buffer(buffer_id, cx);
16701 } else {
16702 self.fold_buffer(buffer_id, cx);
16703 }
16704 }
16705 }
16706 }
16707
16708 pub fn toggle_fold_recursive(
16709 &mut self,
16710 _: &actions::ToggleFoldRecursive,
16711 window: &mut Window,
16712 cx: &mut Context<Self>,
16713 ) {
16714 let selection = self.selections.newest::<Point>(cx);
16715
16716 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16717 let range = if selection.is_empty() {
16718 let point = selection.head().to_display_point(&display_map);
16719 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16720 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16721 .to_point(&display_map);
16722 start..end
16723 } else {
16724 selection.range()
16725 };
16726 if display_map.folds_in_range(range).next().is_some() {
16727 self.unfold_recursive(&Default::default(), window, cx)
16728 } else {
16729 self.fold_recursive(&Default::default(), window, cx)
16730 }
16731 }
16732
16733 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16734 if self.is_singleton(cx) {
16735 let mut to_fold = Vec::new();
16736 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16737 let selections = self.selections.all_adjusted(cx);
16738
16739 for selection in selections {
16740 let range = selection.range().sorted();
16741 let buffer_start_row = range.start.row;
16742
16743 if range.start.row != range.end.row {
16744 let mut found = false;
16745 let mut row = range.start.row;
16746 while row <= range.end.row {
16747 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16748 {
16749 found = true;
16750 row = crease.range().end.row + 1;
16751 to_fold.push(crease);
16752 } else {
16753 row += 1
16754 }
16755 }
16756 if found {
16757 continue;
16758 }
16759 }
16760
16761 for row in (0..=range.start.row).rev() {
16762 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16763 if crease.range().end.row >= buffer_start_row {
16764 to_fold.push(crease);
16765 if row <= range.start.row {
16766 break;
16767 }
16768 }
16769 }
16770 }
16771 }
16772
16773 self.fold_creases(to_fold, true, window, cx);
16774 } else {
16775 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16776 let buffer_ids = self
16777 .selections
16778 .disjoint_anchor_ranges()
16779 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16780 .collect::<HashSet<_>>();
16781 for buffer_id in buffer_ids {
16782 self.fold_buffer(buffer_id, cx);
16783 }
16784 }
16785 }
16786
16787 fn fold_at_level(
16788 &mut self,
16789 fold_at: &FoldAtLevel,
16790 window: &mut Window,
16791 cx: &mut Context<Self>,
16792 ) {
16793 if !self.buffer.read(cx).is_singleton() {
16794 return;
16795 }
16796
16797 let fold_at_level = fold_at.0;
16798 let snapshot = self.buffer.read(cx).snapshot(cx);
16799 let mut to_fold = Vec::new();
16800 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16801
16802 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16803 while start_row < end_row {
16804 match self
16805 .snapshot(window, cx)
16806 .crease_for_buffer_row(MultiBufferRow(start_row))
16807 {
16808 Some(crease) => {
16809 let nested_start_row = crease.range().start.row + 1;
16810 let nested_end_row = crease.range().end.row;
16811
16812 if current_level < fold_at_level {
16813 stack.push((nested_start_row, nested_end_row, current_level + 1));
16814 } else if current_level == fold_at_level {
16815 to_fold.push(crease);
16816 }
16817
16818 start_row = nested_end_row + 1;
16819 }
16820 None => start_row += 1,
16821 }
16822 }
16823 }
16824
16825 self.fold_creases(to_fold, true, window, cx);
16826 }
16827
16828 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16829 if self.buffer.read(cx).is_singleton() {
16830 let mut fold_ranges = Vec::new();
16831 let snapshot = self.buffer.read(cx).snapshot(cx);
16832
16833 for row in 0..snapshot.max_row().0 {
16834 if let Some(foldable_range) = self
16835 .snapshot(window, cx)
16836 .crease_for_buffer_row(MultiBufferRow(row))
16837 {
16838 fold_ranges.push(foldable_range);
16839 }
16840 }
16841
16842 self.fold_creases(fold_ranges, true, window, cx);
16843 } else {
16844 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16845 editor
16846 .update_in(cx, |editor, _, cx| {
16847 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16848 editor.fold_buffer(buffer_id, cx);
16849 }
16850 })
16851 .ok();
16852 });
16853 }
16854 }
16855
16856 pub fn fold_function_bodies(
16857 &mut self,
16858 _: &actions::FoldFunctionBodies,
16859 window: &mut Window,
16860 cx: &mut Context<Self>,
16861 ) {
16862 let snapshot = self.buffer.read(cx).snapshot(cx);
16863
16864 let ranges = snapshot
16865 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16866 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16867 .collect::<Vec<_>>();
16868
16869 let creases = ranges
16870 .into_iter()
16871 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16872 .collect();
16873
16874 self.fold_creases(creases, true, window, cx);
16875 }
16876
16877 pub fn fold_recursive(
16878 &mut self,
16879 _: &actions::FoldRecursive,
16880 window: &mut Window,
16881 cx: &mut Context<Self>,
16882 ) {
16883 let mut to_fold = Vec::new();
16884 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16885 let selections = self.selections.all_adjusted(cx);
16886
16887 for selection in selections {
16888 let range = selection.range().sorted();
16889 let buffer_start_row = range.start.row;
16890
16891 if range.start.row != range.end.row {
16892 let mut found = false;
16893 for row in range.start.row..=range.end.row {
16894 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16895 found = true;
16896 to_fold.push(crease);
16897 }
16898 }
16899 if found {
16900 continue;
16901 }
16902 }
16903
16904 for row in (0..=range.start.row).rev() {
16905 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16906 if crease.range().end.row >= buffer_start_row {
16907 to_fold.push(crease);
16908 } else {
16909 break;
16910 }
16911 }
16912 }
16913 }
16914
16915 self.fold_creases(to_fold, true, window, cx);
16916 }
16917
16918 pub fn fold_at(
16919 &mut self,
16920 buffer_row: MultiBufferRow,
16921 window: &mut Window,
16922 cx: &mut Context<Self>,
16923 ) {
16924 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16925
16926 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16927 let autoscroll = self
16928 .selections
16929 .all::<Point>(cx)
16930 .iter()
16931 .any(|selection| crease.range().overlaps(&selection.range()));
16932
16933 self.fold_creases(vec![crease], autoscroll, window, cx);
16934 }
16935 }
16936
16937 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16938 if self.is_singleton(cx) {
16939 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16940 let buffer = &display_map.buffer_snapshot;
16941 let selections = self.selections.all::<Point>(cx);
16942 let ranges = selections
16943 .iter()
16944 .map(|s| {
16945 let range = s.display_range(&display_map).sorted();
16946 let mut start = range.start.to_point(&display_map);
16947 let mut end = range.end.to_point(&display_map);
16948 start.column = 0;
16949 end.column = buffer.line_len(MultiBufferRow(end.row));
16950 start..end
16951 })
16952 .collect::<Vec<_>>();
16953
16954 self.unfold_ranges(&ranges, true, true, cx);
16955 } else {
16956 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16957 let buffer_ids = self
16958 .selections
16959 .disjoint_anchor_ranges()
16960 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16961 .collect::<HashSet<_>>();
16962 for buffer_id in buffer_ids {
16963 self.unfold_buffer(buffer_id, cx);
16964 }
16965 }
16966 }
16967
16968 pub fn unfold_recursive(
16969 &mut self,
16970 _: &UnfoldRecursive,
16971 _window: &mut Window,
16972 cx: &mut Context<Self>,
16973 ) {
16974 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16975 let selections = self.selections.all::<Point>(cx);
16976 let ranges = selections
16977 .iter()
16978 .map(|s| {
16979 let mut range = s.display_range(&display_map).sorted();
16980 *range.start.column_mut() = 0;
16981 *range.end.column_mut() = display_map.line_len(range.end.row());
16982 let start = range.start.to_point(&display_map);
16983 let end = range.end.to_point(&display_map);
16984 start..end
16985 })
16986 .collect::<Vec<_>>();
16987
16988 self.unfold_ranges(&ranges, true, true, cx);
16989 }
16990
16991 pub fn unfold_at(
16992 &mut self,
16993 buffer_row: MultiBufferRow,
16994 _window: &mut Window,
16995 cx: &mut Context<Self>,
16996 ) {
16997 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16998
16999 let intersection_range = Point::new(buffer_row.0, 0)
17000 ..Point::new(
17001 buffer_row.0,
17002 display_map.buffer_snapshot.line_len(buffer_row),
17003 );
17004
17005 let autoscroll = self
17006 .selections
17007 .all::<Point>(cx)
17008 .iter()
17009 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17010
17011 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17012 }
17013
17014 pub fn unfold_all(
17015 &mut self,
17016 _: &actions::UnfoldAll,
17017 _window: &mut Window,
17018 cx: &mut Context<Self>,
17019 ) {
17020 if self.buffer.read(cx).is_singleton() {
17021 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17022 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17023 } else {
17024 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17025 editor
17026 .update(cx, |editor, cx| {
17027 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17028 editor.unfold_buffer(buffer_id, cx);
17029 }
17030 })
17031 .ok();
17032 });
17033 }
17034 }
17035
17036 pub fn fold_selected_ranges(
17037 &mut self,
17038 _: &FoldSelectedRanges,
17039 window: &mut Window,
17040 cx: &mut Context<Self>,
17041 ) {
17042 let selections = self.selections.all_adjusted(cx);
17043 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17044 let ranges = selections
17045 .into_iter()
17046 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17047 .collect::<Vec<_>>();
17048 self.fold_creases(ranges, true, window, cx);
17049 }
17050
17051 pub fn fold_ranges<T: ToOffset + Clone>(
17052 &mut self,
17053 ranges: Vec<Range<T>>,
17054 auto_scroll: bool,
17055 window: &mut Window,
17056 cx: &mut Context<Self>,
17057 ) {
17058 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17059 let ranges = ranges
17060 .into_iter()
17061 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17062 .collect::<Vec<_>>();
17063 self.fold_creases(ranges, auto_scroll, window, cx);
17064 }
17065
17066 pub fn fold_creases<T: ToOffset + Clone>(
17067 &mut self,
17068 creases: Vec<Crease<T>>,
17069 auto_scroll: bool,
17070 _window: &mut Window,
17071 cx: &mut Context<Self>,
17072 ) {
17073 if creases.is_empty() {
17074 return;
17075 }
17076
17077 let mut buffers_affected = HashSet::default();
17078 let multi_buffer = self.buffer().read(cx);
17079 for crease in &creases {
17080 if let Some((_, buffer, _)) =
17081 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
17082 {
17083 buffers_affected.insert(buffer.read(cx).remote_id());
17084 };
17085 }
17086
17087 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17088
17089 if auto_scroll {
17090 self.request_autoscroll(Autoscroll::fit(), cx);
17091 }
17092
17093 cx.notify();
17094
17095 self.scrollbar_marker_state.dirty = true;
17096 self.folds_did_change(cx);
17097 }
17098
17099 /// Removes any folds whose ranges intersect any of the given ranges.
17100 pub fn unfold_ranges<T: ToOffset + Clone>(
17101 &mut self,
17102 ranges: &[Range<T>],
17103 inclusive: bool,
17104 auto_scroll: bool,
17105 cx: &mut Context<Self>,
17106 ) {
17107 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17108 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17109 });
17110 self.folds_did_change(cx);
17111 }
17112
17113 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17114 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17115 return;
17116 }
17117 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17118 self.display_map.update(cx, |display_map, cx| {
17119 display_map.fold_buffers([buffer_id], cx)
17120 });
17121 cx.emit(EditorEvent::BufferFoldToggled {
17122 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17123 folded: true,
17124 });
17125 cx.notify();
17126 }
17127
17128 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17129 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17130 return;
17131 }
17132 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17133 self.display_map.update(cx, |display_map, cx| {
17134 display_map.unfold_buffers([buffer_id], cx);
17135 });
17136 cx.emit(EditorEvent::BufferFoldToggled {
17137 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17138 folded: false,
17139 });
17140 cx.notify();
17141 }
17142
17143 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17144 self.display_map.read(cx).is_buffer_folded(buffer)
17145 }
17146
17147 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17148 self.display_map.read(cx).folded_buffers()
17149 }
17150
17151 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17152 self.display_map.update(cx, |display_map, cx| {
17153 display_map.disable_header_for_buffer(buffer_id, cx);
17154 });
17155 cx.notify();
17156 }
17157
17158 /// Removes any folds with the given ranges.
17159 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17160 &mut self,
17161 ranges: &[Range<T>],
17162 type_id: TypeId,
17163 auto_scroll: bool,
17164 cx: &mut Context<Self>,
17165 ) {
17166 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17167 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17168 });
17169 self.folds_did_change(cx);
17170 }
17171
17172 fn remove_folds_with<T: ToOffset + Clone>(
17173 &mut self,
17174 ranges: &[Range<T>],
17175 auto_scroll: bool,
17176 cx: &mut Context<Self>,
17177 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17178 ) {
17179 if ranges.is_empty() {
17180 return;
17181 }
17182
17183 let mut buffers_affected = HashSet::default();
17184 let multi_buffer = self.buffer().read(cx);
17185 for range in ranges {
17186 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17187 buffers_affected.insert(buffer.read(cx).remote_id());
17188 };
17189 }
17190
17191 self.display_map.update(cx, update);
17192
17193 if auto_scroll {
17194 self.request_autoscroll(Autoscroll::fit(), cx);
17195 }
17196
17197 cx.notify();
17198 self.scrollbar_marker_state.dirty = true;
17199 self.active_indent_guides_state.dirty = true;
17200 }
17201
17202 pub fn update_fold_widths(
17203 &mut self,
17204 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
17205 cx: &mut Context<Self>,
17206 ) -> bool {
17207 self.display_map
17208 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17209 }
17210
17211 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17212 self.display_map.read(cx).fold_placeholder.clone()
17213 }
17214
17215 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17216 self.buffer.update(cx, |buffer, cx| {
17217 buffer.set_all_diff_hunks_expanded(cx);
17218 });
17219 }
17220
17221 pub fn expand_all_diff_hunks(
17222 &mut self,
17223 _: &ExpandAllDiffHunks,
17224 _window: &mut Window,
17225 cx: &mut Context<Self>,
17226 ) {
17227 self.buffer.update(cx, |buffer, cx| {
17228 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17229 });
17230 }
17231
17232 pub fn toggle_selected_diff_hunks(
17233 &mut self,
17234 _: &ToggleSelectedDiffHunks,
17235 _window: &mut Window,
17236 cx: &mut Context<Self>,
17237 ) {
17238 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17239 self.toggle_diff_hunks_in_ranges(ranges, cx);
17240 }
17241
17242 pub fn diff_hunks_in_ranges<'a>(
17243 &'a self,
17244 ranges: &'a [Range<Anchor>],
17245 buffer: &'a MultiBufferSnapshot,
17246 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17247 ranges.iter().flat_map(move |range| {
17248 let end_excerpt_id = range.end.excerpt_id;
17249 let range = range.to_point(buffer);
17250 let mut peek_end = range.end;
17251 if range.end.row < buffer.max_row().0 {
17252 peek_end = Point::new(range.end.row + 1, 0);
17253 }
17254 buffer
17255 .diff_hunks_in_range(range.start..peek_end)
17256 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17257 })
17258 }
17259
17260 pub fn has_stageable_diff_hunks_in_ranges(
17261 &self,
17262 ranges: &[Range<Anchor>],
17263 snapshot: &MultiBufferSnapshot,
17264 ) -> bool {
17265 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17266 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17267 }
17268
17269 pub fn toggle_staged_selected_diff_hunks(
17270 &mut self,
17271 _: &::git::ToggleStaged,
17272 _: &mut Window,
17273 cx: &mut Context<Self>,
17274 ) {
17275 let snapshot = self.buffer.read(cx).snapshot(cx);
17276 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17277 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17278 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17279 }
17280
17281 pub fn set_render_diff_hunk_controls(
17282 &mut self,
17283 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17284 cx: &mut Context<Self>,
17285 ) {
17286 self.render_diff_hunk_controls = render_diff_hunk_controls;
17287 cx.notify();
17288 }
17289
17290 pub fn stage_and_next(
17291 &mut self,
17292 _: &::git::StageAndNext,
17293 window: &mut Window,
17294 cx: &mut Context<Self>,
17295 ) {
17296 self.do_stage_or_unstage_and_next(true, window, cx);
17297 }
17298
17299 pub fn unstage_and_next(
17300 &mut self,
17301 _: &::git::UnstageAndNext,
17302 window: &mut Window,
17303 cx: &mut Context<Self>,
17304 ) {
17305 self.do_stage_or_unstage_and_next(false, window, cx);
17306 }
17307
17308 pub fn stage_or_unstage_diff_hunks(
17309 &mut self,
17310 stage: bool,
17311 ranges: Vec<Range<Anchor>>,
17312 cx: &mut Context<Self>,
17313 ) {
17314 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17315 cx.spawn(async move |this, cx| {
17316 task.await?;
17317 this.update(cx, |this, cx| {
17318 let snapshot = this.buffer.read(cx).snapshot(cx);
17319 let chunk_by = this
17320 .diff_hunks_in_ranges(&ranges, &snapshot)
17321 .chunk_by(|hunk| hunk.buffer_id);
17322 for (buffer_id, hunks) in &chunk_by {
17323 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17324 }
17325 })
17326 })
17327 .detach_and_log_err(cx);
17328 }
17329
17330 fn save_buffers_for_ranges_if_needed(
17331 &mut self,
17332 ranges: &[Range<Anchor>],
17333 cx: &mut Context<Editor>,
17334 ) -> Task<Result<()>> {
17335 let multibuffer = self.buffer.read(cx);
17336 let snapshot = multibuffer.read(cx);
17337 let buffer_ids: HashSet<_> = ranges
17338 .iter()
17339 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17340 .collect();
17341 drop(snapshot);
17342
17343 let mut buffers = HashSet::default();
17344 for buffer_id in buffer_ids {
17345 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17346 let buffer = buffer_entity.read(cx);
17347 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17348 {
17349 buffers.insert(buffer_entity);
17350 }
17351 }
17352 }
17353
17354 if let Some(project) = &self.project {
17355 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17356 } else {
17357 Task::ready(Ok(()))
17358 }
17359 }
17360
17361 fn do_stage_or_unstage_and_next(
17362 &mut self,
17363 stage: bool,
17364 window: &mut Window,
17365 cx: &mut Context<Self>,
17366 ) {
17367 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17368
17369 if ranges.iter().any(|range| range.start != range.end) {
17370 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17371 return;
17372 }
17373
17374 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17375 let snapshot = self.snapshot(window, cx);
17376 let position = self.selections.newest::<Point>(cx).head();
17377 let mut row = snapshot
17378 .buffer_snapshot
17379 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17380 .find(|hunk| hunk.row_range.start.0 > position.row)
17381 .map(|hunk| hunk.row_range.start);
17382
17383 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17384 // Outside of the project diff editor, wrap around to the beginning.
17385 if !all_diff_hunks_expanded {
17386 row = row.or_else(|| {
17387 snapshot
17388 .buffer_snapshot
17389 .diff_hunks_in_range(Point::zero()..position)
17390 .find(|hunk| hunk.row_range.end.0 < position.row)
17391 .map(|hunk| hunk.row_range.start)
17392 });
17393 }
17394
17395 if let Some(row) = row {
17396 let destination = Point::new(row.0, 0);
17397 let autoscroll = Autoscroll::center();
17398
17399 self.unfold_ranges(&[destination..destination], false, false, cx);
17400 self.change_selections(Some(autoscroll), window, cx, |s| {
17401 s.select_ranges([destination..destination]);
17402 });
17403 }
17404 }
17405
17406 fn do_stage_or_unstage(
17407 &self,
17408 stage: bool,
17409 buffer_id: BufferId,
17410 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17411 cx: &mut App,
17412 ) -> Option<()> {
17413 let project = self.project.as_ref()?;
17414 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17415 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17416 let buffer_snapshot = buffer.read(cx).snapshot();
17417 let file_exists = buffer_snapshot
17418 .file()
17419 .is_some_and(|file| file.disk_state().exists());
17420 diff.update(cx, |diff, cx| {
17421 diff.stage_or_unstage_hunks(
17422 stage,
17423 &hunks
17424 .map(|hunk| buffer_diff::DiffHunk {
17425 buffer_range: hunk.buffer_range,
17426 diff_base_byte_range: hunk.diff_base_byte_range,
17427 secondary_status: hunk.secondary_status,
17428 range: Point::zero()..Point::zero(), // unused
17429 })
17430 .collect::<Vec<_>>(),
17431 &buffer_snapshot,
17432 file_exists,
17433 cx,
17434 )
17435 });
17436 None
17437 }
17438
17439 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17440 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17441 self.buffer
17442 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17443 }
17444
17445 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17446 self.buffer.update(cx, |buffer, cx| {
17447 let ranges = vec![Anchor::min()..Anchor::max()];
17448 if !buffer.all_diff_hunks_expanded()
17449 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17450 {
17451 buffer.collapse_diff_hunks(ranges, cx);
17452 true
17453 } else {
17454 false
17455 }
17456 })
17457 }
17458
17459 fn toggle_diff_hunks_in_ranges(
17460 &mut self,
17461 ranges: Vec<Range<Anchor>>,
17462 cx: &mut Context<Editor>,
17463 ) {
17464 self.buffer.update(cx, |buffer, cx| {
17465 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17466 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17467 })
17468 }
17469
17470 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17471 self.buffer.update(cx, |buffer, cx| {
17472 let snapshot = buffer.snapshot(cx);
17473 let excerpt_id = range.end.excerpt_id;
17474 let point_range = range.to_point(&snapshot);
17475 let expand = !buffer.single_hunk_is_expanded(range, cx);
17476 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17477 })
17478 }
17479
17480 pub(crate) fn apply_all_diff_hunks(
17481 &mut self,
17482 _: &ApplyAllDiffHunks,
17483 window: &mut Window,
17484 cx: &mut Context<Self>,
17485 ) {
17486 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17487
17488 let buffers = self.buffer.read(cx).all_buffers();
17489 for branch_buffer in buffers {
17490 branch_buffer.update(cx, |branch_buffer, cx| {
17491 branch_buffer.merge_into_base(Vec::new(), cx);
17492 });
17493 }
17494
17495 if let Some(project) = self.project.clone() {
17496 self.save(
17497 SaveOptions {
17498 format: true,
17499 autosave: false,
17500 },
17501 project,
17502 window,
17503 cx,
17504 )
17505 .detach_and_log_err(cx);
17506 }
17507 }
17508
17509 pub(crate) fn apply_selected_diff_hunks(
17510 &mut self,
17511 _: &ApplyDiffHunk,
17512 window: &mut Window,
17513 cx: &mut Context<Self>,
17514 ) {
17515 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17516 let snapshot = self.snapshot(window, cx);
17517 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17518 let mut ranges_by_buffer = HashMap::default();
17519 self.transact(window, cx, |editor, _window, cx| {
17520 for hunk in hunks {
17521 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17522 ranges_by_buffer
17523 .entry(buffer.clone())
17524 .or_insert_with(Vec::new)
17525 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17526 }
17527 }
17528
17529 for (buffer, ranges) in ranges_by_buffer {
17530 buffer.update(cx, |buffer, cx| {
17531 buffer.merge_into_base(ranges, cx);
17532 });
17533 }
17534 });
17535
17536 if let Some(project) = self.project.clone() {
17537 self.save(
17538 SaveOptions {
17539 format: true,
17540 autosave: false,
17541 },
17542 project,
17543 window,
17544 cx,
17545 )
17546 .detach_and_log_err(cx);
17547 }
17548 }
17549
17550 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17551 if hovered != self.gutter_hovered {
17552 self.gutter_hovered = hovered;
17553 cx.notify();
17554 }
17555 }
17556
17557 pub fn insert_blocks(
17558 &mut self,
17559 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17560 autoscroll: Option<Autoscroll>,
17561 cx: &mut Context<Self>,
17562 ) -> Vec<CustomBlockId> {
17563 let blocks = self
17564 .display_map
17565 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17566 if let Some(autoscroll) = autoscroll {
17567 self.request_autoscroll(autoscroll, cx);
17568 }
17569 cx.notify();
17570 blocks
17571 }
17572
17573 pub fn resize_blocks(
17574 &mut self,
17575 heights: HashMap<CustomBlockId, u32>,
17576 autoscroll: Option<Autoscroll>,
17577 cx: &mut Context<Self>,
17578 ) {
17579 self.display_map
17580 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17581 if let Some(autoscroll) = autoscroll {
17582 self.request_autoscroll(autoscroll, cx);
17583 }
17584 cx.notify();
17585 }
17586
17587 pub fn replace_blocks(
17588 &mut self,
17589 renderers: HashMap<CustomBlockId, RenderBlock>,
17590 autoscroll: Option<Autoscroll>,
17591 cx: &mut Context<Self>,
17592 ) {
17593 self.display_map
17594 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17595 if let Some(autoscroll) = autoscroll {
17596 self.request_autoscroll(autoscroll, cx);
17597 }
17598 cx.notify();
17599 }
17600
17601 pub fn remove_blocks(
17602 &mut self,
17603 block_ids: HashSet<CustomBlockId>,
17604 autoscroll: Option<Autoscroll>,
17605 cx: &mut Context<Self>,
17606 ) {
17607 self.display_map.update(cx, |display_map, cx| {
17608 display_map.remove_blocks(block_ids, cx)
17609 });
17610 if let Some(autoscroll) = autoscroll {
17611 self.request_autoscroll(autoscroll, cx);
17612 }
17613 cx.notify();
17614 }
17615
17616 pub fn row_for_block(
17617 &self,
17618 block_id: CustomBlockId,
17619 cx: &mut Context<Self>,
17620 ) -> Option<DisplayRow> {
17621 self.display_map
17622 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17623 }
17624
17625 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17626 self.focused_block = Some(focused_block);
17627 }
17628
17629 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17630 self.focused_block.take()
17631 }
17632
17633 pub fn insert_creases(
17634 &mut self,
17635 creases: impl IntoIterator<Item = Crease<Anchor>>,
17636 cx: &mut Context<Self>,
17637 ) -> Vec<CreaseId> {
17638 self.display_map
17639 .update(cx, |map, cx| map.insert_creases(creases, cx))
17640 }
17641
17642 pub fn remove_creases(
17643 &mut self,
17644 ids: impl IntoIterator<Item = CreaseId>,
17645 cx: &mut Context<Self>,
17646 ) -> Vec<(CreaseId, Range<Anchor>)> {
17647 self.display_map
17648 .update(cx, |map, cx| map.remove_creases(ids, cx))
17649 }
17650
17651 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17652 self.display_map
17653 .update(cx, |map, cx| map.snapshot(cx))
17654 .longest_row()
17655 }
17656
17657 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17658 self.display_map
17659 .update(cx, |map, cx| map.snapshot(cx))
17660 .max_point()
17661 }
17662
17663 pub fn text(&self, cx: &App) -> String {
17664 self.buffer.read(cx).read(cx).text()
17665 }
17666
17667 pub fn is_empty(&self, cx: &App) -> bool {
17668 self.buffer.read(cx).read(cx).is_empty()
17669 }
17670
17671 pub fn text_option(&self, cx: &App) -> Option<String> {
17672 let text = self.text(cx);
17673 let text = text.trim();
17674
17675 if text.is_empty() {
17676 return None;
17677 }
17678
17679 Some(text.to_string())
17680 }
17681
17682 pub fn set_text(
17683 &mut self,
17684 text: impl Into<Arc<str>>,
17685 window: &mut Window,
17686 cx: &mut Context<Self>,
17687 ) {
17688 self.transact(window, cx, |this, _, cx| {
17689 this.buffer
17690 .read(cx)
17691 .as_singleton()
17692 .expect("you can only call set_text on editors for singleton buffers")
17693 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17694 });
17695 }
17696
17697 pub fn display_text(&self, cx: &mut App) -> String {
17698 self.display_map
17699 .update(cx, |map, cx| map.snapshot(cx))
17700 .text()
17701 }
17702
17703 fn create_minimap(
17704 &self,
17705 minimap_settings: MinimapSettings,
17706 window: &mut Window,
17707 cx: &mut Context<Self>,
17708 ) -> Option<Entity<Self>> {
17709 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17710 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17711 }
17712
17713 fn initialize_new_minimap(
17714 &self,
17715 minimap_settings: MinimapSettings,
17716 window: &mut Window,
17717 cx: &mut Context<Self>,
17718 ) -> Entity<Self> {
17719 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17720
17721 let mut minimap = Editor::new_internal(
17722 EditorMode::Minimap {
17723 parent: cx.weak_entity(),
17724 },
17725 self.buffer.clone(),
17726 self.project.clone(),
17727 Some(self.display_map.clone()),
17728 window,
17729 cx,
17730 );
17731 minimap.scroll_manager.clone_state(&self.scroll_manager);
17732 minimap.set_text_style_refinement(TextStyleRefinement {
17733 font_size: Some(MINIMAP_FONT_SIZE),
17734 font_weight: Some(MINIMAP_FONT_WEIGHT),
17735 ..Default::default()
17736 });
17737 minimap.update_minimap_configuration(minimap_settings, cx);
17738 cx.new(|_| minimap)
17739 }
17740
17741 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17742 let current_line_highlight = minimap_settings
17743 .current_line_highlight
17744 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17745 self.set_current_line_highlight(Some(current_line_highlight));
17746 }
17747
17748 pub fn minimap(&self) -> Option<&Entity<Self>> {
17749 self.minimap
17750 .as_ref()
17751 .filter(|_| self.minimap_visibility.visible())
17752 }
17753
17754 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17755 let mut wrap_guides = smallvec![];
17756
17757 if self.show_wrap_guides == Some(false) {
17758 return wrap_guides;
17759 }
17760
17761 let settings = self.buffer.read(cx).language_settings(cx);
17762 if settings.show_wrap_guides {
17763 match self.soft_wrap_mode(cx) {
17764 SoftWrap::Column(soft_wrap) => {
17765 wrap_guides.push((soft_wrap as usize, true));
17766 }
17767 SoftWrap::Bounded(soft_wrap) => {
17768 wrap_guides.push((soft_wrap as usize, true));
17769 }
17770 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17771 }
17772 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17773 }
17774
17775 wrap_guides
17776 }
17777
17778 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17779 let settings = self.buffer.read(cx).language_settings(cx);
17780 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17781 match mode {
17782 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17783 SoftWrap::None
17784 }
17785 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17786 language_settings::SoftWrap::PreferredLineLength => {
17787 SoftWrap::Column(settings.preferred_line_length)
17788 }
17789 language_settings::SoftWrap::Bounded => {
17790 SoftWrap::Bounded(settings.preferred_line_length)
17791 }
17792 }
17793 }
17794
17795 pub fn set_soft_wrap_mode(
17796 &mut self,
17797 mode: language_settings::SoftWrap,
17798
17799 cx: &mut Context<Self>,
17800 ) {
17801 self.soft_wrap_mode_override = Some(mode);
17802 cx.notify();
17803 }
17804
17805 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17806 self.hard_wrap = hard_wrap;
17807 cx.notify();
17808 }
17809
17810 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17811 self.text_style_refinement = Some(style);
17812 }
17813
17814 /// called by the Element so we know what style we were most recently rendered with.
17815 pub(crate) fn set_style(
17816 &mut self,
17817 style: EditorStyle,
17818 window: &mut Window,
17819 cx: &mut Context<Self>,
17820 ) {
17821 // We intentionally do not inform the display map about the minimap style
17822 // so that wrapping is not recalculated and stays consistent for the editor
17823 // and its linked minimap.
17824 if !self.mode.is_minimap() {
17825 let rem_size = window.rem_size();
17826 self.display_map.update(cx, |map, cx| {
17827 map.set_font(
17828 style.text.font(),
17829 style.text.font_size.to_pixels(rem_size),
17830 cx,
17831 )
17832 });
17833 }
17834 self.style = Some(style);
17835 }
17836
17837 pub fn style(&self) -> Option<&EditorStyle> {
17838 self.style.as_ref()
17839 }
17840
17841 // Called by the element. This method is not designed to be called outside of the editor
17842 // element's layout code because it does not notify when rewrapping is computed synchronously.
17843 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17844 self.display_map
17845 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17846 }
17847
17848 pub fn set_soft_wrap(&mut self) {
17849 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17850 }
17851
17852 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17853 if self.soft_wrap_mode_override.is_some() {
17854 self.soft_wrap_mode_override.take();
17855 } else {
17856 let soft_wrap = match self.soft_wrap_mode(cx) {
17857 SoftWrap::GitDiff => return,
17858 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17859 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17860 language_settings::SoftWrap::None
17861 }
17862 };
17863 self.soft_wrap_mode_override = Some(soft_wrap);
17864 }
17865 cx.notify();
17866 }
17867
17868 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17869 let Some(workspace) = self.workspace() else {
17870 return;
17871 };
17872 let fs = workspace.read(cx).app_state().fs.clone();
17873 let current_show = TabBarSettings::get_global(cx).show;
17874 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17875 setting.show = Some(!current_show);
17876 });
17877 }
17878
17879 pub fn toggle_indent_guides(
17880 &mut self,
17881 _: &ToggleIndentGuides,
17882 _: &mut Window,
17883 cx: &mut Context<Self>,
17884 ) {
17885 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17886 self.buffer
17887 .read(cx)
17888 .language_settings(cx)
17889 .indent_guides
17890 .enabled
17891 });
17892 self.show_indent_guides = Some(!currently_enabled);
17893 cx.notify();
17894 }
17895
17896 fn should_show_indent_guides(&self) -> Option<bool> {
17897 self.show_indent_guides
17898 }
17899
17900 pub fn toggle_line_numbers(
17901 &mut self,
17902 _: &ToggleLineNumbers,
17903 _: &mut Window,
17904 cx: &mut Context<Self>,
17905 ) {
17906 let mut editor_settings = EditorSettings::get_global(cx).clone();
17907 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17908 EditorSettings::override_global(editor_settings, cx);
17909 }
17910
17911 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17912 if let Some(show_line_numbers) = self.show_line_numbers {
17913 return show_line_numbers;
17914 }
17915 EditorSettings::get_global(cx).gutter.line_numbers
17916 }
17917
17918 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17919 self.use_relative_line_numbers
17920 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17921 }
17922
17923 pub fn toggle_relative_line_numbers(
17924 &mut self,
17925 _: &ToggleRelativeLineNumbers,
17926 _: &mut Window,
17927 cx: &mut Context<Self>,
17928 ) {
17929 let is_relative = self.should_use_relative_line_numbers(cx);
17930 self.set_relative_line_number(Some(!is_relative), cx)
17931 }
17932
17933 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17934 self.use_relative_line_numbers = is_relative;
17935 cx.notify();
17936 }
17937
17938 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17939 self.show_gutter = show_gutter;
17940 cx.notify();
17941 }
17942
17943 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17944 self.show_scrollbars = ScrollbarAxes {
17945 horizontal: show,
17946 vertical: show,
17947 };
17948 cx.notify();
17949 }
17950
17951 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17952 self.show_scrollbars.vertical = show;
17953 cx.notify();
17954 }
17955
17956 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17957 self.show_scrollbars.horizontal = show;
17958 cx.notify();
17959 }
17960
17961 pub fn set_minimap_visibility(
17962 &mut self,
17963 minimap_visibility: MinimapVisibility,
17964 window: &mut Window,
17965 cx: &mut Context<Self>,
17966 ) {
17967 if self.minimap_visibility != minimap_visibility {
17968 if minimap_visibility.visible() && self.minimap.is_none() {
17969 let minimap_settings = EditorSettings::get_global(cx).minimap;
17970 self.minimap =
17971 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17972 }
17973 self.minimap_visibility = minimap_visibility;
17974 cx.notify();
17975 }
17976 }
17977
17978 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17979 self.set_show_scrollbars(false, cx);
17980 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17981 }
17982
17983 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17984 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17985 }
17986
17987 /// Normally the text in full mode and auto height editors is padded on the
17988 /// left side by roughly half a character width for improved hit testing.
17989 ///
17990 /// Use this method to disable this for cases where this is not wanted (e.g.
17991 /// if you want to align the editor text with some other text above or below)
17992 /// or if you want to add this padding to single-line editors.
17993 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17994 self.offset_content = offset_content;
17995 cx.notify();
17996 }
17997
17998 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17999 self.show_line_numbers = Some(show_line_numbers);
18000 cx.notify();
18001 }
18002
18003 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18004 self.disable_expand_excerpt_buttons = true;
18005 cx.notify();
18006 }
18007
18008 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18009 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18010 cx.notify();
18011 }
18012
18013 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18014 self.show_code_actions = Some(show_code_actions);
18015 cx.notify();
18016 }
18017
18018 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18019 self.show_runnables = Some(show_runnables);
18020 cx.notify();
18021 }
18022
18023 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18024 self.show_breakpoints = Some(show_breakpoints);
18025 cx.notify();
18026 }
18027
18028 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18029 if self.display_map.read(cx).masked != masked {
18030 self.display_map.update(cx, |map, _| map.masked = masked);
18031 }
18032 cx.notify()
18033 }
18034
18035 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18036 self.show_wrap_guides = Some(show_wrap_guides);
18037 cx.notify();
18038 }
18039
18040 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18041 self.show_indent_guides = Some(show_indent_guides);
18042 cx.notify();
18043 }
18044
18045 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18046 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18047 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
18048 if let Some(dir) = file.abs_path(cx).parent() {
18049 return Some(dir.to_owned());
18050 }
18051 }
18052
18053 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18054 return Some(project_path.path.to_path_buf());
18055 }
18056 }
18057
18058 None
18059 }
18060
18061 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18062 self.active_excerpt(cx)?
18063 .1
18064 .read(cx)
18065 .file()
18066 .and_then(|f| f.as_local())
18067 }
18068
18069 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18070 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18071 let buffer = buffer.read(cx);
18072 if let Some(project_path) = buffer.project_path(cx) {
18073 let project = self.project.as_ref()?.read(cx);
18074 project.absolute_path(&project_path, cx)
18075 } else {
18076 buffer
18077 .file()
18078 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18079 }
18080 })
18081 }
18082
18083 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18084 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18085 let project_path = buffer.read(cx).project_path(cx)?;
18086 let project = self.project.as_ref()?.read(cx);
18087 let entry = project.entry_for_path(&project_path, cx)?;
18088 let path = entry.path.to_path_buf();
18089 Some(path)
18090 })
18091 }
18092
18093 pub fn reveal_in_finder(
18094 &mut self,
18095 _: &RevealInFileManager,
18096 _window: &mut Window,
18097 cx: &mut Context<Self>,
18098 ) {
18099 if let Some(target) = self.target_file(cx) {
18100 cx.reveal_path(&target.abs_path(cx));
18101 }
18102 }
18103
18104 pub fn copy_path(
18105 &mut self,
18106 _: &zed_actions::workspace::CopyPath,
18107 _window: &mut Window,
18108 cx: &mut Context<Self>,
18109 ) {
18110 if let Some(path) = self.target_file_abs_path(cx) {
18111 if let Some(path) = path.to_str() {
18112 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18113 }
18114 }
18115 }
18116
18117 pub fn copy_relative_path(
18118 &mut self,
18119 _: &zed_actions::workspace::CopyRelativePath,
18120 _window: &mut Window,
18121 cx: &mut Context<Self>,
18122 ) {
18123 if let Some(path) = self.target_file_path(cx) {
18124 if let Some(path) = path.to_str() {
18125 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18126 }
18127 }
18128 }
18129
18130 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18131 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18132 buffer.read(cx).project_path(cx)
18133 } else {
18134 None
18135 }
18136 }
18137
18138 // Returns true if the editor handled a go-to-line request
18139 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18140 maybe!({
18141 let breakpoint_store = self.breakpoint_store.as_ref()?;
18142
18143 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18144 else {
18145 self.clear_row_highlights::<ActiveDebugLine>();
18146 return None;
18147 };
18148
18149 let position = active_stack_frame.position;
18150 let buffer_id = position.buffer_id?;
18151 let snapshot = self
18152 .project
18153 .as_ref()?
18154 .read(cx)
18155 .buffer_for_id(buffer_id, cx)?
18156 .read(cx)
18157 .snapshot();
18158
18159 let mut handled = false;
18160 for (id, ExcerptRange { context, .. }) in
18161 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18162 {
18163 if context.start.cmp(&position, &snapshot).is_ge()
18164 || context.end.cmp(&position, &snapshot).is_lt()
18165 {
18166 continue;
18167 }
18168 let snapshot = self.buffer.read(cx).snapshot(cx);
18169 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18170
18171 handled = true;
18172 self.clear_row_highlights::<ActiveDebugLine>();
18173
18174 self.go_to_line::<ActiveDebugLine>(
18175 multibuffer_anchor,
18176 Some(cx.theme().colors().editor_debugger_active_line_background),
18177 window,
18178 cx,
18179 );
18180
18181 cx.notify();
18182 }
18183
18184 handled.then_some(())
18185 })
18186 .is_some()
18187 }
18188
18189 pub fn copy_file_name_without_extension(
18190 &mut self,
18191 _: &CopyFileNameWithoutExtension,
18192 _: &mut Window,
18193 cx: &mut Context<Self>,
18194 ) {
18195 if let Some(file) = self.target_file(cx) {
18196 if let Some(file_stem) = file.path().file_stem() {
18197 if let Some(name) = file_stem.to_str() {
18198 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18199 }
18200 }
18201 }
18202 }
18203
18204 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18205 if let Some(file) = self.target_file(cx) {
18206 if let Some(file_name) = file.path().file_name() {
18207 if let Some(name) = file_name.to_str() {
18208 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18209 }
18210 }
18211 }
18212 }
18213
18214 pub fn toggle_git_blame(
18215 &mut self,
18216 _: &::git::Blame,
18217 window: &mut Window,
18218 cx: &mut Context<Self>,
18219 ) {
18220 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18221
18222 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18223 self.start_git_blame(true, window, cx);
18224 }
18225
18226 cx.notify();
18227 }
18228
18229 pub fn toggle_git_blame_inline(
18230 &mut self,
18231 _: &ToggleGitBlameInline,
18232 window: &mut Window,
18233 cx: &mut Context<Self>,
18234 ) {
18235 self.toggle_git_blame_inline_internal(true, window, cx);
18236 cx.notify();
18237 }
18238
18239 pub fn open_git_blame_commit(
18240 &mut self,
18241 _: &OpenGitBlameCommit,
18242 window: &mut Window,
18243 cx: &mut Context<Self>,
18244 ) {
18245 self.open_git_blame_commit_internal(window, cx);
18246 }
18247
18248 fn open_git_blame_commit_internal(
18249 &mut self,
18250 window: &mut Window,
18251 cx: &mut Context<Self>,
18252 ) -> Option<()> {
18253 let blame = self.blame.as_ref()?;
18254 let snapshot = self.snapshot(window, cx);
18255 let cursor = self.selections.newest::<Point>(cx).head();
18256 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18257 let blame_entry = blame
18258 .update(cx, |blame, cx| {
18259 blame
18260 .blame_for_rows(
18261 &[RowInfo {
18262 buffer_id: Some(buffer.remote_id()),
18263 buffer_row: Some(point.row),
18264 ..Default::default()
18265 }],
18266 cx,
18267 )
18268 .next()
18269 })
18270 .flatten()?;
18271 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18272 let repo = blame.read(cx).repository(cx)?;
18273 let workspace = self.workspace()?.downgrade();
18274 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18275 None
18276 }
18277
18278 pub fn git_blame_inline_enabled(&self) -> bool {
18279 self.git_blame_inline_enabled
18280 }
18281
18282 pub fn toggle_selection_menu(
18283 &mut self,
18284 _: &ToggleSelectionMenu,
18285 _: &mut Window,
18286 cx: &mut Context<Self>,
18287 ) {
18288 self.show_selection_menu = self
18289 .show_selection_menu
18290 .map(|show_selections_menu| !show_selections_menu)
18291 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18292
18293 cx.notify();
18294 }
18295
18296 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18297 self.show_selection_menu
18298 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18299 }
18300
18301 fn start_git_blame(
18302 &mut self,
18303 user_triggered: bool,
18304 window: &mut Window,
18305 cx: &mut Context<Self>,
18306 ) {
18307 if let Some(project) = self.project.as_ref() {
18308 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18309 return;
18310 };
18311
18312 if buffer.read(cx).file().is_none() {
18313 return;
18314 }
18315
18316 let focused = self.focus_handle(cx).contains_focused(window, cx);
18317
18318 let project = project.clone();
18319 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18320 self.blame_subscription =
18321 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18322 self.blame = Some(blame);
18323 }
18324 }
18325
18326 fn toggle_git_blame_inline_internal(
18327 &mut self,
18328 user_triggered: bool,
18329 window: &mut Window,
18330 cx: &mut Context<Self>,
18331 ) {
18332 if self.git_blame_inline_enabled {
18333 self.git_blame_inline_enabled = false;
18334 self.show_git_blame_inline = false;
18335 self.show_git_blame_inline_delay_task.take();
18336 } else {
18337 self.git_blame_inline_enabled = true;
18338 self.start_git_blame_inline(user_triggered, window, cx);
18339 }
18340
18341 cx.notify();
18342 }
18343
18344 fn start_git_blame_inline(
18345 &mut self,
18346 user_triggered: bool,
18347 window: &mut Window,
18348 cx: &mut Context<Self>,
18349 ) {
18350 self.start_git_blame(user_triggered, window, cx);
18351
18352 if ProjectSettings::get_global(cx)
18353 .git
18354 .inline_blame_delay()
18355 .is_some()
18356 {
18357 self.start_inline_blame_timer(window, cx);
18358 } else {
18359 self.show_git_blame_inline = true
18360 }
18361 }
18362
18363 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18364 self.blame.as_ref()
18365 }
18366
18367 pub fn show_git_blame_gutter(&self) -> bool {
18368 self.show_git_blame_gutter
18369 }
18370
18371 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18372 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18373 }
18374
18375 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18376 self.show_git_blame_inline
18377 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18378 && !self.newest_selection_head_on_empty_line(cx)
18379 && self.has_blame_entries(cx)
18380 }
18381
18382 fn has_blame_entries(&self, cx: &App) -> bool {
18383 self.blame()
18384 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18385 }
18386
18387 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18388 let cursor_anchor = self.selections.newest_anchor().head();
18389
18390 let snapshot = self.buffer.read(cx).snapshot(cx);
18391 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18392
18393 snapshot.line_len(buffer_row) == 0
18394 }
18395
18396 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18397 let buffer_and_selection = maybe!({
18398 let selection = self.selections.newest::<Point>(cx);
18399 let selection_range = selection.range();
18400
18401 let multi_buffer = self.buffer().read(cx);
18402 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18403 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18404
18405 let (buffer, range, _) = if selection.reversed {
18406 buffer_ranges.first()
18407 } else {
18408 buffer_ranges.last()
18409 }?;
18410
18411 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18412 ..text::ToPoint::to_point(&range.end, &buffer).row;
18413 Some((
18414 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18415 selection,
18416 ))
18417 });
18418
18419 let Some((buffer, selection)) = buffer_and_selection else {
18420 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18421 };
18422
18423 let Some(project) = self.project.as_ref() else {
18424 return Task::ready(Err(anyhow!("editor does not have project")));
18425 };
18426
18427 project.update(cx, |project, cx| {
18428 project.get_permalink_to_line(&buffer, selection, cx)
18429 })
18430 }
18431
18432 pub fn copy_permalink_to_line(
18433 &mut self,
18434 _: &CopyPermalinkToLine,
18435 window: &mut Window,
18436 cx: &mut Context<Self>,
18437 ) {
18438 let permalink_task = self.get_permalink_to_line(cx);
18439 let workspace = self.workspace();
18440
18441 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18442 Ok(permalink) => {
18443 cx.update(|_, cx| {
18444 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18445 })
18446 .ok();
18447 }
18448 Err(err) => {
18449 let message = format!("Failed to copy permalink: {err}");
18450
18451 anyhow::Result::<()>::Err(err).log_err();
18452
18453 if let Some(workspace) = workspace {
18454 workspace
18455 .update_in(cx, |workspace, _, cx| {
18456 struct CopyPermalinkToLine;
18457
18458 workspace.show_toast(
18459 Toast::new(
18460 NotificationId::unique::<CopyPermalinkToLine>(),
18461 message,
18462 ),
18463 cx,
18464 )
18465 })
18466 .ok();
18467 }
18468 }
18469 })
18470 .detach();
18471 }
18472
18473 pub fn copy_file_location(
18474 &mut self,
18475 _: &CopyFileLocation,
18476 _: &mut Window,
18477 cx: &mut Context<Self>,
18478 ) {
18479 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18480 if let Some(file) = self.target_file(cx) {
18481 if let Some(path) = file.path().to_str() {
18482 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18483 }
18484 }
18485 }
18486
18487 pub fn open_permalink_to_line(
18488 &mut self,
18489 _: &OpenPermalinkToLine,
18490 window: &mut Window,
18491 cx: &mut Context<Self>,
18492 ) {
18493 let permalink_task = self.get_permalink_to_line(cx);
18494 let workspace = self.workspace();
18495
18496 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18497 Ok(permalink) => {
18498 cx.update(|_, cx| {
18499 cx.open_url(permalink.as_ref());
18500 })
18501 .ok();
18502 }
18503 Err(err) => {
18504 let message = format!("Failed to open permalink: {err}");
18505
18506 anyhow::Result::<()>::Err(err).log_err();
18507
18508 if let Some(workspace) = workspace {
18509 workspace
18510 .update(cx, |workspace, cx| {
18511 struct OpenPermalinkToLine;
18512
18513 workspace.show_toast(
18514 Toast::new(
18515 NotificationId::unique::<OpenPermalinkToLine>(),
18516 message,
18517 ),
18518 cx,
18519 )
18520 })
18521 .ok();
18522 }
18523 }
18524 })
18525 .detach();
18526 }
18527
18528 pub fn insert_uuid_v4(
18529 &mut self,
18530 _: &InsertUuidV4,
18531 window: &mut Window,
18532 cx: &mut Context<Self>,
18533 ) {
18534 self.insert_uuid(UuidVersion::V4, window, cx);
18535 }
18536
18537 pub fn insert_uuid_v7(
18538 &mut self,
18539 _: &InsertUuidV7,
18540 window: &mut Window,
18541 cx: &mut Context<Self>,
18542 ) {
18543 self.insert_uuid(UuidVersion::V7, window, cx);
18544 }
18545
18546 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18547 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18548 self.transact(window, cx, |this, window, cx| {
18549 let edits = this
18550 .selections
18551 .all::<Point>(cx)
18552 .into_iter()
18553 .map(|selection| {
18554 let uuid = match version {
18555 UuidVersion::V4 => uuid::Uuid::new_v4(),
18556 UuidVersion::V7 => uuid::Uuid::now_v7(),
18557 };
18558
18559 (selection.range(), uuid.to_string())
18560 });
18561 this.edit(edits, cx);
18562 this.refresh_inline_completion(true, false, window, cx);
18563 });
18564 }
18565
18566 pub fn open_selections_in_multibuffer(
18567 &mut self,
18568 _: &OpenSelectionsInMultibuffer,
18569 window: &mut Window,
18570 cx: &mut Context<Self>,
18571 ) {
18572 let multibuffer = self.buffer.read(cx);
18573
18574 let Some(buffer) = multibuffer.as_singleton() else {
18575 return;
18576 };
18577
18578 let Some(workspace) = self.workspace() else {
18579 return;
18580 };
18581
18582 let title = multibuffer.title(cx).to_string();
18583
18584 let locations = self
18585 .selections
18586 .all_anchors(cx)
18587 .into_iter()
18588 .map(|selection| Location {
18589 buffer: buffer.clone(),
18590 range: selection.start.text_anchor..selection.end.text_anchor,
18591 })
18592 .collect::<Vec<_>>();
18593
18594 cx.spawn_in(window, async move |_, cx| {
18595 workspace.update_in(cx, |workspace, window, cx| {
18596 Self::open_locations_in_multibuffer(
18597 workspace,
18598 locations,
18599 format!("Selections for '{title}'"),
18600 false,
18601 MultibufferSelectionMode::All,
18602 window,
18603 cx,
18604 );
18605 })
18606 })
18607 .detach();
18608 }
18609
18610 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18611 /// last highlight added will be used.
18612 ///
18613 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18614 pub fn highlight_rows<T: 'static>(
18615 &mut self,
18616 range: Range<Anchor>,
18617 color: Hsla,
18618 options: RowHighlightOptions,
18619 cx: &mut Context<Self>,
18620 ) {
18621 let snapshot = self.buffer().read(cx).snapshot(cx);
18622 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18623 let ix = row_highlights.binary_search_by(|highlight| {
18624 Ordering::Equal
18625 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18626 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18627 });
18628
18629 if let Err(mut ix) = ix {
18630 let index = post_inc(&mut self.highlight_order);
18631
18632 // If this range intersects with the preceding highlight, then merge it with
18633 // the preceding highlight. Otherwise insert a new highlight.
18634 let mut merged = false;
18635 if ix > 0 {
18636 let prev_highlight = &mut row_highlights[ix - 1];
18637 if prev_highlight
18638 .range
18639 .end
18640 .cmp(&range.start, &snapshot)
18641 .is_ge()
18642 {
18643 ix -= 1;
18644 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18645 prev_highlight.range.end = range.end;
18646 }
18647 merged = true;
18648 prev_highlight.index = index;
18649 prev_highlight.color = color;
18650 prev_highlight.options = options;
18651 }
18652 }
18653
18654 if !merged {
18655 row_highlights.insert(
18656 ix,
18657 RowHighlight {
18658 range: range.clone(),
18659 index,
18660 color,
18661 options,
18662 type_id: TypeId::of::<T>(),
18663 },
18664 );
18665 }
18666
18667 // If any of the following highlights intersect with this one, merge them.
18668 while let Some(next_highlight) = row_highlights.get(ix + 1) {
18669 let highlight = &row_highlights[ix];
18670 if next_highlight
18671 .range
18672 .start
18673 .cmp(&highlight.range.end, &snapshot)
18674 .is_le()
18675 {
18676 if next_highlight
18677 .range
18678 .end
18679 .cmp(&highlight.range.end, &snapshot)
18680 .is_gt()
18681 {
18682 row_highlights[ix].range.end = next_highlight.range.end;
18683 }
18684 row_highlights.remove(ix + 1);
18685 } else {
18686 break;
18687 }
18688 }
18689 }
18690 }
18691
18692 /// Remove any highlighted row ranges of the given type that intersect the
18693 /// given ranges.
18694 pub fn remove_highlighted_rows<T: 'static>(
18695 &mut self,
18696 ranges_to_remove: Vec<Range<Anchor>>,
18697 cx: &mut Context<Self>,
18698 ) {
18699 let snapshot = self.buffer().read(cx).snapshot(cx);
18700 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18701 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18702 row_highlights.retain(|highlight| {
18703 while let Some(range_to_remove) = ranges_to_remove.peek() {
18704 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18705 Ordering::Less | Ordering::Equal => {
18706 ranges_to_remove.next();
18707 }
18708 Ordering::Greater => {
18709 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18710 Ordering::Less | Ordering::Equal => {
18711 return false;
18712 }
18713 Ordering::Greater => break,
18714 }
18715 }
18716 }
18717 }
18718
18719 true
18720 })
18721 }
18722
18723 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18724 pub fn clear_row_highlights<T: 'static>(&mut self) {
18725 self.highlighted_rows.remove(&TypeId::of::<T>());
18726 }
18727
18728 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18729 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18730 self.highlighted_rows
18731 .get(&TypeId::of::<T>())
18732 .map_or(&[] as &[_], |vec| vec.as_slice())
18733 .iter()
18734 .map(|highlight| (highlight.range.clone(), highlight.color))
18735 }
18736
18737 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18738 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18739 /// Allows to ignore certain kinds of highlights.
18740 pub fn highlighted_display_rows(
18741 &self,
18742 window: &mut Window,
18743 cx: &mut App,
18744 ) -> BTreeMap<DisplayRow, LineHighlight> {
18745 let snapshot = self.snapshot(window, cx);
18746 let mut used_highlight_orders = HashMap::default();
18747 self.highlighted_rows
18748 .iter()
18749 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18750 .fold(
18751 BTreeMap::<DisplayRow, LineHighlight>::new(),
18752 |mut unique_rows, highlight| {
18753 let start = highlight.range.start.to_display_point(&snapshot);
18754 let end = highlight.range.end.to_display_point(&snapshot);
18755 let start_row = start.row().0;
18756 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18757 && end.column() == 0
18758 {
18759 end.row().0.saturating_sub(1)
18760 } else {
18761 end.row().0
18762 };
18763 for row in start_row..=end_row {
18764 let used_index =
18765 used_highlight_orders.entry(row).or_insert(highlight.index);
18766 if highlight.index >= *used_index {
18767 *used_index = highlight.index;
18768 unique_rows.insert(
18769 DisplayRow(row),
18770 LineHighlight {
18771 include_gutter: highlight.options.include_gutter,
18772 border: None,
18773 background: highlight.color.into(),
18774 type_id: Some(highlight.type_id),
18775 },
18776 );
18777 }
18778 }
18779 unique_rows
18780 },
18781 )
18782 }
18783
18784 pub fn highlighted_display_row_for_autoscroll(
18785 &self,
18786 snapshot: &DisplaySnapshot,
18787 ) -> Option<DisplayRow> {
18788 self.highlighted_rows
18789 .values()
18790 .flat_map(|highlighted_rows| highlighted_rows.iter())
18791 .filter_map(|highlight| {
18792 if highlight.options.autoscroll {
18793 Some(highlight.range.start.to_display_point(snapshot).row())
18794 } else {
18795 None
18796 }
18797 })
18798 .min()
18799 }
18800
18801 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18802 self.highlight_background::<SearchWithinRange>(
18803 ranges,
18804 |colors| colors.colors().editor_document_highlight_read_background,
18805 cx,
18806 )
18807 }
18808
18809 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18810 self.breadcrumb_header = Some(new_header);
18811 }
18812
18813 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18814 self.clear_background_highlights::<SearchWithinRange>(cx);
18815 }
18816
18817 pub fn highlight_background<T: 'static>(
18818 &mut self,
18819 ranges: &[Range<Anchor>],
18820 color_fetcher: fn(&Theme) -> Hsla,
18821 cx: &mut Context<Self>,
18822 ) {
18823 self.background_highlights.insert(
18824 HighlightKey::Type(TypeId::of::<T>()),
18825 (color_fetcher, Arc::from(ranges)),
18826 );
18827 self.scrollbar_marker_state.dirty = true;
18828 cx.notify();
18829 }
18830
18831 pub fn highlight_background_key<T: 'static>(
18832 &mut self,
18833 key: usize,
18834 ranges: &[Range<Anchor>],
18835 color_fetcher: fn(&Theme) -> Hsla,
18836 cx: &mut Context<Self>,
18837 ) {
18838 self.background_highlights.insert(
18839 HighlightKey::TypePlus(TypeId::of::<T>(), key),
18840 (color_fetcher, Arc::from(ranges)),
18841 );
18842 self.scrollbar_marker_state.dirty = true;
18843 cx.notify();
18844 }
18845
18846 pub fn clear_background_highlights<T: 'static>(
18847 &mut self,
18848 cx: &mut Context<Self>,
18849 ) -> Option<BackgroundHighlight> {
18850 let text_highlights = self
18851 .background_highlights
18852 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
18853 if !text_highlights.1.is_empty() {
18854 self.scrollbar_marker_state.dirty = true;
18855 cx.notify();
18856 }
18857 Some(text_highlights)
18858 }
18859
18860 pub fn highlight_gutter<T: 'static>(
18861 &mut self,
18862 ranges: impl Into<Vec<Range<Anchor>>>,
18863 color_fetcher: fn(&App) -> Hsla,
18864 cx: &mut Context<Self>,
18865 ) {
18866 self.gutter_highlights
18867 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
18868 cx.notify();
18869 }
18870
18871 pub fn clear_gutter_highlights<T: 'static>(
18872 &mut self,
18873 cx: &mut Context<Self>,
18874 ) -> Option<GutterHighlight> {
18875 cx.notify();
18876 self.gutter_highlights.remove(&TypeId::of::<T>())
18877 }
18878
18879 pub fn insert_gutter_highlight<T: 'static>(
18880 &mut self,
18881 range: Range<Anchor>,
18882 color_fetcher: fn(&App) -> Hsla,
18883 cx: &mut Context<Self>,
18884 ) {
18885 let snapshot = self.buffer().read(cx).snapshot(cx);
18886 let mut highlights = self
18887 .gutter_highlights
18888 .remove(&TypeId::of::<T>())
18889 .map(|(_, highlights)| highlights)
18890 .unwrap_or_default();
18891 let ix = highlights.binary_search_by(|highlight| {
18892 Ordering::Equal
18893 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
18894 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
18895 });
18896 if let Err(ix) = ix {
18897 highlights.insert(ix, range);
18898 }
18899 self.gutter_highlights
18900 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
18901 }
18902
18903 pub fn remove_gutter_highlights<T: 'static>(
18904 &mut self,
18905 ranges_to_remove: Vec<Range<Anchor>>,
18906 cx: &mut Context<Self>,
18907 ) {
18908 let snapshot = self.buffer().read(cx).snapshot(cx);
18909 let Some((color_fetcher, mut gutter_highlights)) =
18910 self.gutter_highlights.remove(&TypeId::of::<T>())
18911 else {
18912 return;
18913 };
18914 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18915 gutter_highlights.retain(|highlight| {
18916 while let Some(range_to_remove) = ranges_to_remove.peek() {
18917 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
18918 Ordering::Less | Ordering::Equal => {
18919 ranges_to_remove.next();
18920 }
18921 Ordering::Greater => {
18922 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
18923 Ordering::Less | Ordering::Equal => {
18924 return false;
18925 }
18926 Ordering::Greater => break,
18927 }
18928 }
18929 }
18930 }
18931
18932 true
18933 });
18934 self.gutter_highlights
18935 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
18936 }
18937
18938 #[cfg(feature = "test-support")]
18939 pub fn all_text_highlights(
18940 &self,
18941 window: &mut Window,
18942 cx: &mut Context<Self>,
18943 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
18944 let snapshot = self.snapshot(window, cx);
18945 self.display_map.update(cx, |display_map, _| {
18946 display_map
18947 .all_text_highlights()
18948 .map(|highlight| {
18949 let (style, ranges) = highlight.as_ref();
18950 (
18951 *style,
18952 ranges
18953 .iter()
18954 .map(|range| range.clone().to_display_points(&snapshot))
18955 .collect(),
18956 )
18957 })
18958 .collect()
18959 })
18960 }
18961
18962 #[cfg(feature = "test-support")]
18963 pub fn all_text_background_highlights(
18964 &self,
18965 window: &mut Window,
18966 cx: &mut Context<Self>,
18967 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18968 let snapshot = self.snapshot(window, cx);
18969 let buffer = &snapshot.buffer_snapshot;
18970 let start = buffer.anchor_before(0);
18971 let end = buffer.anchor_after(buffer.len());
18972 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
18973 }
18974
18975 #[cfg(feature = "test-support")]
18976 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18977 let snapshot = self.buffer().read(cx).snapshot(cx);
18978
18979 let highlights = self
18980 .background_highlights
18981 .get(&HighlightKey::Type(TypeId::of::<
18982 items::BufferSearchHighlights,
18983 >()));
18984
18985 if let Some((_color, ranges)) = highlights {
18986 ranges
18987 .iter()
18988 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
18989 .collect_vec()
18990 } else {
18991 vec![]
18992 }
18993 }
18994
18995 fn document_highlights_for_position<'a>(
18996 &'a self,
18997 position: Anchor,
18998 buffer: &'a MultiBufferSnapshot,
18999 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19000 let read_highlights = self
19001 .background_highlights
19002 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19003 .map(|h| &h.1);
19004 let write_highlights = self
19005 .background_highlights
19006 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19007 .map(|h| &h.1);
19008 let left_position = position.bias_left(buffer);
19009 let right_position = position.bias_right(buffer);
19010 read_highlights
19011 .into_iter()
19012 .chain(write_highlights)
19013 .flat_map(move |ranges| {
19014 let start_ix = match ranges.binary_search_by(|probe| {
19015 let cmp = probe.end.cmp(&left_position, buffer);
19016 if cmp.is_ge() {
19017 Ordering::Greater
19018 } else {
19019 Ordering::Less
19020 }
19021 }) {
19022 Ok(i) | Err(i) => i,
19023 };
19024
19025 ranges[start_ix..]
19026 .iter()
19027 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19028 })
19029 }
19030
19031 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19032 self.background_highlights
19033 .get(&HighlightKey::Type(TypeId::of::<T>()))
19034 .map_or(false, |(_, highlights)| !highlights.is_empty())
19035 }
19036
19037 pub fn background_highlights_in_range(
19038 &self,
19039 search_range: Range<Anchor>,
19040 display_snapshot: &DisplaySnapshot,
19041 theme: &Theme,
19042 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19043 let mut results = Vec::new();
19044 for (color_fetcher, ranges) in self.background_highlights.values() {
19045 let color = color_fetcher(theme);
19046 let start_ix = match ranges.binary_search_by(|probe| {
19047 let cmp = probe
19048 .end
19049 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19050 if cmp.is_gt() {
19051 Ordering::Greater
19052 } else {
19053 Ordering::Less
19054 }
19055 }) {
19056 Ok(i) | Err(i) => i,
19057 };
19058 for range in &ranges[start_ix..] {
19059 if range
19060 .start
19061 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19062 .is_ge()
19063 {
19064 break;
19065 }
19066
19067 let start = range.start.to_display_point(display_snapshot);
19068 let end = range.end.to_display_point(display_snapshot);
19069 results.push((start..end, color))
19070 }
19071 }
19072 results
19073 }
19074
19075 pub fn background_highlight_row_ranges<T: 'static>(
19076 &self,
19077 search_range: Range<Anchor>,
19078 display_snapshot: &DisplaySnapshot,
19079 count: usize,
19080 ) -> Vec<RangeInclusive<DisplayPoint>> {
19081 let mut results = Vec::new();
19082 let Some((_, ranges)) = self
19083 .background_highlights
19084 .get(&HighlightKey::Type(TypeId::of::<T>()))
19085 else {
19086 return vec![];
19087 };
19088
19089 let start_ix = match ranges.binary_search_by(|probe| {
19090 let cmp = probe
19091 .end
19092 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19093 if cmp.is_gt() {
19094 Ordering::Greater
19095 } else {
19096 Ordering::Less
19097 }
19098 }) {
19099 Ok(i) | Err(i) => i,
19100 };
19101 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19102 if let (Some(start_display), Some(end_display)) = (start, end) {
19103 results.push(
19104 start_display.to_display_point(display_snapshot)
19105 ..=end_display.to_display_point(display_snapshot),
19106 );
19107 }
19108 };
19109 let mut start_row: Option<Point> = None;
19110 let mut end_row: Option<Point> = None;
19111 if ranges.len() > count {
19112 return Vec::new();
19113 }
19114 for range in &ranges[start_ix..] {
19115 if range
19116 .start
19117 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19118 .is_ge()
19119 {
19120 break;
19121 }
19122 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19123 if let Some(current_row) = &end_row {
19124 if end.row == current_row.row {
19125 continue;
19126 }
19127 }
19128 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19129 if start_row.is_none() {
19130 assert_eq!(end_row, None);
19131 start_row = Some(start);
19132 end_row = Some(end);
19133 continue;
19134 }
19135 if let Some(current_end) = end_row.as_mut() {
19136 if start.row > current_end.row + 1 {
19137 push_region(start_row, end_row);
19138 start_row = Some(start);
19139 end_row = Some(end);
19140 } else {
19141 // Merge two hunks.
19142 *current_end = end;
19143 }
19144 } else {
19145 unreachable!();
19146 }
19147 }
19148 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19149 push_region(start_row, end_row);
19150 results
19151 }
19152
19153 pub fn gutter_highlights_in_range(
19154 &self,
19155 search_range: Range<Anchor>,
19156 display_snapshot: &DisplaySnapshot,
19157 cx: &App,
19158 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19159 let mut results = Vec::new();
19160 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19161 let color = color_fetcher(cx);
19162 let start_ix = match ranges.binary_search_by(|probe| {
19163 let cmp = probe
19164 .end
19165 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19166 if cmp.is_gt() {
19167 Ordering::Greater
19168 } else {
19169 Ordering::Less
19170 }
19171 }) {
19172 Ok(i) | Err(i) => i,
19173 };
19174 for range in &ranges[start_ix..] {
19175 if range
19176 .start
19177 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19178 .is_ge()
19179 {
19180 break;
19181 }
19182
19183 let start = range.start.to_display_point(display_snapshot);
19184 let end = range.end.to_display_point(display_snapshot);
19185 results.push((start..end, color))
19186 }
19187 }
19188 results
19189 }
19190
19191 /// Get the text ranges corresponding to the redaction query
19192 pub fn redacted_ranges(
19193 &self,
19194 search_range: Range<Anchor>,
19195 display_snapshot: &DisplaySnapshot,
19196 cx: &App,
19197 ) -> Vec<Range<DisplayPoint>> {
19198 display_snapshot
19199 .buffer_snapshot
19200 .redacted_ranges(search_range, |file| {
19201 if let Some(file) = file {
19202 file.is_private()
19203 && EditorSettings::get(
19204 Some(SettingsLocation {
19205 worktree_id: file.worktree_id(cx),
19206 path: file.path().as_ref(),
19207 }),
19208 cx,
19209 )
19210 .redact_private_values
19211 } else {
19212 false
19213 }
19214 })
19215 .map(|range| {
19216 range.start.to_display_point(display_snapshot)
19217 ..range.end.to_display_point(display_snapshot)
19218 })
19219 .collect()
19220 }
19221
19222 pub fn highlight_text_key<T: 'static>(
19223 &mut self,
19224 key: usize,
19225 ranges: Vec<Range<Anchor>>,
19226 style: HighlightStyle,
19227 cx: &mut Context<Self>,
19228 ) {
19229 self.display_map.update(cx, |map, _| {
19230 map.highlight_text(
19231 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19232 ranges,
19233 style,
19234 );
19235 });
19236 cx.notify();
19237 }
19238
19239 pub fn highlight_text<T: 'static>(
19240 &mut self,
19241 ranges: Vec<Range<Anchor>>,
19242 style: HighlightStyle,
19243 cx: &mut Context<Self>,
19244 ) {
19245 self.display_map.update(cx, |map, _| {
19246 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19247 });
19248 cx.notify();
19249 }
19250
19251 pub(crate) fn highlight_inlays<T: 'static>(
19252 &mut self,
19253 highlights: Vec<InlayHighlight>,
19254 style: HighlightStyle,
19255 cx: &mut Context<Self>,
19256 ) {
19257 self.display_map.update(cx, |map, _| {
19258 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19259 });
19260 cx.notify();
19261 }
19262
19263 pub fn text_highlights<'a, T: 'static>(
19264 &'a self,
19265 cx: &'a App,
19266 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19267 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19268 }
19269
19270 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19271 let cleared = self
19272 .display_map
19273 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19274 if cleared {
19275 cx.notify();
19276 }
19277 }
19278
19279 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19280 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19281 && self.focus_handle.is_focused(window)
19282 }
19283
19284 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19285 self.show_cursor_when_unfocused = is_enabled;
19286 cx.notify();
19287 }
19288
19289 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19290 cx.notify();
19291 }
19292
19293 fn on_debug_session_event(
19294 &mut self,
19295 _session: Entity<Session>,
19296 event: &SessionEvent,
19297 cx: &mut Context<Self>,
19298 ) {
19299 match event {
19300 SessionEvent::InvalidateInlineValue => {
19301 self.refresh_inline_values(cx);
19302 }
19303 _ => {}
19304 }
19305 }
19306
19307 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19308 let Some(project) = self.project.clone() else {
19309 return;
19310 };
19311
19312 if !self.inline_value_cache.enabled {
19313 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19314 self.splice_inlays(&inlays, Vec::new(), cx);
19315 return;
19316 }
19317
19318 let current_execution_position = self
19319 .highlighted_rows
19320 .get(&TypeId::of::<ActiveDebugLine>())
19321 .and_then(|lines| lines.last().map(|line| line.range.end));
19322
19323 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19324 let inline_values = editor
19325 .update(cx, |editor, cx| {
19326 let Some(current_execution_position) = current_execution_position else {
19327 return Some(Task::ready(Ok(Vec::new())));
19328 };
19329
19330 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19331 let snapshot = buffer.snapshot(cx);
19332
19333 let excerpt = snapshot.excerpt_containing(
19334 current_execution_position..current_execution_position,
19335 )?;
19336
19337 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19338 })?;
19339
19340 let range =
19341 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19342
19343 project.inline_values(buffer, range, cx)
19344 })
19345 .ok()
19346 .flatten()?
19347 .await
19348 .context("refreshing debugger inlays")
19349 .log_err()?;
19350
19351 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19352
19353 for (buffer_id, inline_value) in inline_values
19354 .into_iter()
19355 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19356 {
19357 buffer_inline_values
19358 .entry(buffer_id)
19359 .or_default()
19360 .push(inline_value);
19361 }
19362
19363 editor
19364 .update(cx, |editor, cx| {
19365 let snapshot = editor.buffer.read(cx).snapshot(cx);
19366 let mut new_inlays = Vec::default();
19367
19368 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19369 let buffer_id = buffer_snapshot.remote_id();
19370 buffer_inline_values
19371 .get(&buffer_id)
19372 .into_iter()
19373 .flatten()
19374 .for_each(|hint| {
19375 let inlay = Inlay::debugger(
19376 post_inc(&mut editor.next_inlay_id),
19377 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19378 hint.text(),
19379 );
19380
19381 new_inlays.push(inlay);
19382 });
19383 }
19384
19385 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19386 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19387
19388 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19389 })
19390 .ok()?;
19391 Some(())
19392 });
19393 }
19394
19395 fn on_buffer_event(
19396 &mut self,
19397 multibuffer: &Entity<MultiBuffer>,
19398 event: &multi_buffer::Event,
19399 window: &mut Window,
19400 cx: &mut Context<Self>,
19401 ) {
19402 match event {
19403 multi_buffer::Event::Edited {
19404 singleton_buffer_edited,
19405 edited_buffer,
19406 } => {
19407 self.scrollbar_marker_state.dirty = true;
19408 self.active_indent_guides_state.dirty = true;
19409 self.refresh_active_diagnostics(cx);
19410 self.refresh_code_actions(window, cx);
19411 self.refresh_selected_text_highlights(true, window, cx);
19412 refresh_matching_bracket_highlights(self, window, cx);
19413 if self.has_active_inline_completion() {
19414 self.update_visible_inline_completion(window, cx);
19415 }
19416 if let Some(project) = self.project.as_ref() {
19417 if let Some(edited_buffer) = edited_buffer {
19418 project.update(cx, |project, cx| {
19419 self.registered_buffers
19420 .entry(edited_buffer.read(cx).remote_id())
19421 .or_insert_with(|| {
19422 project
19423 .register_buffer_with_language_servers(&edited_buffer, cx)
19424 });
19425 });
19426 }
19427 }
19428 cx.emit(EditorEvent::BufferEdited);
19429 cx.emit(SearchEvent::MatchesInvalidated);
19430
19431 if let Some(buffer) = edited_buffer {
19432 self.update_lsp_data(None, Some(buffer.read(cx).remote_id()), window, cx);
19433 }
19434
19435 if *singleton_buffer_edited {
19436 if let Some(buffer) = edited_buffer {
19437 if buffer.read(cx).file().is_none() {
19438 cx.emit(EditorEvent::TitleChanged);
19439 }
19440 }
19441 if let Some(project) = &self.project {
19442 #[allow(clippy::mutable_key_type)]
19443 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19444 multibuffer
19445 .all_buffers()
19446 .into_iter()
19447 .filter_map(|buffer| {
19448 buffer.update(cx, |buffer, cx| {
19449 let language = buffer.language()?;
19450 let should_discard = project.update(cx, |project, cx| {
19451 project.is_local()
19452 && !project.has_language_servers_for(buffer, cx)
19453 });
19454 should_discard.not().then_some(language.clone())
19455 })
19456 })
19457 .collect::<HashSet<_>>()
19458 });
19459 if !languages_affected.is_empty() {
19460 self.refresh_inlay_hints(
19461 InlayHintRefreshReason::BufferEdited(languages_affected),
19462 cx,
19463 );
19464 }
19465 }
19466 }
19467
19468 let Some(project) = &self.project else { return };
19469 let (telemetry, is_via_ssh) = {
19470 let project = project.read(cx);
19471 let telemetry = project.client().telemetry().clone();
19472 let is_via_ssh = project.is_via_ssh();
19473 (telemetry, is_via_ssh)
19474 };
19475 refresh_linked_ranges(self, window, cx);
19476 telemetry.log_edit_event("editor", is_via_ssh);
19477 }
19478 multi_buffer::Event::ExcerptsAdded {
19479 buffer,
19480 predecessor,
19481 excerpts,
19482 } => {
19483 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19484 let buffer_id = buffer.read(cx).remote_id();
19485 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19486 if let Some(project) = &self.project {
19487 update_uncommitted_diff_for_buffer(
19488 cx.entity(),
19489 project,
19490 [buffer.clone()],
19491 self.buffer.clone(),
19492 cx,
19493 )
19494 .detach();
19495 }
19496 }
19497 self.update_lsp_data(None, Some(buffer_id), window, cx);
19498 cx.emit(EditorEvent::ExcerptsAdded {
19499 buffer: buffer.clone(),
19500 predecessor: *predecessor,
19501 excerpts: excerpts.clone(),
19502 });
19503 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19504 }
19505 multi_buffer::Event::ExcerptsRemoved {
19506 ids,
19507 removed_buffer_ids,
19508 } => {
19509 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19510 let buffer = self.buffer.read(cx);
19511 self.registered_buffers
19512 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19513 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19514 cx.emit(EditorEvent::ExcerptsRemoved {
19515 ids: ids.clone(),
19516 removed_buffer_ids: removed_buffer_ids.clone(),
19517 });
19518 }
19519 multi_buffer::Event::ExcerptsEdited {
19520 excerpt_ids,
19521 buffer_ids,
19522 } => {
19523 self.display_map.update(cx, |map, cx| {
19524 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19525 });
19526 cx.emit(EditorEvent::ExcerptsEdited {
19527 ids: excerpt_ids.clone(),
19528 });
19529 }
19530 multi_buffer::Event::ExcerptsExpanded { ids } => {
19531 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19532 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19533 }
19534 multi_buffer::Event::Reparsed(buffer_id) => {
19535 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19536 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19537
19538 cx.emit(EditorEvent::Reparsed(*buffer_id));
19539 }
19540 multi_buffer::Event::DiffHunksToggled => {
19541 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19542 }
19543 multi_buffer::Event::LanguageChanged(buffer_id) => {
19544 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19545 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19546 cx.emit(EditorEvent::Reparsed(*buffer_id));
19547 cx.notify();
19548 }
19549 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19550 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19551 multi_buffer::Event::FileHandleChanged
19552 | multi_buffer::Event::Reloaded
19553 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19554 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19555 multi_buffer::Event::DiagnosticsUpdated => {
19556 self.update_diagnostics_state(window, cx);
19557 }
19558 _ => {}
19559 };
19560 }
19561
19562 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19563 if !self.diagnostics_enabled() {
19564 return;
19565 }
19566 self.refresh_active_diagnostics(cx);
19567 self.refresh_inline_diagnostics(true, window, cx);
19568 self.scrollbar_marker_state.dirty = true;
19569 cx.notify();
19570 }
19571
19572 pub fn start_temporary_diff_override(&mut self) {
19573 self.load_diff_task.take();
19574 self.temporary_diff_override = true;
19575 }
19576
19577 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19578 self.temporary_diff_override = false;
19579 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19580 self.buffer.update(cx, |buffer, cx| {
19581 buffer.set_all_diff_hunks_collapsed(cx);
19582 });
19583
19584 if let Some(project) = self.project.clone() {
19585 self.load_diff_task = Some(
19586 update_uncommitted_diff_for_buffer(
19587 cx.entity(),
19588 &project,
19589 self.buffer.read(cx).all_buffers(),
19590 self.buffer.clone(),
19591 cx,
19592 )
19593 .shared(),
19594 );
19595 }
19596 }
19597
19598 fn on_display_map_changed(
19599 &mut self,
19600 _: Entity<DisplayMap>,
19601 _: &mut Window,
19602 cx: &mut Context<Self>,
19603 ) {
19604 cx.notify();
19605 }
19606
19607 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19608 let new_severity = if self.diagnostics_enabled() {
19609 EditorSettings::get_global(cx)
19610 .diagnostics_max_severity
19611 .unwrap_or(DiagnosticSeverity::Hint)
19612 } else {
19613 DiagnosticSeverity::Off
19614 };
19615 self.set_max_diagnostics_severity(new_severity, cx);
19616 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19617 self.update_edit_prediction_settings(cx);
19618 self.refresh_inline_completion(true, false, window, cx);
19619 self.refresh_inlay_hints(
19620 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19621 self.selections.newest_anchor().head(),
19622 &self.buffer.read(cx).snapshot(cx),
19623 cx,
19624 )),
19625 cx,
19626 );
19627
19628 let old_cursor_shape = self.cursor_shape;
19629
19630 {
19631 let editor_settings = EditorSettings::get_global(cx);
19632 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19633 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19634 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19635 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19636 self.drag_and_drop_selection_enabled = editor_settings.drag_and_drop_selection;
19637 }
19638
19639 if old_cursor_shape != self.cursor_shape {
19640 cx.emit(EditorEvent::CursorShapeChanged);
19641 }
19642
19643 let project_settings = ProjectSettings::get_global(cx);
19644 self.serialize_dirty_buffers =
19645 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19646
19647 if self.mode.is_full() {
19648 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19649 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19650 if self.show_inline_diagnostics != show_inline_diagnostics {
19651 self.show_inline_diagnostics = show_inline_diagnostics;
19652 self.refresh_inline_diagnostics(false, window, cx);
19653 }
19654
19655 if self.git_blame_inline_enabled != inline_blame_enabled {
19656 self.toggle_git_blame_inline_internal(false, window, cx);
19657 }
19658
19659 let minimap_settings = EditorSettings::get_global(cx).minimap;
19660 if self.minimap_visibility != MinimapVisibility::Disabled {
19661 if self.minimap_visibility.settings_visibility()
19662 != minimap_settings.minimap_enabled()
19663 {
19664 self.set_minimap_visibility(
19665 MinimapVisibility::for_mode(self.mode(), cx),
19666 window,
19667 cx,
19668 );
19669 } else if let Some(minimap_entity) = self.minimap.as_ref() {
19670 minimap_entity.update(cx, |minimap_editor, cx| {
19671 minimap_editor.update_minimap_configuration(minimap_settings, cx)
19672 })
19673 }
19674 }
19675 }
19676
19677 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
19678 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
19679 }) {
19680 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
19681 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
19682 }
19683 self.refresh_colors(None, None, window, cx);
19684 }
19685
19686 cx.notify();
19687 }
19688
19689 pub fn set_searchable(&mut self, searchable: bool) {
19690 self.searchable = searchable;
19691 }
19692
19693 pub fn searchable(&self) -> bool {
19694 self.searchable
19695 }
19696
19697 fn open_proposed_changes_editor(
19698 &mut self,
19699 _: &OpenProposedChangesEditor,
19700 window: &mut Window,
19701 cx: &mut Context<Self>,
19702 ) {
19703 let Some(workspace) = self.workspace() else {
19704 cx.propagate();
19705 return;
19706 };
19707
19708 let selections = self.selections.all::<usize>(cx);
19709 let multi_buffer = self.buffer.read(cx);
19710 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19711 let mut new_selections_by_buffer = HashMap::default();
19712 for selection in selections {
19713 for (buffer, range, _) in
19714 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
19715 {
19716 let mut range = range.to_point(buffer);
19717 range.start.column = 0;
19718 range.end.column = buffer.line_len(range.end.row);
19719 new_selections_by_buffer
19720 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
19721 .or_insert(Vec::new())
19722 .push(range)
19723 }
19724 }
19725
19726 let proposed_changes_buffers = new_selections_by_buffer
19727 .into_iter()
19728 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
19729 .collect::<Vec<_>>();
19730 let proposed_changes_editor = cx.new(|cx| {
19731 ProposedChangesEditor::new(
19732 "Proposed changes",
19733 proposed_changes_buffers,
19734 self.project.clone(),
19735 window,
19736 cx,
19737 )
19738 });
19739
19740 window.defer(cx, move |window, cx| {
19741 workspace.update(cx, |workspace, cx| {
19742 workspace.active_pane().update(cx, |pane, cx| {
19743 pane.add_item(
19744 Box::new(proposed_changes_editor),
19745 true,
19746 true,
19747 None,
19748 window,
19749 cx,
19750 );
19751 });
19752 });
19753 });
19754 }
19755
19756 pub fn open_excerpts_in_split(
19757 &mut self,
19758 _: &OpenExcerptsSplit,
19759 window: &mut Window,
19760 cx: &mut Context<Self>,
19761 ) {
19762 self.open_excerpts_common(None, true, window, cx)
19763 }
19764
19765 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
19766 self.open_excerpts_common(None, false, window, cx)
19767 }
19768
19769 fn open_excerpts_common(
19770 &mut self,
19771 jump_data: Option<JumpData>,
19772 split: bool,
19773 window: &mut Window,
19774 cx: &mut Context<Self>,
19775 ) {
19776 let Some(workspace) = self.workspace() else {
19777 cx.propagate();
19778 return;
19779 };
19780
19781 if self.buffer.read(cx).is_singleton() {
19782 cx.propagate();
19783 return;
19784 }
19785
19786 let mut new_selections_by_buffer = HashMap::default();
19787 match &jump_data {
19788 Some(JumpData::MultiBufferPoint {
19789 excerpt_id,
19790 position,
19791 anchor,
19792 line_offset_from_top,
19793 }) => {
19794 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19795 if let Some(buffer) = multi_buffer_snapshot
19796 .buffer_id_for_excerpt(*excerpt_id)
19797 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
19798 {
19799 let buffer_snapshot = buffer.read(cx).snapshot();
19800 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
19801 language::ToPoint::to_point(anchor, &buffer_snapshot)
19802 } else {
19803 buffer_snapshot.clip_point(*position, Bias::Left)
19804 };
19805 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
19806 new_selections_by_buffer.insert(
19807 buffer,
19808 (
19809 vec![jump_to_offset..jump_to_offset],
19810 Some(*line_offset_from_top),
19811 ),
19812 );
19813 }
19814 }
19815 Some(JumpData::MultiBufferRow {
19816 row,
19817 line_offset_from_top,
19818 }) => {
19819 let point = MultiBufferPoint::new(row.0, 0);
19820 if let Some((buffer, buffer_point, _)) =
19821 self.buffer.read(cx).point_to_buffer_point(point, cx)
19822 {
19823 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
19824 new_selections_by_buffer
19825 .entry(buffer)
19826 .or_insert((Vec::new(), Some(*line_offset_from_top)))
19827 .0
19828 .push(buffer_offset..buffer_offset)
19829 }
19830 }
19831 None => {
19832 let selections = self.selections.all::<usize>(cx);
19833 let multi_buffer = self.buffer.read(cx);
19834 for selection in selections {
19835 for (snapshot, range, _, anchor) in multi_buffer
19836 .snapshot(cx)
19837 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
19838 {
19839 if let Some(anchor) = anchor {
19840 // selection is in a deleted hunk
19841 let Some(buffer_id) = anchor.buffer_id else {
19842 continue;
19843 };
19844 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
19845 continue;
19846 };
19847 let offset = text::ToOffset::to_offset(
19848 &anchor.text_anchor,
19849 &buffer_handle.read(cx).snapshot(),
19850 );
19851 let range = offset..offset;
19852 new_selections_by_buffer
19853 .entry(buffer_handle)
19854 .or_insert((Vec::new(), None))
19855 .0
19856 .push(range)
19857 } else {
19858 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
19859 else {
19860 continue;
19861 };
19862 new_selections_by_buffer
19863 .entry(buffer_handle)
19864 .or_insert((Vec::new(), None))
19865 .0
19866 .push(range)
19867 }
19868 }
19869 }
19870 }
19871 }
19872
19873 new_selections_by_buffer
19874 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
19875
19876 if new_selections_by_buffer.is_empty() {
19877 return;
19878 }
19879
19880 // We defer the pane interaction because we ourselves are a workspace item
19881 // and activating a new item causes the pane to call a method on us reentrantly,
19882 // which panics if we're on the stack.
19883 window.defer(cx, move |window, cx| {
19884 workspace.update(cx, |workspace, cx| {
19885 let pane = if split {
19886 workspace.adjacent_pane(window, cx)
19887 } else {
19888 workspace.active_pane().clone()
19889 };
19890
19891 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
19892 let editor = buffer
19893 .read(cx)
19894 .file()
19895 .is_none()
19896 .then(|| {
19897 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
19898 // so `workspace.open_project_item` will never find them, always opening a new editor.
19899 // Instead, we try to activate the existing editor in the pane first.
19900 let (editor, pane_item_index) =
19901 pane.read(cx).items().enumerate().find_map(|(i, item)| {
19902 let editor = item.downcast::<Editor>()?;
19903 let singleton_buffer =
19904 editor.read(cx).buffer().read(cx).as_singleton()?;
19905 if singleton_buffer == buffer {
19906 Some((editor, i))
19907 } else {
19908 None
19909 }
19910 })?;
19911 pane.update(cx, |pane, cx| {
19912 pane.activate_item(pane_item_index, true, true, window, cx)
19913 });
19914 Some(editor)
19915 })
19916 .flatten()
19917 .unwrap_or_else(|| {
19918 workspace.open_project_item::<Self>(
19919 pane.clone(),
19920 buffer,
19921 true,
19922 true,
19923 window,
19924 cx,
19925 )
19926 });
19927
19928 editor.update(cx, |editor, cx| {
19929 let autoscroll = match scroll_offset {
19930 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
19931 None => Autoscroll::newest(),
19932 };
19933 let nav_history = editor.nav_history.take();
19934 editor.change_selections(Some(autoscroll), window, cx, |s| {
19935 s.select_ranges(ranges);
19936 });
19937 editor.nav_history = nav_history;
19938 });
19939 }
19940 })
19941 });
19942 }
19943
19944 // For now, don't allow opening excerpts in buffers that aren't backed by
19945 // regular project files.
19946 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
19947 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
19948 }
19949
19950 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
19951 let snapshot = self.buffer.read(cx).read(cx);
19952 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
19953 Some(
19954 ranges
19955 .iter()
19956 .map(move |range| {
19957 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19958 })
19959 .collect(),
19960 )
19961 }
19962
19963 fn selection_replacement_ranges(
19964 &self,
19965 range: Range<OffsetUtf16>,
19966 cx: &mut App,
19967 ) -> Vec<Range<OffsetUtf16>> {
19968 let selections = self.selections.all::<OffsetUtf16>(cx);
19969 let newest_selection = selections
19970 .iter()
19971 .max_by_key(|selection| selection.id)
19972 .unwrap();
19973 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19974 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19975 let snapshot = self.buffer.read(cx).read(cx);
19976 selections
19977 .into_iter()
19978 .map(|mut selection| {
19979 selection.start.0 =
19980 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19981 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19982 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19983 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19984 })
19985 .collect()
19986 }
19987
19988 fn report_editor_event(
19989 &self,
19990 event_type: &'static str,
19991 file_extension: Option<String>,
19992 cx: &App,
19993 ) {
19994 if cfg!(any(test, feature = "test-support")) {
19995 return;
19996 }
19997
19998 let Some(project) = &self.project else { return };
19999
20000 // If None, we are in a file without an extension
20001 let file = self
20002 .buffer
20003 .read(cx)
20004 .as_singleton()
20005 .and_then(|b| b.read(cx).file());
20006 let file_extension = file_extension.or(file
20007 .as_ref()
20008 .and_then(|file| Path::new(file.file_name(cx)).extension())
20009 .and_then(|e| e.to_str())
20010 .map(|a| a.to_string()));
20011
20012 let vim_mode = vim_enabled(cx);
20013
20014 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20015 let copilot_enabled = edit_predictions_provider
20016 == language::language_settings::EditPredictionProvider::Copilot;
20017 let copilot_enabled_for_language = self
20018 .buffer
20019 .read(cx)
20020 .language_settings(cx)
20021 .show_edit_predictions;
20022
20023 let project = project.read(cx);
20024 telemetry::event!(
20025 event_type,
20026 file_extension,
20027 vim_mode,
20028 copilot_enabled,
20029 copilot_enabled_for_language,
20030 edit_predictions_provider,
20031 is_via_ssh = project.is_via_ssh(),
20032 );
20033 }
20034
20035 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20036 /// with each line being an array of {text, highlight} objects.
20037 fn copy_highlight_json(
20038 &mut self,
20039 _: &CopyHighlightJson,
20040 window: &mut Window,
20041 cx: &mut Context<Self>,
20042 ) {
20043 #[derive(Serialize)]
20044 struct Chunk<'a> {
20045 text: String,
20046 highlight: Option<&'a str>,
20047 }
20048
20049 let snapshot = self.buffer.read(cx).snapshot(cx);
20050 let range = self
20051 .selected_text_range(false, window, cx)
20052 .and_then(|selection| {
20053 if selection.range.is_empty() {
20054 None
20055 } else {
20056 Some(selection.range)
20057 }
20058 })
20059 .unwrap_or_else(|| 0..snapshot.len());
20060
20061 let chunks = snapshot.chunks(range, true);
20062 let mut lines = Vec::new();
20063 let mut line: VecDeque<Chunk> = VecDeque::new();
20064
20065 let Some(style) = self.style.as_ref() else {
20066 return;
20067 };
20068
20069 for chunk in chunks {
20070 let highlight = chunk
20071 .syntax_highlight_id
20072 .and_then(|id| id.name(&style.syntax));
20073 let mut chunk_lines = chunk.text.split('\n').peekable();
20074 while let Some(text) = chunk_lines.next() {
20075 let mut merged_with_last_token = false;
20076 if let Some(last_token) = line.back_mut() {
20077 if last_token.highlight == highlight {
20078 last_token.text.push_str(text);
20079 merged_with_last_token = true;
20080 }
20081 }
20082
20083 if !merged_with_last_token {
20084 line.push_back(Chunk {
20085 text: text.into(),
20086 highlight,
20087 });
20088 }
20089
20090 if chunk_lines.peek().is_some() {
20091 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20092 line.pop_front();
20093 }
20094 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20095 line.pop_back();
20096 }
20097
20098 lines.push(mem::take(&mut line));
20099 }
20100 }
20101 }
20102
20103 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20104 return;
20105 };
20106 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20107 }
20108
20109 pub fn open_context_menu(
20110 &mut self,
20111 _: &OpenContextMenu,
20112 window: &mut Window,
20113 cx: &mut Context<Self>,
20114 ) {
20115 self.request_autoscroll(Autoscroll::newest(), cx);
20116 let position = self.selections.newest_display(cx).start;
20117 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20118 }
20119
20120 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20121 &self.inlay_hint_cache
20122 }
20123
20124 pub fn replay_insert_event(
20125 &mut self,
20126 text: &str,
20127 relative_utf16_range: Option<Range<isize>>,
20128 window: &mut Window,
20129 cx: &mut Context<Self>,
20130 ) {
20131 if !self.input_enabled {
20132 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20133 return;
20134 }
20135 if let Some(relative_utf16_range) = relative_utf16_range {
20136 let selections = self.selections.all::<OffsetUtf16>(cx);
20137 self.change_selections(None, window, cx, |s| {
20138 let new_ranges = selections.into_iter().map(|range| {
20139 let start = OffsetUtf16(
20140 range
20141 .head()
20142 .0
20143 .saturating_add_signed(relative_utf16_range.start),
20144 );
20145 let end = OffsetUtf16(
20146 range
20147 .head()
20148 .0
20149 .saturating_add_signed(relative_utf16_range.end),
20150 );
20151 start..end
20152 });
20153 s.select_ranges(new_ranges);
20154 });
20155 }
20156
20157 self.handle_input(text, window, cx);
20158 }
20159
20160 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20161 let Some(provider) = self.semantics_provider.as_ref() else {
20162 return false;
20163 };
20164
20165 let mut supports = false;
20166 self.buffer().update(cx, |this, cx| {
20167 this.for_each_buffer(|buffer| {
20168 supports |= provider.supports_inlay_hints(buffer, cx);
20169 });
20170 });
20171
20172 supports
20173 }
20174
20175 pub fn is_focused(&self, window: &Window) -> bool {
20176 self.focus_handle.is_focused(window)
20177 }
20178
20179 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20180 cx.emit(EditorEvent::Focused);
20181
20182 if let Some(descendant) = self
20183 .last_focused_descendant
20184 .take()
20185 .and_then(|descendant| descendant.upgrade())
20186 {
20187 window.focus(&descendant);
20188 } else {
20189 if let Some(blame) = self.blame.as_ref() {
20190 blame.update(cx, GitBlame::focus)
20191 }
20192
20193 self.blink_manager.update(cx, BlinkManager::enable);
20194 self.show_cursor_names(window, cx);
20195 self.buffer.update(cx, |buffer, cx| {
20196 buffer.finalize_last_transaction(cx);
20197 if self.leader_id.is_none() {
20198 buffer.set_active_selections(
20199 &self.selections.disjoint_anchors(),
20200 self.selections.line_mode,
20201 self.cursor_shape,
20202 cx,
20203 );
20204 }
20205 });
20206 }
20207 }
20208
20209 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20210 cx.emit(EditorEvent::FocusedIn)
20211 }
20212
20213 fn handle_focus_out(
20214 &mut self,
20215 event: FocusOutEvent,
20216 _window: &mut Window,
20217 cx: &mut Context<Self>,
20218 ) {
20219 if event.blurred != self.focus_handle {
20220 self.last_focused_descendant = Some(event.blurred);
20221 }
20222 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20223 }
20224
20225 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20226 self.blink_manager.update(cx, BlinkManager::disable);
20227 self.buffer
20228 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20229
20230 if let Some(blame) = self.blame.as_ref() {
20231 blame.update(cx, GitBlame::blur)
20232 }
20233 if !self.hover_state.focused(window, cx) {
20234 hide_hover(self, cx);
20235 }
20236 if !self
20237 .context_menu
20238 .borrow()
20239 .as_ref()
20240 .is_some_and(|context_menu| context_menu.focused(window, cx))
20241 {
20242 self.hide_context_menu(window, cx);
20243 }
20244 self.discard_inline_completion(false, cx);
20245 cx.emit(EditorEvent::Blurred);
20246 cx.notify();
20247 }
20248
20249 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20250 let mut pending: String = window
20251 .pending_input_keystrokes()
20252 .into_iter()
20253 .flatten()
20254 .filter_map(|keystroke| {
20255 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20256 keystroke.key_char.clone()
20257 } else {
20258 None
20259 }
20260 })
20261 .collect();
20262
20263 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20264 pending = "".to_string();
20265 }
20266
20267 let existing_pending = self
20268 .text_highlights::<PendingInput>(cx)
20269 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20270 if existing_pending.is_none() && pending.is_empty() {
20271 return;
20272 }
20273 let transaction =
20274 self.transact(window, cx, |this, window, cx| {
20275 let selections = this.selections.all::<usize>(cx);
20276 let edits = selections
20277 .iter()
20278 .map(|selection| (selection.end..selection.end, pending.clone()));
20279 this.edit(edits, cx);
20280 this.change_selections(None, window, cx, |s| {
20281 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20282 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20283 }));
20284 });
20285 if let Some(existing_ranges) = existing_pending {
20286 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20287 this.edit(edits, cx);
20288 }
20289 });
20290
20291 let snapshot = self.snapshot(window, cx);
20292 let ranges = self
20293 .selections
20294 .all::<usize>(cx)
20295 .into_iter()
20296 .map(|selection| {
20297 snapshot.buffer_snapshot.anchor_after(selection.end)
20298 ..snapshot
20299 .buffer_snapshot
20300 .anchor_before(selection.end + pending.len())
20301 })
20302 .collect();
20303
20304 if pending.is_empty() {
20305 self.clear_highlights::<PendingInput>(cx);
20306 } else {
20307 self.highlight_text::<PendingInput>(
20308 ranges,
20309 HighlightStyle {
20310 underline: Some(UnderlineStyle {
20311 thickness: px(1.),
20312 color: None,
20313 wavy: false,
20314 }),
20315 ..Default::default()
20316 },
20317 cx,
20318 );
20319 }
20320
20321 self.ime_transaction = self.ime_transaction.or(transaction);
20322 if let Some(transaction) = self.ime_transaction {
20323 self.buffer.update(cx, |buffer, cx| {
20324 buffer.group_until_transaction(transaction, cx);
20325 });
20326 }
20327
20328 if self.text_highlights::<PendingInput>(cx).is_none() {
20329 self.ime_transaction.take();
20330 }
20331 }
20332
20333 pub fn register_action_renderer(
20334 &mut self,
20335 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20336 ) -> Subscription {
20337 let id = self.next_editor_action_id.post_inc();
20338 self.editor_actions
20339 .borrow_mut()
20340 .insert(id, Box::new(listener));
20341
20342 let editor_actions = self.editor_actions.clone();
20343 Subscription::new(move || {
20344 editor_actions.borrow_mut().remove(&id);
20345 })
20346 }
20347
20348 pub fn register_action<A: Action>(
20349 &mut self,
20350 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20351 ) -> Subscription {
20352 let id = self.next_editor_action_id.post_inc();
20353 let listener = Arc::new(listener);
20354 self.editor_actions.borrow_mut().insert(
20355 id,
20356 Box::new(move |_, window, _| {
20357 let listener = listener.clone();
20358 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20359 let action = action.downcast_ref().unwrap();
20360 if phase == DispatchPhase::Bubble {
20361 listener(action, window, cx)
20362 }
20363 })
20364 }),
20365 );
20366
20367 let editor_actions = self.editor_actions.clone();
20368 Subscription::new(move || {
20369 editor_actions.borrow_mut().remove(&id);
20370 })
20371 }
20372
20373 pub fn file_header_size(&self) -> u32 {
20374 FILE_HEADER_HEIGHT
20375 }
20376
20377 pub fn restore(
20378 &mut self,
20379 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20380 window: &mut Window,
20381 cx: &mut Context<Self>,
20382 ) {
20383 let workspace = self.workspace();
20384 let project = self.project.as_ref();
20385 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20386 let mut tasks = Vec::new();
20387 for (buffer_id, changes) in revert_changes {
20388 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20389 buffer.update(cx, |buffer, cx| {
20390 buffer.edit(
20391 changes
20392 .into_iter()
20393 .map(|(range, text)| (range, text.to_string())),
20394 None,
20395 cx,
20396 );
20397 });
20398
20399 if let Some(project) =
20400 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20401 {
20402 project.update(cx, |project, cx| {
20403 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20404 })
20405 }
20406 }
20407 }
20408 tasks
20409 });
20410 cx.spawn_in(window, async move |_, cx| {
20411 for (buffer, task) in save_tasks {
20412 let result = task.await;
20413 if result.is_err() {
20414 let Some(path) = buffer
20415 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20416 .ok()
20417 else {
20418 continue;
20419 };
20420 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20421 let Some(task) = cx
20422 .update_window_entity(&workspace, |workspace, window, cx| {
20423 workspace
20424 .open_path_preview(path, None, false, false, false, window, cx)
20425 })
20426 .ok()
20427 else {
20428 continue;
20429 };
20430 task.await.log_err();
20431 }
20432 }
20433 }
20434 })
20435 .detach();
20436 self.change_selections(None, window, cx, |selections| selections.refresh());
20437 }
20438
20439 pub fn to_pixel_point(
20440 &self,
20441 source: multi_buffer::Anchor,
20442 editor_snapshot: &EditorSnapshot,
20443 window: &mut Window,
20444 ) -> Option<gpui::Point<Pixels>> {
20445 let source_point = source.to_display_point(editor_snapshot);
20446 self.display_to_pixel_point(source_point, editor_snapshot, window)
20447 }
20448
20449 pub fn display_to_pixel_point(
20450 &self,
20451 source: DisplayPoint,
20452 editor_snapshot: &EditorSnapshot,
20453 window: &mut Window,
20454 ) -> Option<gpui::Point<Pixels>> {
20455 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20456 let text_layout_details = self.text_layout_details(window);
20457 let scroll_top = text_layout_details
20458 .scroll_anchor
20459 .scroll_position(editor_snapshot)
20460 .y;
20461
20462 if source.row().as_f32() < scroll_top.floor() {
20463 return None;
20464 }
20465 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20466 let source_y = line_height * (source.row().as_f32() - scroll_top);
20467 Some(gpui::Point::new(source_x, source_y))
20468 }
20469
20470 pub fn has_visible_completions_menu(&self) -> bool {
20471 !self.edit_prediction_preview_is_active()
20472 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20473 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20474 })
20475 }
20476
20477 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20478 if self.mode.is_minimap() {
20479 return;
20480 }
20481 self.addons
20482 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20483 }
20484
20485 pub fn unregister_addon<T: Addon>(&mut self) {
20486 self.addons.remove(&std::any::TypeId::of::<T>());
20487 }
20488
20489 pub fn addon<T: Addon>(&self) -> Option<&T> {
20490 let type_id = std::any::TypeId::of::<T>();
20491 self.addons
20492 .get(&type_id)
20493 .and_then(|item| item.to_any().downcast_ref::<T>())
20494 }
20495
20496 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20497 let type_id = std::any::TypeId::of::<T>();
20498 self.addons
20499 .get_mut(&type_id)
20500 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20501 }
20502
20503 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
20504 let text_layout_details = self.text_layout_details(window);
20505 let style = &text_layout_details.editor_style;
20506 let font_id = window.text_system().resolve_font(&style.text.font());
20507 let font_size = style.text.font_size.to_pixels(window.rem_size());
20508 let line_height = style.text.line_height_in_pixels(window.rem_size());
20509 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20510
20511 gpui::Size::new(em_width, line_height)
20512 }
20513
20514 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20515 self.load_diff_task.clone()
20516 }
20517
20518 fn read_metadata_from_db(
20519 &mut self,
20520 item_id: u64,
20521 workspace_id: WorkspaceId,
20522 window: &mut Window,
20523 cx: &mut Context<Editor>,
20524 ) {
20525 if self.is_singleton(cx)
20526 && !self.mode.is_minimap()
20527 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20528 {
20529 let buffer_snapshot = OnceCell::new();
20530
20531 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20532 if !folds.is_empty() {
20533 let snapshot =
20534 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20535 self.fold_ranges(
20536 folds
20537 .into_iter()
20538 .map(|(start, end)| {
20539 snapshot.clip_offset(start, Bias::Left)
20540 ..snapshot.clip_offset(end, Bias::Right)
20541 })
20542 .collect(),
20543 false,
20544 window,
20545 cx,
20546 );
20547 }
20548 }
20549
20550 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20551 if !selections.is_empty() {
20552 let snapshot =
20553 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20554 // skip adding the initial selection to selection history
20555 self.selection_history.mode = SelectionHistoryMode::Skipping;
20556 self.change_selections(None, window, cx, |s| {
20557 s.select_ranges(selections.into_iter().map(|(start, end)| {
20558 snapshot.clip_offset(start, Bias::Left)
20559 ..snapshot.clip_offset(end, Bias::Right)
20560 }));
20561 });
20562 self.selection_history.mode = SelectionHistoryMode::Normal;
20563 }
20564 };
20565 }
20566
20567 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20568 }
20569
20570 fn update_lsp_data(
20571 &mut self,
20572 for_server_id: Option<LanguageServerId>,
20573 for_buffer: Option<BufferId>,
20574 window: &mut Window,
20575 cx: &mut Context<'_, Self>,
20576 ) {
20577 self.pull_diagnostics(for_buffer, window, cx);
20578 self.refresh_colors(for_server_id, for_buffer, window, cx);
20579 }
20580}
20581
20582fn vim_enabled(cx: &App) -> bool {
20583 cx.global::<SettingsStore>()
20584 .raw_user_settings()
20585 .get("vim_mode")
20586 == Some(&serde_json::Value::Bool(true))
20587}
20588
20589fn process_completion_for_edit(
20590 completion: &Completion,
20591 intent: CompletionIntent,
20592 buffer: &Entity<Buffer>,
20593 cursor_position: &text::Anchor,
20594 cx: &mut Context<Editor>,
20595) -> CompletionEdit {
20596 let buffer = buffer.read(cx);
20597 let buffer_snapshot = buffer.snapshot();
20598 let (snippet, new_text) = if completion.is_snippet() {
20599 // Workaround for typescript language server issues so that methods don't expand within
20600 // strings and functions with type expressions. The previous point is used because the query
20601 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
20602 let mut snippet_source = completion.new_text.clone();
20603 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
20604 previous_point.column = previous_point.column.saturating_sub(1);
20605 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
20606 if scope.prefers_label_for_snippet_in_completion() {
20607 if let Some(label) = completion.label() {
20608 if matches!(
20609 completion.kind(),
20610 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20611 ) {
20612 snippet_source = label;
20613 }
20614 }
20615 }
20616 }
20617 match Snippet::parse(&snippet_source).log_err() {
20618 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20619 None => (None, completion.new_text.clone()),
20620 }
20621 } else {
20622 (None, completion.new_text.clone())
20623 };
20624
20625 let mut range_to_replace = {
20626 let replace_range = &completion.replace_range;
20627 if let CompletionSource::Lsp {
20628 insert_range: Some(insert_range),
20629 ..
20630 } = &completion.source
20631 {
20632 debug_assert_eq!(
20633 insert_range.start, replace_range.start,
20634 "insert_range and replace_range should start at the same position"
20635 );
20636 debug_assert!(
20637 insert_range
20638 .start
20639 .cmp(&cursor_position, &buffer_snapshot)
20640 .is_le(),
20641 "insert_range should start before or at cursor position"
20642 );
20643 debug_assert!(
20644 replace_range
20645 .start
20646 .cmp(&cursor_position, &buffer_snapshot)
20647 .is_le(),
20648 "replace_range should start before or at cursor position"
20649 );
20650 debug_assert!(
20651 insert_range
20652 .end
20653 .cmp(&cursor_position, &buffer_snapshot)
20654 .is_le(),
20655 "insert_range should end before or at cursor position"
20656 );
20657
20658 let should_replace = match intent {
20659 CompletionIntent::CompleteWithInsert => false,
20660 CompletionIntent::CompleteWithReplace => true,
20661 CompletionIntent::Complete | CompletionIntent::Compose => {
20662 let insert_mode =
20663 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
20664 .completions
20665 .lsp_insert_mode;
20666 match insert_mode {
20667 LspInsertMode::Insert => false,
20668 LspInsertMode::Replace => true,
20669 LspInsertMode::ReplaceSubsequence => {
20670 let mut text_to_replace = buffer.chars_for_range(
20671 buffer.anchor_before(replace_range.start)
20672 ..buffer.anchor_after(replace_range.end),
20673 );
20674 let mut current_needle = text_to_replace.next();
20675 for haystack_ch in completion.label.text.chars() {
20676 if let Some(needle_ch) = current_needle {
20677 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
20678 current_needle = text_to_replace.next();
20679 }
20680 }
20681 }
20682 current_needle.is_none()
20683 }
20684 LspInsertMode::ReplaceSuffix => {
20685 if replace_range
20686 .end
20687 .cmp(&cursor_position, &buffer_snapshot)
20688 .is_gt()
20689 {
20690 let range_after_cursor = *cursor_position..replace_range.end;
20691 let text_after_cursor = buffer
20692 .text_for_range(
20693 buffer.anchor_before(range_after_cursor.start)
20694 ..buffer.anchor_after(range_after_cursor.end),
20695 )
20696 .collect::<String>()
20697 .to_ascii_lowercase();
20698 completion
20699 .label
20700 .text
20701 .to_ascii_lowercase()
20702 .ends_with(&text_after_cursor)
20703 } else {
20704 true
20705 }
20706 }
20707 }
20708 }
20709 };
20710
20711 if should_replace {
20712 replace_range.clone()
20713 } else {
20714 insert_range.clone()
20715 }
20716 } else {
20717 replace_range.clone()
20718 }
20719 };
20720
20721 if range_to_replace
20722 .end
20723 .cmp(&cursor_position, &buffer_snapshot)
20724 .is_lt()
20725 {
20726 range_to_replace.end = *cursor_position;
20727 }
20728
20729 CompletionEdit {
20730 new_text,
20731 replace_range: range_to_replace.to_offset(&buffer),
20732 snippet,
20733 }
20734}
20735
20736struct CompletionEdit {
20737 new_text: String,
20738 replace_range: Range<usize>,
20739 snippet: Option<Snippet>,
20740}
20741
20742fn insert_extra_newline_brackets(
20743 buffer: &MultiBufferSnapshot,
20744 range: Range<usize>,
20745 language: &language::LanguageScope,
20746) -> bool {
20747 let leading_whitespace_len = buffer
20748 .reversed_chars_at(range.start)
20749 .take_while(|c| c.is_whitespace() && *c != '\n')
20750 .map(|c| c.len_utf8())
20751 .sum::<usize>();
20752 let trailing_whitespace_len = buffer
20753 .chars_at(range.end)
20754 .take_while(|c| c.is_whitespace() && *c != '\n')
20755 .map(|c| c.len_utf8())
20756 .sum::<usize>();
20757 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
20758
20759 language.brackets().any(|(pair, enabled)| {
20760 let pair_start = pair.start.trim_end();
20761 let pair_end = pair.end.trim_start();
20762
20763 enabled
20764 && pair.newline
20765 && buffer.contains_str_at(range.end, pair_end)
20766 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
20767 })
20768}
20769
20770fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
20771 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
20772 [(buffer, range, _)] => (*buffer, range.clone()),
20773 _ => return false,
20774 };
20775 let pair = {
20776 let mut result: Option<BracketMatch> = None;
20777
20778 for pair in buffer
20779 .all_bracket_ranges(range.clone())
20780 .filter(move |pair| {
20781 pair.open_range.start <= range.start && pair.close_range.end >= range.end
20782 })
20783 {
20784 let len = pair.close_range.end - pair.open_range.start;
20785
20786 if let Some(existing) = &result {
20787 let existing_len = existing.close_range.end - existing.open_range.start;
20788 if len > existing_len {
20789 continue;
20790 }
20791 }
20792
20793 result = Some(pair);
20794 }
20795
20796 result
20797 };
20798 let Some(pair) = pair else {
20799 return false;
20800 };
20801 pair.newline_only
20802 && buffer
20803 .chars_for_range(pair.open_range.end..range.start)
20804 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
20805 .all(|c| c.is_whitespace() && c != '\n')
20806}
20807
20808fn update_uncommitted_diff_for_buffer(
20809 editor: Entity<Editor>,
20810 project: &Entity<Project>,
20811 buffers: impl IntoIterator<Item = Entity<Buffer>>,
20812 buffer: Entity<MultiBuffer>,
20813 cx: &mut App,
20814) -> Task<()> {
20815 let mut tasks = Vec::new();
20816 project.update(cx, |project, cx| {
20817 for buffer in buffers {
20818 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
20819 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
20820 }
20821 }
20822 });
20823 cx.spawn(async move |cx| {
20824 let diffs = future::join_all(tasks).await;
20825 if editor
20826 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
20827 .unwrap_or(false)
20828 {
20829 return;
20830 }
20831
20832 buffer
20833 .update(cx, |buffer, cx| {
20834 for diff in diffs.into_iter().flatten() {
20835 buffer.add_diff(diff, cx);
20836 }
20837 })
20838 .ok();
20839 })
20840}
20841
20842fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
20843 let tab_size = tab_size.get() as usize;
20844 let mut width = offset;
20845
20846 for ch in text.chars() {
20847 width += if ch == '\t' {
20848 tab_size - (width % tab_size)
20849 } else {
20850 1
20851 };
20852 }
20853
20854 width - offset
20855}
20856
20857#[cfg(test)]
20858mod tests {
20859 use super::*;
20860
20861 #[test]
20862 fn test_string_size_with_expanded_tabs() {
20863 let nz = |val| NonZeroU32::new(val).unwrap();
20864 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
20865 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
20866 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
20867 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
20868 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
20869 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
20870 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
20871 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
20872 }
20873}
20874
20875/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
20876struct WordBreakingTokenizer<'a> {
20877 input: &'a str,
20878}
20879
20880impl<'a> WordBreakingTokenizer<'a> {
20881 fn new(input: &'a str) -> Self {
20882 Self { input }
20883 }
20884}
20885
20886fn is_char_ideographic(ch: char) -> bool {
20887 use unicode_script::Script::*;
20888 use unicode_script::UnicodeScript;
20889 matches!(ch.script(), Han | Tangut | Yi)
20890}
20891
20892fn is_grapheme_ideographic(text: &str) -> bool {
20893 text.chars().any(is_char_ideographic)
20894}
20895
20896fn is_grapheme_whitespace(text: &str) -> bool {
20897 text.chars().any(|x| x.is_whitespace())
20898}
20899
20900fn should_stay_with_preceding_ideograph(text: &str) -> bool {
20901 text.chars().next().map_or(false, |ch| {
20902 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
20903 })
20904}
20905
20906#[derive(PartialEq, Eq, Debug, Clone, Copy)]
20907enum WordBreakToken<'a> {
20908 Word { token: &'a str, grapheme_len: usize },
20909 InlineWhitespace { token: &'a str, grapheme_len: usize },
20910 Newline,
20911}
20912
20913impl<'a> Iterator for WordBreakingTokenizer<'a> {
20914 /// Yields a span, the count of graphemes in the token, and whether it was
20915 /// whitespace. Note that it also breaks at word boundaries.
20916 type Item = WordBreakToken<'a>;
20917
20918 fn next(&mut self) -> Option<Self::Item> {
20919 use unicode_segmentation::UnicodeSegmentation;
20920 if self.input.is_empty() {
20921 return None;
20922 }
20923
20924 let mut iter = self.input.graphemes(true).peekable();
20925 let mut offset = 0;
20926 let mut grapheme_len = 0;
20927 if let Some(first_grapheme) = iter.next() {
20928 let is_newline = first_grapheme == "\n";
20929 let is_whitespace = is_grapheme_whitespace(first_grapheme);
20930 offset += first_grapheme.len();
20931 grapheme_len += 1;
20932 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
20933 if let Some(grapheme) = iter.peek().copied() {
20934 if should_stay_with_preceding_ideograph(grapheme) {
20935 offset += grapheme.len();
20936 grapheme_len += 1;
20937 }
20938 }
20939 } else {
20940 let mut words = self.input[offset..].split_word_bound_indices().peekable();
20941 let mut next_word_bound = words.peek().copied();
20942 if next_word_bound.map_or(false, |(i, _)| i == 0) {
20943 next_word_bound = words.next();
20944 }
20945 while let Some(grapheme) = iter.peek().copied() {
20946 if next_word_bound.map_or(false, |(i, _)| i == offset) {
20947 break;
20948 };
20949 if is_grapheme_whitespace(grapheme) != is_whitespace
20950 || (grapheme == "\n") != is_newline
20951 {
20952 break;
20953 };
20954 offset += grapheme.len();
20955 grapheme_len += 1;
20956 iter.next();
20957 }
20958 }
20959 let token = &self.input[..offset];
20960 self.input = &self.input[offset..];
20961 if token == "\n" {
20962 Some(WordBreakToken::Newline)
20963 } else if is_whitespace {
20964 Some(WordBreakToken::InlineWhitespace {
20965 token,
20966 grapheme_len,
20967 })
20968 } else {
20969 Some(WordBreakToken::Word {
20970 token,
20971 grapheme_len,
20972 })
20973 }
20974 } else {
20975 None
20976 }
20977 }
20978}
20979
20980#[test]
20981fn test_word_breaking_tokenizer() {
20982 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
20983 ("", &[]),
20984 (" ", &[whitespace(" ", 2)]),
20985 ("Ʒ", &[word("Ʒ", 1)]),
20986 ("Ǽ", &[word("Ǽ", 1)]),
20987 ("⋑", &[word("⋑", 1)]),
20988 ("⋑⋑", &[word("⋑⋑", 2)]),
20989 (
20990 "原理,进而",
20991 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
20992 ),
20993 (
20994 "hello world",
20995 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
20996 ),
20997 (
20998 "hello, world",
20999 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21000 ),
21001 (
21002 " hello world",
21003 &[
21004 whitespace(" ", 2),
21005 word("hello", 5),
21006 whitespace(" ", 1),
21007 word("world", 5),
21008 ],
21009 ),
21010 (
21011 "这是什么 \n 钢笔",
21012 &[
21013 word("这", 1),
21014 word("是", 1),
21015 word("什", 1),
21016 word("么", 1),
21017 whitespace(" ", 1),
21018 newline(),
21019 whitespace(" ", 1),
21020 word("钢", 1),
21021 word("笔", 1),
21022 ],
21023 ),
21024 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21025 ];
21026
21027 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21028 WordBreakToken::Word {
21029 token,
21030 grapheme_len,
21031 }
21032 }
21033
21034 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21035 WordBreakToken::InlineWhitespace {
21036 token,
21037 grapheme_len,
21038 }
21039 }
21040
21041 fn newline() -> WordBreakToken<'static> {
21042 WordBreakToken::Newline
21043 }
21044
21045 for (input, result) in tests {
21046 assert_eq!(
21047 WordBreakingTokenizer::new(input)
21048 .collect::<Vec<_>>()
21049 .as_slice(),
21050 *result,
21051 );
21052 }
21053}
21054
21055fn wrap_with_prefix(
21056 line_prefix: String,
21057 unwrapped_text: String,
21058 wrap_column: usize,
21059 tab_size: NonZeroU32,
21060 preserve_existing_whitespace: bool,
21061) -> String {
21062 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
21063 let mut wrapped_text = String::new();
21064 let mut current_line = line_prefix.clone();
21065
21066 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21067 let mut current_line_len = line_prefix_len;
21068 let mut in_whitespace = false;
21069 for token in tokenizer {
21070 let have_preceding_whitespace = in_whitespace;
21071 match token {
21072 WordBreakToken::Word {
21073 token,
21074 grapheme_len,
21075 } => {
21076 in_whitespace = false;
21077 if current_line_len + grapheme_len > wrap_column
21078 && current_line_len != line_prefix_len
21079 {
21080 wrapped_text.push_str(current_line.trim_end());
21081 wrapped_text.push('\n');
21082 current_line.truncate(line_prefix.len());
21083 current_line_len = line_prefix_len;
21084 }
21085 current_line.push_str(token);
21086 current_line_len += grapheme_len;
21087 }
21088 WordBreakToken::InlineWhitespace {
21089 mut token,
21090 mut grapheme_len,
21091 } => {
21092 in_whitespace = true;
21093 if have_preceding_whitespace && !preserve_existing_whitespace {
21094 continue;
21095 }
21096 if !preserve_existing_whitespace {
21097 token = " ";
21098 grapheme_len = 1;
21099 }
21100 if current_line_len + grapheme_len > wrap_column {
21101 wrapped_text.push_str(current_line.trim_end());
21102 wrapped_text.push('\n');
21103 current_line.truncate(line_prefix.len());
21104 current_line_len = line_prefix_len;
21105 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
21106 current_line.push_str(token);
21107 current_line_len += grapheme_len;
21108 }
21109 }
21110 WordBreakToken::Newline => {
21111 in_whitespace = true;
21112 if preserve_existing_whitespace {
21113 wrapped_text.push_str(current_line.trim_end());
21114 wrapped_text.push('\n');
21115 current_line.truncate(line_prefix.len());
21116 current_line_len = line_prefix_len;
21117 } else if have_preceding_whitespace {
21118 continue;
21119 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
21120 {
21121 wrapped_text.push_str(current_line.trim_end());
21122 wrapped_text.push('\n');
21123 current_line.truncate(line_prefix.len());
21124 current_line_len = line_prefix_len;
21125 } else if current_line_len != line_prefix_len {
21126 current_line.push(' ');
21127 current_line_len += 1;
21128 }
21129 }
21130 }
21131 }
21132
21133 if !current_line.is_empty() {
21134 wrapped_text.push_str(¤t_line);
21135 }
21136 wrapped_text
21137}
21138
21139#[test]
21140fn test_wrap_with_prefix() {
21141 assert_eq!(
21142 wrap_with_prefix(
21143 "# ".to_string(),
21144 "abcdefg".to_string(),
21145 4,
21146 NonZeroU32::new(4).unwrap(),
21147 false,
21148 ),
21149 "# abcdefg"
21150 );
21151 assert_eq!(
21152 wrap_with_prefix(
21153 "".to_string(),
21154 "\thello world".to_string(),
21155 8,
21156 NonZeroU32::new(4).unwrap(),
21157 false,
21158 ),
21159 "hello\nworld"
21160 );
21161 assert_eq!(
21162 wrap_with_prefix(
21163 "// ".to_string(),
21164 "xx \nyy zz aa bb cc".to_string(),
21165 12,
21166 NonZeroU32::new(4).unwrap(),
21167 false,
21168 ),
21169 "// xx yy zz\n// aa bb cc"
21170 );
21171 assert_eq!(
21172 wrap_with_prefix(
21173 String::new(),
21174 "这是什么 \n 钢笔".to_string(),
21175 3,
21176 NonZeroU32::new(4).unwrap(),
21177 false,
21178 ),
21179 "这是什\n么 钢\n笔"
21180 );
21181}
21182
21183pub trait CollaborationHub {
21184 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21185 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21186 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21187}
21188
21189impl CollaborationHub for Entity<Project> {
21190 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21191 self.read(cx).collaborators()
21192 }
21193
21194 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21195 self.read(cx).user_store().read(cx).participant_indices()
21196 }
21197
21198 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21199 let this = self.read(cx);
21200 let user_ids = this.collaborators().values().map(|c| c.user_id);
21201 this.user_store().read(cx).participant_names(user_ids, cx)
21202 }
21203}
21204
21205pub trait SemanticsProvider {
21206 fn hover(
21207 &self,
21208 buffer: &Entity<Buffer>,
21209 position: text::Anchor,
21210 cx: &mut App,
21211 ) -> Option<Task<Vec<project::Hover>>>;
21212
21213 fn inline_values(
21214 &self,
21215 buffer_handle: Entity<Buffer>,
21216 range: Range<text::Anchor>,
21217 cx: &mut App,
21218 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21219
21220 fn inlay_hints(
21221 &self,
21222 buffer_handle: Entity<Buffer>,
21223 range: Range<text::Anchor>,
21224 cx: &mut App,
21225 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21226
21227 fn resolve_inlay_hint(
21228 &self,
21229 hint: InlayHint,
21230 buffer_handle: Entity<Buffer>,
21231 server_id: LanguageServerId,
21232 cx: &mut App,
21233 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21234
21235 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21236
21237 fn document_highlights(
21238 &self,
21239 buffer: &Entity<Buffer>,
21240 position: text::Anchor,
21241 cx: &mut App,
21242 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21243
21244 fn definitions(
21245 &self,
21246 buffer: &Entity<Buffer>,
21247 position: text::Anchor,
21248 kind: GotoDefinitionKind,
21249 cx: &mut App,
21250 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21251
21252 fn range_for_rename(
21253 &self,
21254 buffer: &Entity<Buffer>,
21255 position: text::Anchor,
21256 cx: &mut App,
21257 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21258
21259 fn perform_rename(
21260 &self,
21261 buffer: &Entity<Buffer>,
21262 position: text::Anchor,
21263 new_name: String,
21264 cx: &mut App,
21265 ) -> Option<Task<Result<ProjectTransaction>>>;
21266}
21267
21268pub trait CompletionProvider {
21269 fn completions(
21270 &self,
21271 excerpt_id: ExcerptId,
21272 buffer: &Entity<Buffer>,
21273 buffer_position: text::Anchor,
21274 trigger: CompletionContext,
21275 window: &mut Window,
21276 cx: &mut Context<Editor>,
21277 ) -> Task<Result<Vec<CompletionResponse>>>;
21278
21279 fn resolve_completions(
21280 &self,
21281 _buffer: Entity<Buffer>,
21282 _completion_indices: Vec<usize>,
21283 _completions: Rc<RefCell<Box<[Completion]>>>,
21284 _cx: &mut Context<Editor>,
21285 ) -> Task<Result<bool>> {
21286 Task::ready(Ok(false))
21287 }
21288
21289 fn apply_additional_edits_for_completion(
21290 &self,
21291 _buffer: Entity<Buffer>,
21292 _completions: Rc<RefCell<Box<[Completion]>>>,
21293 _completion_index: usize,
21294 _push_to_history: bool,
21295 _cx: &mut Context<Editor>,
21296 ) -> Task<Result<Option<language::Transaction>>> {
21297 Task::ready(Ok(None))
21298 }
21299
21300 fn is_completion_trigger(
21301 &self,
21302 buffer: &Entity<Buffer>,
21303 position: language::Anchor,
21304 text: &str,
21305 trigger_in_words: bool,
21306 menu_is_open: bool,
21307 cx: &mut Context<Editor>,
21308 ) -> bool;
21309
21310 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21311
21312 fn sort_completions(&self) -> bool {
21313 true
21314 }
21315
21316 fn filter_completions(&self) -> bool {
21317 true
21318 }
21319}
21320
21321pub trait CodeActionProvider {
21322 fn id(&self) -> Arc<str>;
21323
21324 fn code_actions(
21325 &self,
21326 buffer: &Entity<Buffer>,
21327 range: Range<text::Anchor>,
21328 window: &mut Window,
21329 cx: &mut App,
21330 ) -> Task<Result<Vec<CodeAction>>>;
21331
21332 fn apply_code_action(
21333 &self,
21334 buffer_handle: Entity<Buffer>,
21335 action: CodeAction,
21336 excerpt_id: ExcerptId,
21337 push_to_history: bool,
21338 window: &mut Window,
21339 cx: &mut App,
21340 ) -> Task<Result<ProjectTransaction>>;
21341}
21342
21343impl CodeActionProvider for Entity<Project> {
21344 fn id(&self) -> Arc<str> {
21345 "project".into()
21346 }
21347
21348 fn code_actions(
21349 &self,
21350 buffer: &Entity<Buffer>,
21351 range: Range<text::Anchor>,
21352 _window: &mut Window,
21353 cx: &mut App,
21354 ) -> Task<Result<Vec<CodeAction>>> {
21355 self.update(cx, |project, cx| {
21356 let code_lens = project.code_lens(buffer, range.clone(), cx);
21357 let code_actions = project.code_actions(buffer, range, None, cx);
21358 cx.background_spawn(async move {
21359 let (code_lens, code_actions) = join(code_lens, code_actions).await;
21360 Ok(code_lens
21361 .context("code lens fetch")?
21362 .into_iter()
21363 .chain(code_actions.context("code action fetch")?)
21364 .collect())
21365 })
21366 })
21367 }
21368
21369 fn apply_code_action(
21370 &self,
21371 buffer_handle: Entity<Buffer>,
21372 action: CodeAction,
21373 _excerpt_id: ExcerptId,
21374 push_to_history: bool,
21375 _window: &mut Window,
21376 cx: &mut App,
21377 ) -> Task<Result<ProjectTransaction>> {
21378 self.update(cx, |project, cx| {
21379 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21380 })
21381 }
21382}
21383
21384fn snippet_completions(
21385 project: &Project,
21386 buffer: &Entity<Buffer>,
21387 buffer_position: text::Anchor,
21388 cx: &mut App,
21389) -> Task<Result<CompletionResponse>> {
21390 let languages = buffer.read(cx).languages_at(buffer_position);
21391 let snippet_store = project.snippets().read(cx);
21392
21393 let scopes: Vec<_> = languages
21394 .iter()
21395 .filter_map(|language| {
21396 let language_name = language.lsp_id();
21397 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21398
21399 if snippets.is_empty() {
21400 None
21401 } else {
21402 Some((language.default_scope(), snippets))
21403 }
21404 })
21405 .collect();
21406
21407 if scopes.is_empty() {
21408 return Task::ready(Ok(CompletionResponse {
21409 completions: vec![],
21410 is_incomplete: false,
21411 }));
21412 }
21413
21414 let snapshot = buffer.read(cx).text_snapshot();
21415 let chars: String = snapshot
21416 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
21417 .collect();
21418 let executor = cx.background_executor().clone();
21419
21420 cx.background_spawn(async move {
21421 let mut is_incomplete = false;
21422 let mut completions: Vec<Completion> = Vec::new();
21423 for (scope, snippets) in scopes.into_iter() {
21424 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
21425 let mut last_word = chars
21426 .chars()
21427 .take_while(|c| classifier.is_word(*c))
21428 .collect::<String>();
21429 last_word = last_word.chars().rev().collect();
21430
21431 if last_word.is_empty() {
21432 return Ok(CompletionResponse {
21433 completions: vec![],
21434 is_incomplete: true,
21435 });
21436 }
21437
21438 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
21439 let to_lsp = |point: &text::Anchor| {
21440 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
21441 point_to_lsp(end)
21442 };
21443 let lsp_end = to_lsp(&buffer_position);
21444
21445 let candidates = snippets
21446 .iter()
21447 .enumerate()
21448 .flat_map(|(ix, snippet)| {
21449 snippet
21450 .prefix
21451 .iter()
21452 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21453 })
21454 .collect::<Vec<StringMatchCandidate>>();
21455
21456 const MAX_RESULTS: usize = 100;
21457 let mut matches = fuzzy::match_strings(
21458 &candidates,
21459 &last_word,
21460 last_word.chars().any(|c| c.is_uppercase()),
21461 true,
21462 MAX_RESULTS,
21463 &Default::default(),
21464 executor.clone(),
21465 )
21466 .await;
21467
21468 if matches.len() >= MAX_RESULTS {
21469 is_incomplete = true;
21470 }
21471
21472 // Remove all candidates where the query's start does not match the start of any word in the candidate
21473 if let Some(query_start) = last_word.chars().next() {
21474 matches.retain(|string_match| {
21475 split_words(&string_match.string).any(|word| {
21476 // Check that the first codepoint of the word as lowercase matches the first
21477 // codepoint of the query as lowercase
21478 word.chars()
21479 .flat_map(|codepoint| codepoint.to_lowercase())
21480 .zip(query_start.to_lowercase())
21481 .all(|(word_cp, query_cp)| word_cp == query_cp)
21482 })
21483 });
21484 }
21485
21486 let matched_strings = matches
21487 .into_iter()
21488 .map(|m| m.string)
21489 .collect::<HashSet<_>>();
21490
21491 completions.extend(snippets.iter().filter_map(|snippet| {
21492 let matching_prefix = snippet
21493 .prefix
21494 .iter()
21495 .find(|prefix| matched_strings.contains(*prefix))?;
21496 let start = as_offset - last_word.len();
21497 let start = snapshot.anchor_before(start);
21498 let range = start..buffer_position;
21499 let lsp_start = to_lsp(&start);
21500 let lsp_range = lsp::Range {
21501 start: lsp_start,
21502 end: lsp_end,
21503 };
21504 Some(Completion {
21505 replace_range: range,
21506 new_text: snippet.body.clone(),
21507 source: CompletionSource::Lsp {
21508 insert_range: None,
21509 server_id: LanguageServerId(usize::MAX),
21510 resolved: true,
21511 lsp_completion: Box::new(lsp::CompletionItem {
21512 label: snippet.prefix.first().unwrap().clone(),
21513 kind: Some(CompletionItemKind::SNIPPET),
21514 label_details: snippet.description.as_ref().map(|description| {
21515 lsp::CompletionItemLabelDetails {
21516 detail: Some(description.clone()),
21517 description: None,
21518 }
21519 }),
21520 insert_text_format: Some(InsertTextFormat::SNIPPET),
21521 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21522 lsp::InsertReplaceEdit {
21523 new_text: snippet.body.clone(),
21524 insert: lsp_range,
21525 replace: lsp_range,
21526 },
21527 )),
21528 filter_text: Some(snippet.body.clone()),
21529 sort_text: Some(char::MAX.to_string()),
21530 ..lsp::CompletionItem::default()
21531 }),
21532 lsp_defaults: None,
21533 },
21534 label: CodeLabel {
21535 text: matching_prefix.clone(),
21536 runs: Vec::new(),
21537 filter_range: 0..matching_prefix.len(),
21538 },
21539 icon_path: None,
21540 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
21541 single_line: snippet.name.clone().into(),
21542 plain_text: snippet
21543 .description
21544 .clone()
21545 .map(|description| description.into()),
21546 }),
21547 insert_text_mode: None,
21548 confirm: None,
21549 })
21550 }))
21551 }
21552
21553 Ok(CompletionResponse {
21554 completions,
21555 is_incomplete,
21556 })
21557 })
21558}
21559
21560impl CompletionProvider for Entity<Project> {
21561 fn completions(
21562 &self,
21563 _excerpt_id: ExcerptId,
21564 buffer: &Entity<Buffer>,
21565 buffer_position: text::Anchor,
21566 options: CompletionContext,
21567 _window: &mut Window,
21568 cx: &mut Context<Editor>,
21569 ) -> Task<Result<Vec<CompletionResponse>>> {
21570 self.update(cx, |project, cx| {
21571 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21572 let project_completions = project.completions(buffer, buffer_position, options, cx);
21573 cx.background_spawn(async move {
21574 let mut responses = project_completions.await?;
21575 let snippets = snippets.await?;
21576 if !snippets.completions.is_empty() {
21577 responses.push(snippets);
21578 }
21579 Ok(responses)
21580 })
21581 })
21582 }
21583
21584 fn resolve_completions(
21585 &self,
21586 buffer: Entity<Buffer>,
21587 completion_indices: Vec<usize>,
21588 completions: Rc<RefCell<Box<[Completion]>>>,
21589 cx: &mut Context<Editor>,
21590 ) -> Task<Result<bool>> {
21591 self.update(cx, |project, cx| {
21592 project.lsp_store().update(cx, |lsp_store, cx| {
21593 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21594 })
21595 })
21596 }
21597
21598 fn apply_additional_edits_for_completion(
21599 &self,
21600 buffer: Entity<Buffer>,
21601 completions: Rc<RefCell<Box<[Completion]>>>,
21602 completion_index: usize,
21603 push_to_history: bool,
21604 cx: &mut Context<Editor>,
21605 ) -> Task<Result<Option<language::Transaction>>> {
21606 self.update(cx, |project, cx| {
21607 project.lsp_store().update(cx, |lsp_store, cx| {
21608 lsp_store.apply_additional_edits_for_completion(
21609 buffer,
21610 completions,
21611 completion_index,
21612 push_to_history,
21613 cx,
21614 )
21615 })
21616 })
21617 }
21618
21619 fn is_completion_trigger(
21620 &self,
21621 buffer: &Entity<Buffer>,
21622 position: language::Anchor,
21623 text: &str,
21624 trigger_in_words: bool,
21625 menu_is_open: bool,
21626 cx: &mut Context<Editor>,
21627 ) -> bool {
21628 let mut chars = text.chars();
21629 let char = if let Some(char) = chars.next() {
21630 char
21631 } else {
21632 return false;
21633 };
21634 if chars.next().is_some() {
21635 return false;
21636 }
21637
21638 let buffer = buffer.read(cx);
21639 let snapshot = buffer.snapshot();
21640 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
21641 return false;
21642 }
21643 let classifier = snapshot.char_classifier_at(position).for_completion(true);
21644 if trigger_in_words && classifier.is_word(char) {
21645 return true;
21646 }
21647
21648 buffer.completion_triggers().contains(text)
21649 }
21650}
21651
21652impl SemanticsProvider for Entity<Project> {
21653 fn hover(
21654 &self,
21655 buffer: &Entity<Buffer>,
21656 position: text::Anchor,
21657 cx: &mut App,
21658 ) -> Option<Task<Vec<project::Hover>>> {
21659 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
21660 }
21661
21662 fn document_highlights(
21663 &self,
21664 buffer: &Entity<Buffer>,
21665 position: text::Anchor,
21666 cx: &mut App,
21667 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
21668 Some(self.update(cx, |project, cx| {
21669 project.document_highlights(buffer, position, cx)
21670 }))
21671 }
21672
21673 fn definitions(
21674 &self,
21675 buffer: &Entity<Buffer>,
21676 position: text::Anchor,
21677 kind: GotoDefinitionKind,
21678 cx: &mut App,
21679 ) -> Option<Task<Result<Vec<LocationLink>>>> {
21680 Some(self.update(cx, |project, cx| match kind {
21681 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
21682 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
21683 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
21684 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
21685 }))
21686 }
21687
21688 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
21689 // TODO: make this work for remote projects
21690 self.update(cx, |project, cx| {
21691 if project
21692 .active_debug_session(cx)
21693 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
21694 {
21695 return true;
21696 }
21697
21698 buffer.update(cx, |buffer, cx| {
21699 project.any_language_server_supports_inlay_hints(buffer, cx)
21700 })
21701 })
21702 }
21703
21704 fn inline_values(
21705 &self,
21706 buffer_handle: Entity<Buffer>,
21707 range: Range<text::Anchor>,
21708 cx: &mut App,
21709 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21710 self.update(cx, |project, cx| {
21711 let (session, active_stack_frame) = project.active_debug_session(cx)?;
21712
21713 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
21714 })
21715 }
21716
21717 fn inlay_hints(
21718 &self,
21719 buffer_handle: Entity<Buffer>,
21720 range: Range<text::Anchor>,
21721 cx: &mut App,
21722 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21723 Some(self.update(cx, |project, cx| {
21724 project.inlay_hints(buffer_handle, range, cx)
21725 }))
21726 }
21727
21728 fn resolve_inlay_hint(
21729 &self,
21730 hint: InlayHint,
21731 buffer_handle: Entity<Buffer>,
21732 server_id: LanguageServerId,
21733 cx: &mut App,
21734 ) -> Option<Task<anyhow::Result<InlayHint>>> {
21735 Some(self.update(cx, |project, cx| {
21736 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
21737 }))
21738 }
21739
21740 fn range_for_rename(
21741 &self,
21742 buffer: &Entity<Buffer>,
21743 position: text::Anchor,
21744 cx: &mut App,
21745 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
21746 Some(self.update(cx, |project, cx| {
21747 let buffer = buffer.clone();
21748 let task = project.prepare_rename(buffer.clone(), position, cx);
21749 cx.spawn(async move |_, cx| {
21750 Ok(match task.await? {
21751 PrepareRenameResponse::Success(range) => Some(range),
21752 PrepareRenameResponse::InvalidPosition => None,
21753 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
21754 // Fallback on using TreeSitter info to determine identifier range
21755 buffer.read_with(cx, |buffer, _| {
21756 let snapshot = buffer.snapshot();
21757 let (range, kind) = snapshot.surrounding_word(position);
21758 if kind != Some(CharKind::Word) {
21759 return None;
21760 }
21761 Some(
21762 snapshot.anchor_before(range.start)
21763 ..snapshot.anchor_after(range.end),
21764 )
21765 })?
21766 }
21767 })
21768 })
21769 }))
21770 }
21771
21772 fn perform_rename(
21773 &self,
21774 buffer: &Entity<Buffer>,
21775 position: text::Anchor,
21776 new_name: String,
21777 cx: &mut App,
21778 ) -> Option<Task<Result<ProjectTransaction>>> {
21779 Some(self.update(cx, |project, cx| {
21780 project.perform_rename(buffer.clone(), position, new_name, cx)
21781 }))
21782 }
21783}
21784
21785fn inlay_hint_settings(
21786 location: Anchor,
21787 snapshot: &MultiBufferSnapshot,
21788 cx: &mut Context<Editor>,
21789) -> InlayHintSettings {
21790 let file = snapshot.file_at(location);
21791 let language = snapshot.language_at(location).map(|l| l.name());
21792 language_settings(language, file, cx).inlay_hints
21793}
21794
21795fn consume_contiguous_rows(
21796 contiguous_row_selections: &mut Vec<Selection<Point>>,
21797 selection: &Selection<Point>,
21798 display_map: &DisplaySnapshot,
21799 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
21800) -> (MultiBufferRow, MultiBufferRow) {
21801 contiguous_row_selections.push(selection.clone());
21802 let start_row = MultiBufferRow(selection.start.row);
21803 let mut end_row = ending_row(selection, display_map);
21804
21805 while let Some(next_selection) = selections.peek() {
21806 if next_selection.start.row <= end_row.0 {
21807 end_row = ending_row(next_selection, display_map);
21808 contiguous_row_selections.push(selections.next().unwrap().clone());
21809 } else {
21810 break;
21811 }
21812 }
21813 (start_row, end_row)
21814}
21815
21816fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
21817 if next_selection.end.column > 0 || next_selection.is_empty() {
21818 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
21819 } else {
21820 MultiBufferRow(next_selection.end.row)
21821 }
21822}
21823
21824impl EditorSnapshot {
21825 pub fn remote_selections_in_range<'a>(
21826 &'a self,
21827 range: &'a Range<Anchor>,
21828 collaboration_hub: &dyn CollaborationHub,
21829 cx: &'a App,
21830 ) -> impl 'a + Iterator<Item = RemoteSelection> {
21831 let participant_names = collaboration_hub.user_names(cx);
21832 let participant_indices = collaboration_hub.user_participant_indices(cx);
21833 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
21834 let collaborators_by_replica_id = collaborators_by_peer_id
21835 .values()
21836 .map(|collaborator| (collaborator.replica_id, collaborator))
21837 .collect::<HashMap<_, _>>();
21838 self.buffer_snapshot
21839 .selections_in_range(range, false)
21840 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
21841 if replica_id == AGENT_REPLICA_ID {
21842 Some(RemoteSelection {
21843 replica_id,
21844 selection,
21845 cursor_shape,
21846 line_mode,
21847 collaborator_id: CollaboratorId::Agent,
21848 user_name: Some("Agent".into()),
21849 color: cx.theme().players().agent(),
21850 })
21851 } else {
21852 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
21853 let participant_index = participant_indices.get(&collaborator.user_id).copied();
21854 let user_name = participant_names.get(&collaborator.user_id).cloned();
21855 Some(RemoteSelection {
21856 replica_id,
21857 selection,
21858 cursor_shape,
21859 line_mode,
21860 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
21861 user_name,
21862 color: if let Some(index) = participant_index {
21863 cx.theme().players().color_for_participant(index.0)
21864 } else {
21865 cx.theme().players().absent()
21866 },
21867 })
21868 }
21869 })
21870 }
21871
21872 pub fn hunks_for_ranges(
21873 &self,
21874 ranges: impl IntoIterator<Item = Range<Point>>,
21875 ) -> Vec<MultiBufferDiffHunk> {
21876 let mut hunks = Vec::new();
21877 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
21878 HashMap::default();
21879 for query_range in ranges {
21880 let query_rows =
21881 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
21882 for hunk in self.buffer_snapshot.diff_hunks_in_range(
21883 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
21884 ) {
21885 // Include deleted hunks that are adjacent to the query range, because
21886 // otherwise they would be missed.
21887 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
21888 if hunk.status().is_deleted() {
21889 intersects_range |= hunk.row_range.start == query_rows.end;
21890 intersects_range |= hunk.row_range.end == query_rows.start;
21891 }
21892 if intersects_range {
21893 if !processed_buffer_rows
21894 .entry(hunk.buffer_id)
21895 .or_default()
21896 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
21897 {
21898 continue;
21899 }
21900 hunks.push(hunk);
21901 }
21902 }
21903 }
21904
21905 hunks
21906 }
21907
21908 fn display_diff_hunks_for_rows<'a>(
21909 &'a self,
21910 display_rows: Range<DisplayRow>,
21911 folded_buffers: &'a HashSet<BufferId>,
21912 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
21913 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
21914 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
21915
21916 self.buffer_snapshot
21917 .diff_hunks_in_range(buffer_start..buffer_end)
21918 .filter_map(|hunk| {
21919 if folded_buffers.contains(&hunk.buffer_id) {
21920 return None;
21921 }
21922
21923 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
21924 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
21925
21926 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
21927 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
21928
21929 let display_hunk = if hunk_display_start.column() != 0 {
21930 DisplayDiffHunk::Folded {
21931 display_row: hunk_display_start.row(),
21932 }
21933 } else {
21934 let mut end_row = hunk_display_end.row();
21935 if hunk_display_end.column() > 0 {
21936 end_row.0 += 1;
21937 }
21938 let is_created_file = hunk.is_created_file();
21939 DisplayDiffHunk::Unfolded {
21940 status: hunk.status(),
21941 diff_base_byte_range: hunk.diff_base_byte_range,
21942 display_row_range: hunk_display_start.row()..end_row,
21943 multi_buffer_range: Anchor::range_in_buffer(
21944 hunk.excerpt_id,
21945 hunk.buffer_id,
21946 hunk.buffer_range,
21947 ),
21948 is_created_file,
21949 }
21950 };
21951
21952 Some(display_hunk)
21953 })
21954 }
21955
21956 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
21957 self.display_snapshot.buffer_snapshot.language_at(position)
21958 }
21959
21960 pub fn is_focused(&self) -> bool {
21961 self.is_focused
21962 }
21963
21964 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
21965 self.placeholder_text.as_ref()
21966 }
21967
21968 pub fn scroll_position(&self) -> gpui::Point<f32> {
21969 self.scroll_anchor.scroll_position(&self.display_snapshot)
21970 }
21971
21972 fn gutter_dimensions(
21973 &self,
21974 font_id: FontId,
21975 font_size: Pixels,
21976 max_line_number_width: Pixels,
21977 cx: &App,
21978 ) -> Option<GutterDimensions> {
21979 if !self.show_gutter {
21980 return None;
21981 }
21982
21983 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
21984 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
21985
21986 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
21987 matches!(
21988 ProjectSettings::get_global(cx).git.git_gutter,
21989 Some(GitGutterSetting::TrackedFiles)
21990 )
21991 });
21992 let gutter_settings = EditorSettings::get_global(cx).gutter;
21993 let show_line_numbers = self
21994 .show_line_numbers
21995 .unwrap_or(gutter_settings.line_numbers);
21996 let line_gutter_width = if show_line_numbers {
21997 // Avoid flicker-like gutter resizes when the line number gains another digit by
21998 // only resizing the gutter on files with > 10**min_line_number_digits lines.
21999 let min_width_for_number_on_gutter =
22000 ch_advance * gutter_settings.min_line_number_digits as f32;
22001 max_line_number_width.max(min_width_for_number_on_gutter)
22002 } else {
22003 0.0.into()
22004 };
22005
22006 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22007 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22008
22009 let git_blame_entries_width =
22010 self.git_blame_gutter_max_author_length
22011 .map(|max_author_length| {
22012 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22013 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22014
22015 /// The number of characters to dedicate to gaps and margins.
22016 const SPACING_WIDTH: usize = 4;
22017
22018 let max_char_count = max_author_length.min(renderer.max_author_length())
22019 + ::git::SHORT_SHA_LENGTH
22020 + MAX_RELATIVE_TIMESTAMP.len()
22021 + SPACING_WIDTH;
22022
22023 ch_advance * max_char_count
22024 });
22025
22026 let is_singleton = self.buffer_snapshot.is_singleton();
22027
22028 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22029 left_padding += if !is_singleton {
22030 ch_width * 4.0
22031 } else if show_runnables || show_breakpoints {
22032 ch_width * 3.0
22033 } else if show_git_gutter && show_line_numbers {
22034 ch_width * 2.0
22035 } else if show_git_gutter || show_line_numbers {
22036 ch_width
22037 } else {
22038 px(0.)
22039 };
22040
22041 let shows_folds = is_singleton && gutter_settings.folds;
22042
22043 let right_padding = if shows_folds && show_line_numbers {
22044 ch_width * 4.0
22045 } else if shows_folds || (!is_singleton && show_line_numbers) {
22046 ch_width * 3.0
22047 } else if show_line_numbers {
22048 ch_width
22049 } else {
22050 px(0.)
22051 };
22052
22053 Some(GutterDimensions {
22054 left_padding,
22055 right_padding,
22056 width: line_gutter_width + left_padding + right_padding,
22057 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22058 git_blame_entries_width,
22059 })
22060 }
22061
22062 pub fn render_crease_toggle(
22063 &self,
22064 buffer_row: MultiBufferRow,
22065 row_contains_cursor: bool,
22066 editor: Entity<Editor>,
22067 window: &mut Window,
22068 cx: &mut App,
22069 ) -> Option<AnyElement> {
22070 let folded = self.is_line_folded(buffer_row);
22071 let mut is_foldable = false;
22072
22073 if let Some(crease) = self
22074 .crease_snapshot
22075 .query_row(buffer_row, &self.buffer_snapshot)
22076 {
22077 is_foldable = true;
22078 match crease {
22079 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22080 if let Some(render_toggle) = render_toggle {
22081 let toggle_callback =
22082 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22083 if folded {
22084 editor.update(cx, |editor, cx| {
22085 editor.fold_at(buffer_row, window, cx)
22086 });
22087 } else {
22088 editor.update(cx, |editor, cx| {
22089 editor.unfold_at(buffer_row, window, cx)
22090 });
22091 }
22092 });
22093 return Some((render_toggle)(
22094 buffer_row,
22095 folded,
22096 toggle_callback,
22097 window,
22098 cx,
22099 ));
22100 }
22101 }
22102 }
22103 }
22104
22105 is_foldable |= self.starts_indent(buffer_row);
22106
22107 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22108 Some(
22109 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22110 .toggle_state(folded)
22111 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22112 if folded {
22113 this.unfold_at(buffer_row, window, cx);
22114 } else {
22115 this.fold_at(buffer_row, window, cx);
22116 }
22117 }))
22118 .into_any_element(),
22119 )
22120 } else {
22121 None
22122 }
22123 }
22124
22125 pub fn render_crease_trailer(
22126 &self,
22127 buffer_row: MultiBufferRow,
22128 window: &mut Window,
22129 cx: &mut App,
22130 ) -> Option<AnyElement> {
22131 let folded = self.is_line_folded(buffer_row);
22132 if let Crease::Inline { render_trailer, .. } = self
22133 .crease_snapshot
22134 .query_row(buffer_row, &self.buffer_snapshot)?
22135 {
22136 let render_trailer = render_trailer.as_ref()?;
22137 Some(render_trailer(buffer_row, folded, window, cx))
22138 } else {
22139 None
22140 }
22141 }
22142}
22143
22144impl Deref for EditorSnapshot {
22145 type Target = DisplaySnapshot;
22146
22147 fn deref(&self) -> &Self::Target {
22148 &self.display_snapshot
22149 }
22150}
22151
22152#[derive(Clone, Debug, PartialEq, Eq)]
22153pub enum EditorEvent {
22154 InputIgnored {
22155 text: Arc<str>,
22156 },
22157 InputHandled {
22158 utf16_range_to_replace: Option<Range<isize>>,
22159 text: Arc<str>,
22160 },
22161 ExcerptsAdded {
22162 buffer: Entity<Buffer>,
22163 predecessor: ExcerptId,
22164 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22165 },
22166 ExcerptsRemoved {
22167 ids: Vec<ExcerptId>,
22168 removed_buffer_ids: Vec<BufferId>,
22169 },
22170 BufferFoldToggled {
22171 ids: Vec<ExcerptId>,
22172 folded: bool,
22173 },
22174 ExcerptsEdited {
22175 ids: Vec<ExcerptId>,
22176 },
22177 ExcerptsExpanded {
22178 ids: Vec<ExcerptId>,
22179 },
22180 BufferEdited,
22181 Edited {
22182 transaction_id: clock::Lamport,
22183 },
22184 Reparsed(BufferId),
22185 Focused,
22186 FocusedIn,
22187 Blurred,
22188 DirtyChanged,
22189 Saved,
22190 TitleChanged,
22191 DiffBaseChanged,
22192 SelectionsChanged {
22193 local: bool,
22194 },
22195 ScrollPositionChanged {
22196 local: bool,
22197 autoscroll: bool,
22198 },
22199 Closed,
22200 TransactionUndone {
22201 transaction_id: clock::Lamport,
22202 },
22203 TransactionBegun {
22204 transaction_id: clock::Lamport,
22205 },
22206 Reloaded,
22207 CursorShapeChanged,
22208 PushedToNavHistory {
22209 anchor: Anchor,
22210 is_deactivate: bool,
22211 },
22212}
22213
22214impl EventEmitter<EditorEvent> for Editor {}
22215
22216impl Focusable for Editor {
22217 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22218 self.focus_handle.clone()
22219 }
22220}
22221
22222impl Render for Editor {
22223 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22224 let settings = ThemeSettings::get_global(cx);
22225
22226 let mut text_style = match self.mode {
22227 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22228 color: cx.theme().colors().editor_foreground,
22229 font_family: settings.ui_font.family.clone(),
22230 font_features: settings.ui_font.features.clone(),
22231 font_fallbacks: settings.ui_font.fallbacks.clone(),
22232 font_size: rems(0.875).into(),
22233 font_weight: settings.ui_font.weight,
22234 line_height: relative(settings.buffer_line_height.value()),
22235 ..Default::default()
22236 },
22237 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22238 color: cx.theme().colors().editor_foreground,
22239 font_family: settings.buffer_font.family.clone(),
22240 font_features: settings.buffer_font.features.clone(),
22241 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22242 font_size: settings.buffer_font_size(cx).into(),
22243 font_weight: settings.buffer_font.weight,
22244 line_height: relative(settings.buffer_line_height.value()),
22245 ..Default::default()
22246 },
22247 };
22248 if let Some(text_style_refinement) = &self.text_style_refinement {
22249 text_style.refine(text_style_refinement)
22250 }
22251
22252 let background = match self.mode {
22253 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22254 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22255 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22256 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22257 };
22258
22259 EditorElement::new(
22260 &cx.entity(),
22261 EditorStyle {
22262 background,
22263 local_player: cx.theme().players().local(),
22264 text: text_style,
22265 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22266 syntax: cx.theme().syntax().clone(),
22267 status: cx.theme().status().clone(),
22268 inlay_hints_style: make_inlay_hints_style(cx),
22269 inline_completion_styles: make_suggestion_styles(cx),
22270 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22271 show_underlines: self.diagnostics_enabled(),
22272 },
22273 )
22274 }
22275}
22276
22277impl EntityInputHandler for Editor {
22278 fn text_for_range(
22279 &mut self,
22280 range_utf16: Range<usize>,
22281 adjusted_range: &mut Option<Range<usize>>,
22282 _: &mut Window,
22283 cx: &mut Context<Self>,
22284 ) -> Option<String> {
22285 let snapshot = self.buffer.read(cx).read(cx);
22286 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22287 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22288 if (start.0..end.0) != range_utf16 {
22289 adjusted_range.replace(start.0..end.0);
22290 }
22291 Some(snapshot.text_for_range(start..end).collect())
22292 }
22293
22294 fn selected_text_range(
22295 &mut self,
22296 ignore_disabled_input: bool,
22297 _: &mut Window,
22298 cx: &mut Context<Self>,
22299 ) -> Option<UTF16Selection> {
22300 // Prevent the IME menu from appearing when holding down an alphabetic key
22301 // while input is disabled.
22302 if !ignore_disabled_input && !self.input_enabled {
22303 return None;
22304 }
22305
22306 let selection = self.selections.newest::<OffsetUtf16>(cx);
22307 let range = selection.range();
22308
22309 Some(UTF16Selection {
22310 range: range.start.0..range.end.0,
22311 reversed: selection.reversed,
22312 })
22313 }
22314
22315 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22316 let snapshot = self.buffer.read(cx).read(cx);
22317 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22318 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22319 }
22320
22321 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22322 self.clear_highlights::<InputComposition>(cx);
22323 self.ime_transaction.take();
22324 }
22325
22326 fn replace_text_in_range(
22327 &mut self,
22328 range_utf16: Option<Range<usize>>,
22329 text: &str,
22330 window: &mut Window,
22331 cx: &mut Context<Self>,
22332 ) {
22333 if !self.input_enabled {
22334 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22335 return;
22336 }
22337
22338 self.transact(window, cx, |this, window, cx| {
22339 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22340 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22341 Some(this.selection_replacement_ranges(range_utf16, cx))
22342 } else {
22343 this.marked_text_ranges(cx)
22344 };
22345
22346 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22347 let newest_selection_id = this.selections.newest_anchor().id;
22348 this.selections
22349 .all::<OffsetUtf16>(cx)
22350 .iter()
22351 .zip(ranges_to_replace.iter())
22352 .find_map(|(selection, range)| {
22353 if selection.id == newest_selection_id {
22354 Some(
22355 (range.start.0 as isize - selection.head().0 as isize)
22356 ..(range.end.0 as isize - selection.head().0 as isize),
22357 )
22358 } else {
22359 None
22360 }
22361 })
22362 });
22363
22364 cx.emit(EditorEvent::InputHandled {
22365 utf16_range_to_replace: range_to_replace,
22366 text: text.into(),
22367 });
22368
22369 if let Some(new_selected_ranges) = new_selected_ranges {
22370 this.change_selections(None, window, cx, |selections| {
22371 selections.select_ranges(new_selected_ranges)
22372 });
22373 this.backspace(&Default::default(), window, cx);
22374 }
22375
22376 this.handle_input(text, window, cx);
22377 });
22378
22379 if let Some(transaction) = self.ime_transaction {
22380 self.buffer.update(cx, |buffer, cx| {
22381 buffer.group_until_transaction(transaction, cx);
22382 });
22383 }
22384
22385 self.unmark_text(window, cx);
22386 }
22387
22388 fn replace_and_mark_text_in_range(
22389 &mut self,
22390 range_utf16: Option<Range<usize>>,
22391 text: &str,
22392 new_selected_range_utf16: Option<Range<usize>>,
22393 window: &mut Window,
22394 cx: &mut Context<Self>,
22395 ) {
22396 if !self.input_enabled {
22397 return;
22398 }
22399
22400 let transaction = self.transact(window, cx, |this, window, cx| {
22401 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22402 let snapshot = this.buffer.read(cx).read(cx);
22403 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22404 for marked_range in &mut marked_ranges {
22405 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22406 marked_range.start.0 += relative_range_utf16.start;
22407 marked_range.start =
22408 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22409 marked_range.end =
22410 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22411 }
22412 }
22413 Some(marked_ranges)
22414 } else if let Some(range_utf16) = range_utf16 {
22415 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22416 Some(this.selection_replacement_ranges(range_utf16, cx))
22417 } else {
22418 None
22419 };
22420
22421 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22422 let newest_selection_id = this.selections.newest_anchor().id;
22423 this.selections
22424 .all::<OffsetUtf16>(cx)
22425 .iter()
22426 .zip(ranges_to_replace.iter())
22427 .find_map(|(selection, range)| {
22428 if selection.id == newest_selection_id {
22429 Some(
22430 (range.start.0 as isize - selection.head().0 as isize)
22431 ..(range.end.0 as isize - selection.head().0 as isize),
22432 )
22433 } else {
22434 None
22435 }
22436 })
22437 });
22438
22439 cx.emit(EditorEvent::InputHandled {
22440 utf16_range_to_replace: range_to_replace,
22441 text: text.into(),
22442 });
22443
22444 if let Some(ranges) = ranges_to_replace {
22445 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
22446 }
22447
22448 let marked_ranges = {
22449 let snapshot = this.buffer.read(cx).read(cx);
22450 this.selections
22451 .disjoint_anchors()
22452 .iter()
22453 .map(|selection| {
22454 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22455 })
22456 .collect::<Vec<_>>()
22457 };
22458
22459 if text.is_empty() {
22460 this.unmark_text(window, cx);
22461 } else {
22462 this.highlight_text::<InputComposition>(
22463 marked_ranges.clone(),
22464 HighlightStyle {
22465 underline: Some(UnderlineStyle {
22466 thickness: px(1.),
22467 color: None,
22468 wavy: false,
22469 }),
22470 ..Default::default()
22471 },
22472 cx,
22473 );
22474 }
22475
22476 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22477 let use_autoclose = this.use_autoclose;
22478 let use_auto_surround = this.use_auto_surround;
22479 this.set_use_autoclose(false);
22480 this.set_use_auto_surround(false);
22481 this.handle_input(text, window, cx);
22482 this.set_use_autoclose(use_autoclose);
22483 this.set_use_auto_surround(use_auto_surround);
22484
22485 if let Some(new_selected_range) = new_selected_range_utf16 {
22486 let snapshot = this.buffer.read(cx).read(cx);
22487 let new_selected_ranges = marked_ranges
22488 .into_iter()
22489 .map(|marked_range| {
22490 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22491 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22492 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22493 snapshot.clip_offset_utf16(new_start, Bias::Left)
22494 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22495 })
22496 .collect::<Vec<_>>();
22497
22498 drop(snapshot);
22499 this.change_selections(None, window, cx, |selections| {
22500 selections.select_ranges(new_selected_ranges)
22501 });
22502 }
22503 });
22504
22505 self.ime_transaction = self.ime_transaction.or(transaction);
22506 if let Some(transaction) = self.ime_transaction {
22507 self.buffer.update(cx, |buffer, cx| {
22508 buffer.group_until_transaction(transaction, cx);
22509 });
22510 }
22511
22512 if self.text_highlights::<InputComposition>(cx).is_none() {
22513 self.ime_transaction.take();
22514 }
22515 }
22516
22517 fn bounds_for_range(
22518 &mut self,
22519 range_utf16: Range<usize>,
22520 element_bounds: gpui::Bounds<Pixels>,
22521 window: &mut Window,
22522 cx: &mut Context<Self>,
22523 ) -> Option<gpui::Bounds<Pixels>> {
22524 let text_layout_details = self.text_layout_details(window);
22525 let gpui::Size {
22526 width: em_width,
22527 height: line_height,
22528 } = self.character_size(window);
22529
22530 let snapshot = self.snapshot(window, cx);
22531 let scroll_position = snapshot.scroll_position();
22532 let scroll_left = scroll_position.x * em_width;
22533
22534 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22535 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22536 + self.gutter_dimensions.width
22537 + self.gutter_dimensions.margin;
22538 let y = line_height * (start.row().as_f32() - scroll_position.y);
22539
22540 Some(Bounds {
22541 origin: element_bounds.origin + point(x, y),
22542 size: size(em_width, line_height),
22543 })
22544 }
22545
22546 fn character_index_for_point(
22547 &mut self,
22548 point: gpui::Point<Pixels>,
22549 _window: &mut Window,
22550 _cx: &mut Context<Self>,
22551 ) -> Option<usize> {
22552 let position_map = self.last_position_map.as_ref()?;
22553 if !position_map.text_hitbox.contains(&point) {
22554 return None;
22555 }
22556 let display_point = position_map.point_for_position(point).previous_valid;
22557 let anchor = position_map
22558 .snapshot
22559 .display_point_to_anchor(display_point, Bias::Left);
22560 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22561 Some(utf16_offset.0)
22562 }
22563}
22564
22565trait SelectionExt {
22566 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22567 fn spanned_rows(
22568 &self,
22569 include_end_if_at_line_start: bool,
22570 map: &DisplaySnapshot,
22571 ) -> Range<MultiBufferRow>;
22572}
22573
22574impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22575 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22576 let start = self
22577 .start
22578 .to_point(&map.buffer_snapshot)
22579 .to_display_point(map);
22580 let end = self
22581 .end
22582 .to_point(&map.buffer_snapshot)
22583 .to_display_point(map);
22584 if self.reversed {
22585 end..start
22586 } else {
22587 start..end
22588 }
22589 }
22590
22591 fn spanned_rows(
22592 &self,
22593 include_end_if_at_line_start: bool,
22594 map: &DisplaySnapshot,
22595 ) -> Range<MultiBufferRow> {
22596 let start = self.start.to_point(&map.buffer_snapshot);
22597 let mut end = self.end.to_point(&map.buffer_snapshot);
22598 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22599 end.row -= 1;
22600 }
22601
22602 let buffer_start = map.prev_line_boundary(start).0;
22603 let buffer_end = map.next_line_boundary(end).0;
22604 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22605 }
22606}
22607
22608impl<T: InvalidationRegion> InvalidationStack<T> {
22609 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22610 where
22611 S: Clone + ToOffset,
22612 {
22613 while let Some(region) = self.last() {
22614 let all_selections_inside_invalidation_ranges =
22615 if selections.len() == region.ranges().len() {
22616 selections
22617 .iter()
22618 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
22619 .all(|(selection, invalidation_range)| {
22620 let head = selection.head().to_offset(buffer);
22621 invalidation_range.start <= head && invalidation_range.end >= head
22622 })
22623 } else {
22624 false
22625 };
22626
22627 if all_selections_inside_invalidation_ranges {
22628 break;
22629 } else {
22630 self.pop();
22631 }
22632 }
22633 }
22634}
22635
22636impl<T> Default for InvalidationStack<T> {
22637 fn default() -> Self {
22638 Self(Default::default())
22639 }
22640}
22641
22642impl<T> Deref for InvalidationStack<T> {
22643 type Target = Vec<T>;
22644
22645 fn deref(&self) -> &Self::Target {
22646 &self.0
22647 }
22648}
22649
22650impl<T> DerefMut for InvalidationStack<T> {
22651 fn deref_mut(&mut self) -> &mut Self::Target {
22652 &mut self.0
22653 }
22654}
22655
22656impl InvalidationRegion for SnippetState {
22657 fn ranges(&self) -> &[Range<Anchor>] {
22658 &self.ranges[self.active_index]
22659 }
22660}
22661
22662fn inline_completion_edit_text(
22663 current_snapshot: &BufferSnapshot,
22664 edits: &[(Range<Anchor>, String)],
22665 edit_preview: &EditPreview,
22666 include_deletions: bool,
22667 cx: &App,
22668) -> HighlightedText {
22669 let edits = edits
22670 .iter()
22671 .map(|(anchor, text)| {
22672 (
22673 anchor.start.text_anchor..anchor.end.text_anchor,
22674 text.clone(),
22675 )
22676 })
22677 .collect::<Vec<_>>();
22678
22679 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
22680}
22681
22682pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
22683 match severity {
22684 lsp::DiagnosticSeverity::ERROR => colors.error,
22685 lsp::DiagnosticSeverity::WARNING => colors.warning,
22686 lsp::DiagnosticSeverity::INFORMATION => colors.info,
22687 lsp::DiagnosticSeverity::HINT => colors.info,
22688 _ => colors.ignored,
22689 }
22690}
22691
22692pub fn styled_runs_for_code_label<'a>(
22693 label: &'a CodeLabel,
22694 syntax_theme: &'a theme::SyntaxTheme,
22695) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
22696 let fade_out = HighlightStyle {
22697 fade_out: Some(0.35),
22698 ..Default::default()
22699 };
22700
22701 let mut prev_end = label.filter_range.end;
22702 label
22703 .runs
22704 .iter()
22705 .enumerate()
22706 .flat_map(move |(ix, (range, highlight_id))| {
22707 let style = if let Some(style) = highlight_id.style(syntax_theme) {
22708 style
22709 } else {
22710 return Default::default();
22711 };
22712 let mut muted_style = style;
22713 muted_style.highlight(fade_out);
22714
22715 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
22716 if range.start >= label.filter_range.end {
22717 if range.start > prev_end {
22718 runs.push((prev_end..range.start, fade_out));
22719 }
22720 runs.push((range.clone(), muted_style));
22721 } else if range.end <= label.filter_range.end {
22722 runs.push((range.clone(), style));
22723 } else {
22724 runs.push((range.start..label.filter_range.end, style));
22725 runs.push((label.filter_range.end..range.end, muted_style));
22726 }
22727 prev_end = cmp::max(prev_end, range.end);
22728
22729 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
22730 runs.push((prev_end..label.text.len(), fade_out));
22731 }
22732
22733 runs
22734 })
22735}
22736
22737pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
22738 let mut prev_index = 0;
22739 let mut prev_codepoint: Option<char> = None;
22740 text.char_indices()
22741 .chain([(text.len(), '\0')])
22742 .filter_map(move |(index, codepoint)| {
22743 let prev_codepoint = prev_codepoint.replace(codepoint)?;
22744 let is_boundary = index == text.len()
22745 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
22746 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
22747 if is_boundary {
22748 let chunk = &text[prev_index..index];
22749 prev_index = index;
22750 Some(chunk)
22751 } else {
22752 None
22753 }
22754 })
22755}
22756
22757pub trait RangeToAnchorExt: Sized {
22758 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
22759
22760 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
22761 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
22762 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
22763 }
22764}
22765
22766impl<T: ToOffset> RangeToAnchorExt for Range<T> {
22767 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
22768 let start_offset = self.start.to_offset(snapshot);
22769 let end_offset = self.end.to_offset(snapshot);
22770 if start_offset == end_offset {
22771 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
22772 } else {
22773 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
22774 }
22775 }
22776}
22777
22778pub trait RowExt {
22779 fn as_f32(&self) -> f32;
22780
22781 fn next_row(&self) -> Self;
22782
22783 fn previous_row(&self) -> Self;
22784
22785 fn minus(&self, other: Self) -> u32;
22786}
22787
22788impl RowExt for DisplayRow {
22789 fn as_f32(&self) -> f32 {
22790 self.0 as f32
22791 }
22792
22793 fn next_row(&self) -> Self {
22794 Self(self.0 + 1)
22795 }
22796
22797 fn previous_row(&self) -> Self {
22798 Self(self.0.saturating_sub(1))
22799 }
22800
22801 fn minus(&self, other: Self) -> u32 {
22802 self.0 - other.0
22803 }
22804}
22805
22806impl RowExt for MultiBufferRow {
22807 fn as_f32(&self) -> f32 {
22808 self.0 as f32
22809 }
22810
22811 fn next_row(&self) -> Self {
22812 Self(self.0 + 1)
22813 }
22814
22815 fn previous_row(&self) -> Self {
22816 Self(self.0.saturating_sub(1))
22817 }
22818
22819 fn minus(&self, other: Self) -> u32 {
22820 self.0 - other.0
22821 }
22822}
22823
22824trait RowRangeExt {
22825 type Row;
22826
22827 fn len(&self) -> usize;
22828
22829 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
22830}
22831
22832impl RowRangeExt for Range<MultiBufferRow> {
22833 type Row = MultiBufferRow;
22834
22835 fn len(&self) -> usize {
22836 (self.end.0 - self.start.0) as usize
22837 }
22838
22839 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
22840 (self.start.0..self.end.0).map(MultiBufferRow)
22841 }
22842}
22843
22844impl RowRangeExt for Range<DisplayRow> {
22845 type Row = DisplayRow;
22846
22847 fn len(&self) -> usize {
22848 (self.end.0 - self.start.0) as usize
22849 }
22850
22851 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
22852 (self.start.0..self.end.0).map(DisplayRow)
22853 }
22854}
22855
22856/// If select range has more than one line, we
22857/// just point the cursor to range.start.
22858fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
22859 if range.start.row == range.end.row {
22860 range
22861 } else {
22862 range.start..range.start
22863 }
22864}
22865pub struct KillRing(ClipboardItem);
22866impl Global for KillRing {}
22867
22868const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
22869
22870enum BreakpointPromptEditAction {
22871 Log,
22872 Condition,
22873 HitCondition,
22874}
22875
22876struct BreakpointPromptEditor {
22877 pub(crate) prompt: Entity<Editor>,
22878 editor: WeakEntity<Editor>,
22879 breakpoint_anchor: Anchor,
22880 breakpoint: Breakpoint,
22881 edit_action: BreakpointPromptEditAction,
22882 block_ids: HashSet<CustomBlockId>,
22883 editor_margins: Arc<Mutex<EditorMargins>>,
22884 _subscriptions: Vec<Subscription>,
22885}
22886
22887impl BreakpointPromptEditor {
22888 const MAX_LINES: u8 = 4;
22889
22890 fn new(
22891 editor: WeakEntity<Editor>,
22892 breakpoint_anchor: Anchor,
22893 breakpoint: Breakpoint,
22894 edit_action: BreakpointPromptEditAction,
22895 window: &mut Window,
22896 cx: &mut Context<Self>,
22897 ) -> Self {
22898 let base_text = match edit_action {
22899 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
22900 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
22901 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
22902 }
22903 .map(|msg| msg.to_string())
22904 .unwrap_or_default();
22905
22906 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
22907 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
22908
22909 let prompt = cx.new(|cx| {
22910 let mut prompt = Editor::new(
22911 EditorMode::AutoHeight {
22912 min_lines: 1,
22913 max_lines: Some(Self::MAX_LINES as usize),
22914 },
22915 buffer,
22916 None,
22917 window,
22918 cx,
22919 );
22920 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
22921 prompt.set_show_cursor_when_unfocused(false, cx);
22922 prompt.set_placeholder_text(
22923 match edit_action {
22924 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
22925 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
22926 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
22927 },
22928 cx,
22929 );
22930
22931 prompt
22932 });
22933
22934 Self {
22935 prompt,
22936 editor,
22937 breakpoint_anchor,
22938 breakpoint,
22939 edit_action,
22940 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
22941 block_ids: Default::default(),
22942 _subscriptions: vec![],
22943 }
22944 }
22945
22946 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
22947 self.block_ids.extend(block_ids)
22948 }
22949
22950 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
22951 if let Some(editor) = self.editor.upgrade() {
22952 let message = self
22953 .prompt
22954 .read(cx)
22955 .buffer
22956 .read(cx)
22957 .as_singleton()
22958 .expect("A multi buffer in breakpoint prompt isn't possible")
22959 .read(cx)
22960 .as_rope()
22961 .to_string();
22962
22963 editor.update(cx, |editor, cx| {
22964 editor.edit_breakpoint_at_anchor(
22965 self.breakpoint_anchor,
22966 self.breakpoint.clone(),
22967 match self.edit_action {
22968 BreakpointPromptEditAction::Log => {
22969 BreakpointEditAction::EditLogMessage(message.into())
22970 }
22971 BreakpointPromptEditAction::Condition => {
22972 BreakpointEditAction::EditCondition(message.into())
22973 }
22974 BreakpointPromptEditAction::HitCondition => {
22975 BreakpointEditAction::EditHitCondition(message.into())
22976 }
22977 },
22978 cx,
22979 );
22980
22981 editor.remove_blocks(self.block_ids.clone(), None, cx);
22982 cx.focus_self(window);
22983 });
22984 }
22985 }
22986
22987 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
22988 self.editor
22989 .update(cx, |editor, cx| {
22990 editor.remove_blocks(self.block_ids.clone(), None, cx);
22991 window.focus(&editor.focus_handle);
22992 })
22993 .log_err();
22994 }
22995
22996 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
22997 let settings = ThemeSettings::get_global(cx);
22998 let text_style = TextStyle {
22999 color: if self.prompt.read(cx).read_only(cx) {
23000 cx.theme().colors().text_disabled
23001 } else {
23002 cx.theme().colors().text
23003 },
23004 font_family: settings.buffer_font.family.clone(),
23005 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23006 font_size: settings.buffer_font_size(cx).into(),
23007 font_weight: settings.buffer_font.weight,
23008 line_height: relative(settings.buffer_line_height.value()),
23009 ..Default::default()
23010 };
23011 EditorElement::new(
23012 &self.prompt,
23013 EditorStyle {
23014 background: cx.theme().colors().editor_background,
23015 local_player: cx.theme().players().local(),
23016 text: text_style,
23017 ..Default::default()
23018 },
23019 )
23020 }
23021}
23022
23023impl Render for BreakpointPromptEditor {
23024 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23025 let editor_margins = *self.editor_margins.lock();
23026 let gutter_dimensions = editor_margins.gutter;
23027 h_flex()
23028 .key_context("Editor")
23029 .bg(cx.theme().colors().editor_background)
23030 .border_y_1()
23031 .border_color(cx.theme().status().info_border)
23032 .size_full()
23033 .py(window.line_height() / 2.5)
23034 .on_action(cx.listener(Self::confirm))
23035 .on_action(cx.listener(Self::cancel))
23036 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23037 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23038 }
23039}
23040
23041impl Focusable for BreakpointPromptEditor {
23042 fn focus_handle(&self, cx: &App) -> FocusHandle {
23043 self.prompt.focus_handle(cx)
23044 }
23045}
23046
23047fn all_edits_insertions_or_deletions(
23048 edits: &Vec<(Range<Anchor>, String)>,
23049 snapshot: &MultiBufferSnapshot,
23050) -> bool {
23051 let mut all_insertions = true;
23052 let mut all_deletions = true;
23053
23054 for (range, new_text) in edits.iter() {
23055 let range_is_empty = range.to_offset(&snapshot).is_empty();
23056 let text_is_empty = new_text.is_empty();
23057
23058 if range_is_empty != text_is_empty {
23059 if range_is_empty {
23060 all_deletions = false;
23061 } else {
23062 all_insertions = false;
23063 }
23064 } else {
23065 return false;
23066 }
23067
23068 if !all_insertions && !all_deletions {
23069 return false;
23070 }
23071 }
23072 all_insertions || all_deletions
23073}
23074
23075struct MissingEditPredictionKeybindingTooltip;
23076
23077impl Render for MissingEditPredictionKeybindingTooltip {
23078 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23079 ui::tooltip_container(window, cx, |container, _, cx| {
23080 container
23081 .flex_shrink_0()
23082 .max_w_80()
23083 .min_h(rems_from_px(124.))
23084 .justify_between()
23085 .child(
23086 v_flex()
23087 .flex_1()
23088 .text_ui_sm(cx)
23089 .child(Label::new("Conflict with Accept Keybinding"))
23090 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23091 )
23092 .child(
23093 h_flex()
23094 .pb_1()
23095 .gap_1()
23096 .items_end()
23097 .w_full()
23098 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23099 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23100 }))
23101 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23102 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23103 })),
23104 )
23105 })
23106 }
23107}
23108
23109#[derive(Debug, Clone, Copy, PartialEq)]
23110pub struct LineHighlight {
23111 pub background: Background,
23112 pub border: Option<gpui::Hsla>,
23113 pub include_gutter: bool,
23114 pub type_id: Option<TypeId>,
23115}
23116
23117struct LineManipulationResult {
23118 pub new_text: String,
23119 pub line_count_before: usize,
23120 pub line_count_after: usize,
23121}
23122
23123fn render_diff_hunk_controls(
23124 row: u32,
23125 status: &DiffHunkStatus,
23126 hunk_range: Range<Anchor>,
23127 is_created_file: bool,
23128 line_height: Pixels,
23129 editor: &Entity<Editor>,
23130 _window: &mut Window,
23131 cx: &mut App,
23132) -> AnyElement {
23133 h_flex()
23134 .h(line_height)
23135 .mr_1()
23136 .gap_1()
23137 .px_0p5()
23138 .pb_1()
23139 .border_x_1()
23140 .border_b_1()
23141 .border_color(cx.theme().colors().border_variant)
23142 .rounded_b_lg()
23143 .bg(cx.theme().colors().editor_background)
23144 .gap_1()
23145 .block_mouse_except_scroll()
23146 .shadow_md()
23147 .child(if status.has_secondary_hunk() {
23148 Button::new(("stage", row as u64), "Stage")
23149 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23150 .tooltip({
23151 let focus_handle = editor.focus_handle(cx);
23152 move |window, cx| {
23153 Tooltip::for_action_in(
23154 "Stage Hunk",
23155 &::git::ToggleStaged,
23156 &focus_handle,
23157 window,
23158 cx,
23159 )
23160 }
23161 })
23162 .on_click({
23163 let editor = editor.clone();
23164 move |_event, _window, cx| {
23165 editor.update(cx, |editor, cx| {
23166 editor.stage_or_unstage_diff_hunks(
23167 true,
23168 vec![hunk_range.start..hunk_range.start],
23169 cx,
23170 );
23171 });
23172 }
23173 })
23174 } else {
23175 Button::new(("unstage", row as u64), "Unstage")
23176 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23177 .tooltip({
23178 let focus_handle = editor.focus_handle(cx);
23179 move |window, cx| {
23180 Tooltip::for_action_in(
23181 "Unstage Hunk",
23182 &::git::ToggleStaged,
23183 &focus_handle,
23184 window,
23185 cx,
23186 )
23187 }
23188 })
23189 .on_click({
23190 let editor = editor.clone();
23191 move |_event, _window, cx| {
23192 editor.update(cx, |editor, cx| {
23193 editor.stage_or_unstage_diff_hunks(
23194 false,
23195 vec![hunk_range.start..hunk_range.start],
23196 cx,
23197 );
23198 });
23199 }
23200 })
23201 })
23202 .child(
23203 Button::new(("restore", row as u64), "Restore")
23204 .tooltip({
23205 let focus_handle = editor.focus_handle(cx);
23206 move |window, cx| {
23207 Tooltip::for_action_in(
23208 "Restore Hunk",
23209 &::git::Restore,
23210 &focus_handle,
23211 window,
23212 cx,
23213 )
23214 }
23215 })
23216 .on_click({
23217 let editor = editor.clone();
23218 move |_event, window, cx| {
23219 editor.update(cx, |editor, cx| {
23220 let snapshot = editor.snapshot(window, cx);
23221 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23222 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23223 });
23224 }
23225 })
23226 .disabled(is_created_file),
23227 )
23228 .when(
23229 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23230 |el| {
23231 el.child(
23232 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23233 .shape(IconButtonShape::Square)
23234 .icon_size(IconSize::Small)
23235 // .disabled(!has_multiple_hunks)
23236 .tooltip({
23237 let focus_handle = editor.focus_handle(cx);
23238 move |window, cx| {
23239 Tooltip::for_action_in(
23240 "Next Hunk",
23241 &GoToHunk,
23242 &focus_handle,
23243 window,
23244 cx,
23245 )
23246 }
23247 })
23248 .on_click({
23249 let editor = editor.clone();
23250 move |_event, window, cx| {
23251 editor.update(cx, |editor, cx| {
23252 let snapshot = editor.snapshot(window, cx);
23253 let position =
23254 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23255 editor.go_to_hunk_before_or_after_position(
23256 &snapshot,
23257 position,
23258 Direction::Next,
23259 window,
23260 cx,
23261 );
23262 editor.expand_selected_diff_hunks(cx);
23263 });
23264 }
23265 }),
23266 )
23267 .child(
23268 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23269 .shape(IconButtonShape::Square)
23270 .icon_size(IconSize::Small)
23271 // .disabled(!has_multiple_hunks)
23272 .tooltip({
23273 let focus_handle = editor.focus_handle(cx);
23274 move |window, cx| {
23275 Tooltip::for_action_in(
23276 "Previous Hunk",
23277 &GoToPreviousHunk,
23278 &focus_handle,
23279 window,
23280 cx,
23281 )
23282 }
23283 })
23284 .on_click({
23285 let editor = editor.clone();
23286 move |_event, window, cx| {
23287 editor.update(cx, |editor, cx| {
23288 let snapshot = editor.snapshot(window, cx);
23289 let point =
23290 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23291 editor.go_to_hunk_before_or_after_position(
23292 &snapshot,
23293 point,
23294 Direction::Prev,
23295 window,
23296 cx,
23297 );
23298 editor.expand_selected_diff_hunks(cx);
23299 });
23300 }
23301 }),
23302 )
23303 },
23304 )
23305 .into_any_element()
23306}