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 pub 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 HashSet::default(),
16179 cx,
16180 );
16181 });
16182 })
16183 }
16184 }
16185
16186 pub fn stop_language_server(
16187 &mut self,
16188 _: &StopLanguageServer,
16189 _: &mut Window,
16190 cx: &mut Context<Self>,
16191 ) {
16192 if let Some(project) = self.project.clone() {
16193 self.buffer.update(cx, |multi_buffer, cx| {
16194 project.update(cx, |project, cx| {
16195 project.stop_language_servers_for_buffers(
16196 multi_buffer.all_buffers().into_iter().collect(),
16197 HashSet::default(),
16198 cx,
16199 );
16200 cx.emit(project::Event::RefreshInlayHints);
16201 });
16202 });
16203 }
16204 }
16205
16206 fn cancel_language_server_work(
16207 workspace: &mut Workspace,
16208 _: &actions::CancelLanguageServerWork,
16209 _: &mut Window,
16210 cx: &mut Context<Workspace>,
16211 ) {
16212 let project = workspace.project();
16213 let buffers = workspace
16214 .active_item(cx)
16215 .and_then(|item| item.act_as::<Editor>(cx))
16216 .map_or(HashSet::default(), |editor| {
16217 editor.read(cx).buffer.read(cx).all_buffers()
16218 });
16219 project.update(cx, |project, cx| {
16220 project.cancel_language_server_work_for_buffers(buffers, cx);
16221 });
16222 }
16223
16224 fn show_character_palette(
16225 &mut self,
16226 _: &ShowCharacterPalette,
16227 window: &mut Window,
16228 _: &mut Context<Self>,
16229 ) {
16230 window.show_character_palette();
16231 }
16232
16233 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16234 if !self.diagnostics_enabled() {
16235 return;
16236 }
16237
16238 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16239 let buffer = self.buffer.read(cx).snapshot(cx);
16240 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16241 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16242 let is_valid = buffer
16243 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16244 .any(|entry| {
16245 entry.diagnostic.is_primary
16246 && !entry.range.is_empty()
16247 && entry.range.start == primary_range_start
16248 && entry.diagnostic.message == active_diagnostics.active_message
16249 });
16250
16251 if !is_valid {
16252 self.dismiss_diagnostics(cx);
16253 }
16254 }
16255 }
16256
16257 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16258 match &self.active_diagnostics {
16259 ActiveDiagnostic::Group(group) => Some(group),
16260 _ => None,
16261 }
16262 }
16263
16264 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16265 if !self.diagnostics_enabled() {
16266 return;
16267 }
16268 self.dismiss_diagnostics(cx);
16269 self.active_diagnostics = ActiveDiagnostic::All;
16270 }
16271
16272 fn activate_diagnostics(
16273 &mut self,
16274 buffer_id: BufferId,
16275 diagnostic: DiagnosticEntry<usize>,
16276 window: &mut Window,
16277 cx: &mut Context<Self>,
16278 ) {
16279 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16280 return;
16281 }
16282 self.dismiss_diagnostics(cx);
16283 let snapshot = self.snapshot(window, cx);
16284 let buffer = self.buffer.read(cx).snapshot(cx);
16285 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16286 return;
16287 };
16288
16289 let diagnostic_group = buffer
16290 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16291 .collect::<Vec<_>>();
16292
16293 let blocks =
16294 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16295
16296 let blocks = self.display_map.update(cx, |display_map, cx| {
16297 display_map.insert_blocks(blocks, cx).into_iter().collect()
16298 });
16299 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16300 active_range: buffer.anchor_before(diagnostic.range.start)
16301 ..buffer.anchor_after(diagnostic.range.end),
16302 active_message: diagnostic.diagnostic.message.clone(),
16303 group_id: diagnostic.diagnostic.group_id,
16304 blocks,
16305 });
16306 cx.notify();
16307 }
16308
16309 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16310 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16311 return;
16312 };
16313
16314 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16315 if let ActiveDiagnostic::Group(group) = prev {
16316 self.display_map.update(cx, |display_map, cx| {
16317 display_map.remove_blocks(group.blocks, cx);
16318 });
16319 cx.notify();
16320 }
16321 }
16322
16323 /// Disable inline diagnostics rendering for this editor.
16324 pub fn disable_inline_diagnostics(&mut self) {
16325 self.inline_diagnostics_enabled = false;
16326 self.inline_diagnostics_update = Task::ready(());
16327 self.inline_diagnostics.clear();
16328 }
16329
16330 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16331 self.diagnostics_enabled = false;
16332 self.dismiss_diagnostics(cx);
16333 self.inline_diagnostics_update = Task::ready(());
16334 self.inline_diagnostics.clear();
16335 }
16336
16337 pub fn diagnostics_enabled(&self) -> bool {
16338 self.diagnostics_enabled && self.mode.is_full()
16339 }
16340
16341 pub fn inline_diagnostics_enabled(&self) -> bool {
16342 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16343 }
16344
16345 pub fn show_inline_diagnostics(&self) -> bool {
16346 self.show_inline_diagnostics
16347 }
16348
16349 pub fn toggle_inline_diagnostics(
16350 &mut self,
16351 _: &ToggleInlineDiagnostics,
16352 window: &mut Window,
16353 cx: &mut Context<Editor>,
16354 ) {
16355 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16356 self.refresh_inline_diagnostics(false, window, cx);
16357 }
16358
16359 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16360 self.diagnostics_max_severity = severity;
16361 self.display_map.update(cx, |display_map, _| {
16362 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16363 });
16364 }
16365
16366 pub fn toggle_diagnostics(
16367 &mut self,
16368 _: &ToggleDiagnostics,
16369 window: &mut Window,
16370 cx: &mut Context<Editor>,
16371 ) {
16372 if !self.diagnostics_enabled() {
16373 return;
16374 }
16375
16376 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16377 EditorSettings::get_global(cx)
16378 .diagnostics_max_severity
16379 .filter(|severity| severity != &DiagnosticSeverity::Off)
16380 .unwrap_or(DiagnosticSeverity::Hint)
16381 } else {
16382 DiagnosticSeverity::Off
16383 };
16384 self.set_max_diagnostics_severity(new_severity, cx);
16385 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16386 self.active_diagnostics = ActiveDiagnostic::None;
16387 self.inline_diagnostics_update = Task::ready(());
16388 self.inline_diagnostics.clear();
16389 } else {
16390 self.refresh_inline_diagnostics(false, window, cx);
16391 }
16392
16393 cx.notify();
16394 }
16395
16396 pub fn toggle_minimap(
16397 &mut self,
16398 _: &ToggleMinimap,
16399 window: &mut Window,
16400 cx: &mut Context<Editor>,
16401 ) {
16402 if self.supports_minimap(cx) {
16403 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16404 }
16405 }
16406
16407 fn refresh_inline_diagnostics(
16408 &mut self,
16409 debounce: bool,
16410 window: &mut Window,
16411 cx: &mut Context<Self>,
16412 ) {
16413 let max_severity = ProjectSettings::get_global(cx)
16414 .diagnostics
16415 .inline
16416 .max_severity
16417 .unwrap_or(self.diagnostics_max_severity);
16418
16419 if !self.inline_diagnostics_enabled()
16420 || !self.show_inline_diagnostics
16421 || max_severity == DiagnosticSeverity::Off
16422 {
16423 self.inline_diagnostics_update = Task::ready(());
16424 self.inline_diagnostics.clear();
16425 return;
16426 }
16427
16428 let debounce_ms = ProjectSettings::get_global(cx)
16429 .diagnostics
16430 .inline
16431 .update_debounce_ms;
16432 let debounce = if debounce && debounce_ms > 0 {
16433 Some(Duration::from_millis(debounce_ms))
16434 } else {
16435 None
16436 };
16437 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16438 if let Some(debounce) = debounce {
16439 cx.background_executor().timer(debounce).await;
16440 }
16441 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16442 editor
16443 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16444 .ok()
16445 }) else {
16446 return;
16447 };
16448
16449 let new_inline_diagnostics = cx
16450 .background_spawn(async move {
16451 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16452 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16453 let message = diagnostic_entry
16454 .diagnostic
16455 .message
16456 .split_once('\n')
16457 .map(|(line, _)| line)
16458 .map(SharedString::new)
16459 .unwrap_or_else(|| {
16460 SharedString::from(diagnostic_entry.diagnostic.message)
16461 });
16462 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16463 let (Ok(i) | Err(i)) = inline_diagnostics
16464 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16465 inline_diagnostics.insert(
16466 i,
16467 (
16468 start_anchor,
16469 InlineDiagnostic {
16470 message,
16471 group_id: diagnostic_entry.diagnostic.group_id,
16472 start: diagnostic_entry.range.start.to_point(&snapshot),
16473 is_primary: diagnostic_entry.diagnostic.is_primary,
16474 severity: diagnostic_entry.diagnostic.severity,
16475 },
16476 ),
16477 );
16478 }
16479 inline_diagnostics
16480 })
16481 .await;
16482
16483 editor
16484 .update(cx, |editor, cx| {
16485 editor.inline_diagnostics = new_inline_diagnostics;
16486 cx.notify();
16487 })
16488 .ok();
16489 });
16490 }
16491
16492 fn pull_diagnostics(
16493 &mut self,
16494 buffer_id: Option<BufferId>,
16495 window: &Window,
16496 cx: &mut Context<Self>,
16497 ) -> Option<()> {
16498 if !self.mode().is_full() {
16499 return None;
16500 }
16501 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16502 .diagnostics
16503 .lsp_pull_diagnostics;
16504 if !pull_diagnostics_settings.enabled {
16505 return None;
16506 }
16507 let project = self.project.as_ref()?.downgrade();
16508 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16509 let mut buffers = self.buffer.read(cx).all_buffers();
16510 if let Some(buffer_id) = buffer_id {
16511 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16512 }
16513
16514 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16515 cx.background_executor().timer(debounce).await;
16516
16517 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16518 buffers
16519 .into_iter()
16520 .filter_map(|buffer| {
16521 project
16522 .update(cx, |project, cx| {
16523 project.lsp_store().update(cx, |lsp_store, cx| {
16524 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
16525 })
16526 })
16527 .ok()
16528 })
16529 .collect::<FuturesUnordered<_>>()
16530 }) else {
16531 return;
16532 };
16533
16534 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16535 match pull_task {
16536 Ok(()) => {
16537 if editor
16538 .update_in(cx, |editor, window, cx| {
16539 editor.update_diagnostics_state(window, cx);
16540 })
16541 .is_err()
16542 {
16543 return;
16544 }
16545 }
16546 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16547 }
16548 }
16549 });
16550
16551 Some(())
16552 }
16553
16554 pub fn set_selections_from_remote(
16555 &mut self,
16556 selections: Vec<Selection<Anchor>>,
16557 pending_selection: Option<Selection<Anchor>>,
16558 window: &mut Window,
16559 cx: &mut Context<Self>,
16560 ) {
16561 let old_cursor_position = self.selections.newest_anchor().head();
16562 self.selections.change_with(cx, |s| {
16563 s.select_anchors(selections);
16564 if let Some(pending_selection) = pending_selection {
16565 s.set_pending(pending_selection, SelectMode::Character);
16566 } else {
16567 s.clear_pending();
16568 }
16569 });
16570 self.selections_did_change(
16571 false,
16572 &old_cursor_position,
16573 SelectionEffects::default(),
16574 window,
16575 cx,
16576 );
16577 }
16578
16579 pub fn transact(
16580 &mut self,
16581 window: &mut Window,
16582 cx: &mut Context<Self>,
16583 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16584 ) -> Option<TransactionId> {
16585 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16586 this.start_transaction_at(Instant::now(), window, cx);
16587 update(this, window, cx);
16588 this.end_transaction_at(Instant::now(), cx)
16589 })
16590 }
16591
16592 pub fn start_transaction_at(
16593 &mut self,
16594 now: Instant,
16595 window: &mut Window,
16596 cx: &mut Context<Self>,
16597 ) {
16598 self.end_selection(window, cx);
16599 if let Some(tx_id) = self
16600 .buffer
16601 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16602 {
16603 self.selection_history
16604 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16605 cx.emit(EditorEvent::TransactionBegun {
16606 transaction_id: tx_id,
16607 })
16608 }
16609 }
16610
16611 pub fn end_transaction_at(
16612 &mut self,
16613 now: Instant,
16614 cx: &mut Context<Self>,
16615 ) -> Option<TransactionId> {
16616 if let Some(transaction_id) = self
16617 .buffer
16618 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16619 {
16620 if let Some((_, end_selections)) =
16621 self.selection_history.transaction_mut(transaction_id)
16622 {
16623 *end_selections = Some(self.selections.disjoint_anchors());
16624 } else {
16625 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16626 }
16627
16628 cx.emit(EditorEvent::Edited { transaction_id });
16629 Some(transaction_id)
16630 } else {
16631 None
16632 }
16633 }
16634
16635 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16636 if self.selection_mark_mode {
16637 self.change_selections(None, window, cx, |s| {
16638 s.move_with(|_, sel| {
16639 sel.collapse_to(sel.head(), SelectionGoal::None);
16640 });
16641 })
16642 }
16643 self.selection_mark_mode = true;
16644 cx.notify();
16645 }
16646
16647 pub fn swap_selection_ends(
16648 &mut self,
16649 _: &actions::SwapSelectionEnds,
16650 window: &mut Window,
16651 cx: &mut Context<Self>,
16652 ) {
16653 self.change_selections(None, window, cx, |s| {
16654 s.move_with(|_, sel| {
16655 if sel.start != sel.end {
16656 sel.reversed = !sel.reversed
16657 }
16658 });
16659 });
16660 self.request_autoscroll(Autoscroll::newest(), cx);
16661 cx.notify();
16662 }
16663
16664 pub fn toggle_fold(
16665 &mut self,
16666 _: &actions::ToggleFold,
16667 window: &mut Window,
16668 cx: &mut Context<Self>,
16669 ) {
16670 if self.is_singleton(cx) {
16671 let selection = self.selections.newest::<Point>(cx);
16672
16673 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16674 let range = if selection.is_empty() {
16675 let point = selection.head().to_display_point(&display_map);
16676 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16677 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16678 .to_point(&display_map);
16679 start..end
16680 } else {
16681 selection.range()
16682 };
16683 if display_map.folds_in_range(range).next().is_some() {
16684 self.unfold_lines(&Default::default(), window, cx)
16685 } else {
16686 self.fold(&Default::default(), window, cx)
16687 }
16688 } else {
16689 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16690 let buffer_ids: HashSet<_> = self
16691 .selections
16692 .disjoint_anchor_ranges()
16693 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16694 .collect();
16695
16696 let should_unfold = buffer_ids
16697 .iter()
16698 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16699
16700 for buffer_id in buffer_ids {
16701 if should_unfold {
16702 self.unfold_buffer(buffer_id, cx);
16703 } else {
16704 self.fold_buffer(buffer_id, cx);
16705 }
16706 }
16707 }
16708 }
16709
16710 pub fn toggle_fold_recursive(
16711 &mut self,
16712 _: &actions::ToggleFoldRecursive,
16713 window: &mut Window,
16714 cx: &mut Context<Self>,
16715 ) {
16716 let selection = self.selections.newest::<Point>(cx);
16717
16718 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16719 let range = if selection.is_empty() {
16720 let point = selection.head().to_display_point(&display_map);
16721 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16722 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16723 .to_point(&display_map);
16724 start..end
16725 } else {
16726 selection.range()
16727 };
16728 if display_map.folds_in_range(range).next().is_some() {
16729 self.unfold_recursive(&Default::default(), window, cx)
16730 } else {
16731 self.fold_recursive(&Default::default(), window, cx)
16732 }
16733 }
16734
16735 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16736 if self.is_singleton(cx) {
16737 let mut to_fold = Vec::new();
16738 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16739 let selections = self.selections.all_adjusted(cx);
16740
16741 for selection in selections {
16742 let range = selection.range().sorted();
16743 let buffer_start_row = range.start.row;
16744
16745 if range.start.row != range.end.row {
16746 let mut found = false;
16747 let mut row = range.start.row;
16748 while row <= range.end.row {
16749 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16750 {
16751 found = true;
16752 row = crease.range().end.row + 1;
16753 to_fold.push(crease);
16754 } else {
16755 row += 1
16756 }
16757 }
16758 if found {
16759 continue;
16760 }
16761 }
16762
16763 for row in (0..=range.start.row).rev() {
16764 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16765 if crease.range().end.row >= buffer_start_row {
16766 to_fold.push(crease);
16767 if row <= range.start.row {
16768 break;
16769 }
16770 }
16771 }
16772 }
16773 }
16774
16775 self.fold_creases(to_fold, true, window, cx);
16776 } else {
16777 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16778 let buffer_ids = self
16779 .selections
16780 .disjoint_anchor_ranges()
16781 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16782 .collect::<HashSet<_>>();
16783 for buffer_id in buffer_ids {
16784 self.fold_buffer(buffer_id, cx);
16785 }
16786 }
16787 }
16788
16789 fn fold_at_level(
16790 &mut self,
16791 fold_at: &FoldAtLevel,
16792 window: &mut Window,
16793 cx: &mut Context<Self>,
16794 ) {
16795 if !self.buffer.read(cx).is_singleton() {
16796 return;
16797 }
16798
16799 let fold_at_level = fold_at.0;
16800 let snapshot = self.buffer.read(cx).snapshot(cx);
16801 let mut to_fold = Vec::new();
16802 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16803
16804 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16805 while start_row < end_row {
16806 match self
16807 .snapshot(window, cx)
16808 .crease_for_buffer_row(MultiBufferRow(start_row))
16809 {
16810 Some(crease) => {
16811 let nested_start_row = crease.range().start.row + 1;
16812 let nested_end_row = crease.range().end.row;
16813
16814 if current_level < fold_at_level {
16815 stack.push((nested_start_row, nested_end_row, current_level + 1));
16816 } else if current_level == fold_at_level {
16817 to_fold.push(crease);
16818 }
16819
16820 start_row = nested_end_row + 1;
16821 }
16822 None => start_row += 1,
16823 }
16824 }
16825 }
16826
16827 self.fold_creases(to_fold, true, window, cx);
16828 }
16829
16830 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16831 if self.buffer.read(cx).is_singleton() {
16832 let mut fold_ranges = Vec::new();
16833 let snapshot = self.buffer.read(cx).snapshot(cx);
16834
16835 for row in 0..snapshot.max_row().0 {
16836 if let Some(foldable_range) = self
16837 .snapshot(window, cx)
16838 .crease_for_buffer_row(MultiBufferRow(row))
16839 {
16840 fold_ranges.push(foldable_range);
16841 }
16842 }
16843
16844 self.fold_creases(fold_ranges, true, window, cx);
16845 } else {
16846 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16847 editor
16848 .update_in(cx, |editor, _, cx| {
16849 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16850 editor.fold_buffer(buffer_id, cx);
16851 }
16852 })
16853 .ok();
16854 });
16855 }
16856 }
16857
16858 pub fn fold_function_bodies(
16859 &mut self,
16860 _: &actions::FoldFunctionBodies,
16861 window: &mut Window,
16862 cx: &mut Context<Self>,
16863 ) {
16864 let snapshot = self.buffer.read(cx).snapshot(cx);
16865
16866 let ranges = snapshot
16867 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16868 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16869 .collect::<Vec<_>>();
16870
16871 let creases = ranges
16872 .into_iter()
16873 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16874 .collect();
16875
16876 self.fold_creases(creases, true, window, cx);
16877 }
16878
16879 pub fn fold_recursive(
16880 &mut self,
16881 _: &actions::FoldRecursive,
16882 window: &mut Window,
16883 cx: &mut Context<Self>,
16884 ) {
16885 let mut to_fold = Vec::new();
16886 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16887 let selections = self.selections.all_adjusted(cx);
16888
16889 for selection in selections {
16890 let range = selection.range().sorted();
16891 let buffer_start_row = range.start.row;
16892
16893 if range.start.row != range.end.row {
16894 let mut found = false;
16895 for row in range.start.row..=range.end.row {
16896 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16897 found = true;
16898 to_fold.push(crease);
16899 }
16900 }
16901 if found {
16902 continue;
16903 }
16904 }
16905
16906 for row in (0..=range.start.row).rev() {
16907 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16908 if crease.range().end.row >= buffer_start_row {
16909 to_fold.push(crease);
16910 } else {
16911 break;
16912 }
16913 }
16914 }
16915 }
16916
16917 self.fold_creases(to_fold, true, window, cx);
16918 }
16919
16920 pub fn fold_at(
16921 &mut self,
16922 buffer_row: MultiBufferRow,
16923 window: &mut Window,
16924 cx: &mut Context<Self>,
16925 ) {
16926 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16927
16928 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16929 let autoscroll = self
16930 .selections
16931 .all::<Point>(cx)
16932 .iter()
16933 .any(|selection| crease.range().overlaps(&selection.range()));
16934
16935 self.fold_creases(vec![crease], autoscroll, window, cx);
16936 }
16937 }
16938
16939 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16940 if self.is_singleton(cx) {
16941 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16942 let buffer = &display_map.buffer_snapshot;
16943 let selections = self.selections.all::<Point>(cx);
16944 let ranges = selections
16945 .iter()
16946 .map(|s| {
16947 let range = s.display_range(&display_map).sorted();
16948 let mut start = range.start.to_point(&display_map);
16949 let mut end = range.end.to_point(&display_map);
16950 start.column = 0;
16951 end.column = buffer.line_len(MultiBufferRow(end.row));
16952 start..end
16953 })
16954 .collect::<Vec<_>>();
16955
16956 self.unfold_ranges(&ranges, true, true, cx);
16957 } else {
16958 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16959 let buffer_ids = self
16960 .selections
16961 .disjoint_anchor_ranges()
16962 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16963 .collect::<HashSet<_>>();
16964 for buffer_id in buffer_ids {
16965 self.unfold_buffer(buffer_id, cx);
16966 }
16967 }
16968 }
16969
16970 pub fn unfold_recursive(
16971 &mut self,
16972 _: &UnfoldRecursive,
16973 _window: &mut Window,
16974 cx: &mut Context<Self>,
16975 ) {
16976 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16977 let selections = self.selections.all::<Point>(cx);
16978 let ranges = selections
16979 .iter()
16980 .map(|s| {
16981 let mut range = s.display_range(&display_map).sorted();
16982 *range.start.column_mut() = 0;
16983 *range.end.column_mut() = display_map.line_len(range.end.row());
16984 let start = range.start.to_point(&display_map);
16985 let end = range.end.to_point(&display_map);
16986 start..end
16987 })
16988 .collect::<Vec<_>>();
16989
16990 self.unfold_ranges(&ranges, true, true, cx);
16991 }
16992
16993 pub fn unfold_at(
16994 &mut self,
16995 buffer_row: MultiBufferRow,
16996 _window: &mut Window,
16997 cx: &mut Context<Self>,
16998 ) {
16999 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17000
17001 let intersection_range = Point::new(buffer_row.0, 0)
17002 ..Point::new(
17003 buffer_row.0,
17004 display_map.buffer_snapshot.line_len(buffer_row),
17005 );
17006
17007 let autoscroll = self
17008 .selections
17009 .all::<Point>(cx)
17010 .iter()
17011 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17012
17013 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17014 }
17015
17016 pub fn unfold_all(
17017 &mut self,
17018 _: &actions::UnfoldAll,
17019 _window: &mut Window,
17020 cx: &mut Context<Self>,
17021 ) {
17022 if self.buffer.read(cx).is_singleton() {
17023 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17024 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17025 } else {
17026 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17027 editor
17028 .update(cx, |editor, cx| {
17029 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17030 editor.unfold_buffer(buffer_id, cx);
17031 }
17032 })
17033 .ok();
17034 });
17035 }
17036 }
17037
17038 pub fn fold_selected_ranges(
17039 &mut self,
17040 _: &FoldSelectedRanges,
17041 window: &mut Window,
17042 cx: &mut Context<Self>,
17043 ) {
17044 let selections = self.selections.all_adjusted(cx);
17045 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17046 let ranges = selections
17047 .into_iter()
17048 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17049 .collect::<Vec<_>>();
17050 self.fold_creases(ranges, true, window, cx);
17051 }
17052
17053 pub fn fold_ranges<T: ToOffset + Clone>(
17054 &mut self,
17055 ranges: Vec<Range<T>>,
17056 auto_scroll: bool,
17057 window: &mut Window,
17058 cx: &mut Context<Self>,
17059 ) {
17060 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17061 let ranges = ranges
17062 .into_iter()
17063 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17064 .collect::<Vec<_>>();
17065 self.fold_creases(ranges, auto_scroll, window, cx);
17066 }
17067
17068 pub fn fold_creases<T: ToOffset + Clone>(
17069 &mut self,
17070 creases: Vec<Crease<T>>,
17071 auto_scroll: bool,
17072 _window: &mut Window,
17073 cx: &mut Context<Self>,
17074 ) {
17075 if creases.is_empty() {
17076 return;
17077 }
17078
17079 let mut buffers_affected = HashSet::default();
17080 let multi_buffer = self.buffer().read(cx);
17081 for crease in &creases {
17082 if let Some((_, buffer, _)) =
17083 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
17084 {
17085 buffers_affected.insert(buffer.read(cx).remote_id());
17086 };
17087 }
17088
17089 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17090
17091 if auto_scroll {
17092 self.request_autoscroll(Autoscroll::fit(), cx);
17093 }
17094
17095 cx.notify();
17096
17097 self.scrollbar_marker_state.dirty = true;
17098 self.folds_did_change(cx);
17099 }
17100
17101 /// Removes any folds whose ranges intersect any of the given ranges.
17102 pub fn unfold_ranges<T: ToOffset + Clone>(
17103 &mut self,
17104 ranges: &[Range<T>],
17105 inclusive: bool,
17106 auto_scroll: bool,
17107 cx: &mut Context<Self>,
17108 ) {
17109 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17110 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17111 });
17112 self.folds_did_change(cx);
17113 }
17114
17115 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17116 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17117 return;
17118 }
17119 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17120 self.display_map.update(cx, |display_map, cx| {
17121 display_map.fold_buffers([buffer_id], cx)
17122 });
17123 cx.emit(EditorEvent::BufferFoldToggled {
17124 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17125 folded: true,
17126 });
17127 cx.notify();
17128 }
17129
17130 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17131 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17132 return;
17133 }
17134 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17135 self.display_map.update(cx, |display_map, cx| {
17136 display_map.unfold_buffers([buffer_id], cx);
17137 });
17138 cx.emit(EditorEvent::BufferFoldToggled {
17139 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17140 folded: false,
17141 });
17142 cx.notify();
17143 }
17144
17145 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17146 self.display_map.read(cx).is_buffer_folded(buffer)
17147 }
17148
17149 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17150 self.display_map.read(cx).folded_buffers()
17151 }
17152
17153 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17154 self.display_map.update(cx, |display_map, cx| {
17155 display_map.disable_header_for_buffer(buffer_id, cx);
17156 });
17157 cx.notify();
17158 }
17159
17160 /// Removes any folds with the given ranges.
17161 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17162 &mut self,
17163 ranges: &[Range<T>],
17164 type_id: TypeId,
17165 auto_scroll: bool,
17166 cx: &mut Context<Self>,
17167 ) {
17168 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17169 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17170 });
17171 self.folds_did_change(cx);
17172 }
17173
17174 fn remove_folds_with<T: ToOffset + Clone>(
17175 &mut self,
17176 ranges: &[Range<T>],
17177 auto_scroll: bool,
17178 cx: &mut Context<Self>,
17179 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17180 ) {
17181 if ranges.is_empty() {
17182 return;
17183 }
17184
17185 let mut buffers_affected = HashSet::default();
17186 let multi_buffer = self.buffer().read(cx);
17187 for range in ranges {
17188 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17189 buffers_affected.insert(buffer.read(cx).remote_id());
17190 };
17191 }
17192
17193 self.display_map.update(cx, update);
17194
17195 if auto_scroll {
17196 self.request_autoscroll(Autoscroll::fit(), cx);
17197 }
17198
17199 cx.notify();
17200 self.scrollbar_marker_state.dirty = true;
17201 self.active_indent_guides_state.dirty = true;
17202 }
17203
17204 pub fn update_fold_widths(
17205 &mut self,
17206 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
17207 cx: &mut Context<Self>,
17208 ) -> bool {
17209 self.display_map
17210 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17211 }
17212
17213 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17214 self.display_map.read(cx).fold_placeholder.clone()
17215 }
17216
17217 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17218 self.buffer.update(cx, |buffer, cx| {
17219 buffer.set_all_diff_hunks_expanded(cx);
17220 });
17221 }
17222
17223 pub fn expand_all_diff_hunks(
17224 &mut self,
17225 _: &ExpandAllDiffHunks,
17226 _window: &mut Window,
17227 cx: &mut Context<Self>,
17228 ) {
17229 self.buffer.update(cx, |buffer, cx| {
17230 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17231 });
17232 }
17233
17234 pub fn toggle_selected_diff_hunks(
17235 &mut self,
17236 _: &ToggleSelectedDiffHunks,
17237 _window: &mut Window,
17238 cx: &mut Context<Self>,
17239 ) {
17240 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17241 self.toggle_diff_hunks_in_ranges(ranges, cx);
17242 }
17243
17244 pub fn diff_hunks_in_ranges<'a>(
17245 &'a self,
17246 ranges: &'a [Range<Anchor>],
17247 buffer: &'a MultiBufferSnapshot,
17248 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17249 ranges.iter().flat_map(move |range| {
17250 let end_excerpt_id = range.end.excerpt_id;
17251 let range = range.to_point(buffer);
17252 let mut peek_end = range.end;
17253 if range.end.row < buffer.max_row().0 {
17254 peek_end = Point::new(range.end.row + 1, 0);
17255 }
17256 buffer
17257 .diff_hunks_in_range(range.start..peek_end)
17258 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17259 })
17260 }
17261
17262 pub fn has_stageable_diff_hunks_in_ranges(
17263 &self,
17264 ranges: &[Range<Anchor>],
17265 snapshot: &MultiBufferSnapshot,
17266 ) -> bool {
17267 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17268 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17269 }
17270
17271 pub fn toggle_staged_selected_diff_hunks(
17272 &mut self,
17273 _: &::git::ToggleStaged,
17274 _: &mut Window,
17275 cx: &mut Context<Self>,
17276 ) {
17277 let snapshot = self.buffer.read(cx).snapshot(cx);
17278 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17279 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17280 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17281 }
17282
17283 pub fn set_render_diff_hunk_controls(
17284 &mut self,
17285 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17286 cx: &mut Context<Self>,
17287 ) {
17288 self.render_diff_hunk_controls = render_diff_hunk_controls;
17289 cx.notify();
17290 }
17291
17292 pub fn stage_and_next(
17293 &mut self,
17294 _: &::git::StageAndNext,
17295 window: &mut Window,
17296 cx: &mut Context<Self>,
17297 ) {
17298 self.do_stage_or_unstage_and_next(true, window, cx);
17299 }
17300
17301 pub fn unstage_and_next(
17302 &mut self,
17303 _: &::git::UnstageAndNext,
17304 window: &mut Window,
17305 cx: &mut Context<Self>,
17306 ) {
17307 self.do_stage_or_unstage_and_next(false, window, cx);
17308 }
17309
17310 pub fn stage_or_unstage_diff_hunks(
17311 &mut self,
17312 stage: bool,
17313 ranges: Vec<Range<Anchor>>,
17314 cx: &mut Context<Self>,
17315 ) {
17316 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17317 cx.spawn(async move |this, cx| {
17318 task.await?;
17319 this.update(cx, |this, cx| {
17320 let snapshot = this.buffer.read(cx).snapshot(cx);
17321 let chunk_by = this
17322 .diff_hunks_in_ranges(&ranges, &snapshot)
17323 .chunk_by(|hunk| hunk.buffer_id);
17324 for (buffer_id, hunks) in &chunk_by {
17325 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17326 }
17327 })
17328 })
17329 .detach_and_log_err(cx);
17330 }
17331
17332 fn save_buffers_for_ranges_if_needed(
17333 &mut self,
17334 ranges: &[Range<Anchor>],
17335 cx: &mut Context<Editor>,
17336 ) -> Task<Result<()>> {
17337 let multibuffer = self.buffer.read(cx);
17338 let snapshot = multibuffer.read(cx);
17339 let buffer_ids: HashSet<_> = ranges
17340 .iter()
17341 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17342 .collect();
17343 drop(snapshot);
17344
17345 let mut buffers = HashSet::default();
17346 for buffer_id in buffer_ids {
17347 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17348 let buffer = buffer_entity.read(cx);
17349 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17350 {
17351 buffers.insert(buffer_entity);
17352 }
17353 }
17354 }
17355
17356 if let Some(project) = &self.project {
17357 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17358 } else {
17359 Task::ready(Ok(()))
17360 }
17361 }
17362
17363 fn do_stage_or_unstage_and_next(
17364 &mut self,
17365 stage: bool,
17366 window: &mut Window,
17367 cx: &mut Context<Self>,
17368 ) {
17369 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17370
17371 if ranges.iter().any(|range| range.start != range.end) {
17372 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17373 return;
17374 }
17375
17376 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17377 let snapshot = self.snapshot(window, cx);
17378 let position = self.selections.newest::<Point>(cx).head();
17379 let mut row = snapshot
17380 .buffer_snapshot
17381 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17382 .find(|hunk| hunk.row_range.start.0 > position.row)
17383 .map(|hunk| hunk.row_range.start);
17384
17385 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17386 // Outside of the project diff editor, wrap around to the beginning.
17387 if !all_diff_hunks_expanded {
17388 row = row.or_else(|| {
17389 snapshot
17390 .buffer_snapshot
17391 .diff_hunks_in_range(Point::zero()..position)
17392 .find(|hunk| hunk.row_range.end.0 < position.row)
17393 .map(|hunk| hunk.row_range.start)
17394 });
17395 }
17396
17397 if let Some(row) = row {
17398 let destination = Point::new(row.0, 0);
17399 let autoscroll = Autoscroll::center();
17400
17401 self.unfold_ranges(&[destination..destination], false, false, cx);
17402 self.change_selections(Some(autoscroll), window, cx, |s| {
17403 s.select_ranges([destination..destination]);
17404 });
17405 }
17406 }
17407
17408 fn do_stage_or_unstage(
17409 &self,
17410 stage: bool,
17411 buffer_id: BufferId,
17412 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17413 cx: &mut App,
17414 ) -> Option<()> {
17415 let project = self.project.as_ref()?;
17416 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17417 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17418 let buffer_snapshot = buffer.read(cx).snapshot();
17419 let file_exists = buffer_snapshot
17420 .file()
17421 .is_some_and(|file| file.disk_state().exists());
17422 diff.update(cx, |diff, cx| {
17423 diff.stage_or_unstage_hunks(
17424 stage,
17425 &hunks
17426 .map(|hunk| buffer_diff::DiffHunk {
17427 buffer_range: hunk.buffer_range,
17428 diff_base_byte_range: hunk.diff_base_byte_range,
17429 secondary_status: hunk.secondary_status,
17430 range: Point::zero()..Point::zero(), // unused
17431 })
17432 .collect::<Vec<_>>(),
17433 &buffer_snapshot,
17434 file_exists,
17435 cx,
17436 )
17437 });
17438 None
17439 }
17440
17441 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17442 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17443 self.buffer
17444 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17445 }
17446
17447 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17448 self.buffer.update(cx, |buffer, cx| {
17449 let ranges = vec![Anchor::min()..Anchor::max()];
17450 if !buffer.all_diff_hunks_expanded()
17451 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17452 {
17453 buffer.collapse_diff_hunks(ranges, cx);
17454 true
17455 } else {
17456 false
17457 }
17458 })
17459 }
17460
17461 fn toggle_diff_hunks_in_ranges(
17462 &mut self,
17463 ranges: Vec<Range<Anchor>>,
17464 cx: &mut Context<Editor>,
17465 ) {
17466 self.buffer.update(cx, |buffer, cx| {
17467 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17468 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17469 })
17470 }
17471
17472 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17473 self.buffer.update(cx, |buffer, cx| {
17474 let snapshot = buffer.snapshot(cx);
17475 let excerpt_id = range.end.excerpt_id;
17476 let point_range = range.to_point(&snapshot);
17477 let expand = !buffer.single_hunk_is_expanded(range, cx);
17478 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17479 })
17480 }
17481
17482 pub(crate) fn apply_all_diff_hunks(
17483 &mut self,
17484 _: &ApplyAllDiffHunks,
17485 window: &mut Window,
17486 cx: &mut Context<Self>,
17487 ) {
17488 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17489
17490 let buffers = self.buffer.read(cx).all_buffers();
17491 for branch_buffer in buffers {
17492 branch_buffer.update(cx, |branch_buffer, cx| {
17493 branch_buffer.merge_into_base(Vec::new(), cx);
17494 });
17495 }
17496
17497 if let Some(project) = self.project.clone() {
17498 self.save(
17499 SaveOptions {
17500 format: true,
17501 autosave: false,
17502 },
17503 project,
17504 window,
17505 cx,
17506 )
17507 .detach_and_log_err(cx);
17508 }
17509 }
17510
17511 pub(crate) fn apply_selected_diff_hunks(
17512 &mut self,
17513 _: &ApplyDiffHunk,
17514 window: &mut Window,
17515 cx: &mut Context<Self>,
17516 ) {
17517 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17518 let snapshot = self.snapshot(window, cx);
17519 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17520 let mut ranges_by_buffer = HashMap::default();
17521 self.transact(window, cx, |editor, _window, cx| {
17522 for hunk in hunks {
17523 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17524 ranges_by_buffer
17525 .entry(buffer.clone())
17526 .or_insert_with(Vec::new)
17527 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17528 }
17529 }
17530
17531 for (buffer, ranges) in ranges_by_buffer {
17532 buffer.update(cx, |buffer, cx| {
17533 buffer.merge_into_base(ranges, cx);
17534 });
17535 }
17536 });
17537
17538 if let Some(project) = self.project.clone() {
17539 self.save(
17540 SaveOptions {
17541 format: true,
17542 autosave: false,
17543 },
17544 project,
17545 window,
17546 cx,
17547 )
17548 .detach_and_log_err(cx);
17549 }
17550 }
17551
17552 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17553 if hovered != self.gutter_hovered {
17554 self.gutter_hovered = hovered;
17555 cx.notify();
17556 }
17557 }
17558
17559 pub fn insert_blocks(
17560 &mut self,
17561 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17562 autoscroll: Option<Autoscroll>,
17563 cx: &mut Context<Self>,
17564 ) -> Vec<CustomBlockId> {
17565 let blocks = self
17566 .display_map
17567 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17568 if let Some(autoscroll) = autoscroll {
17569 self.request_autoscroll(autoscroll, cx);
17570 }
17571 cx.notify();
17572 blocks
17573 }
17574
17575 pub fn resize_blocks(
17576 &mut self,
17577 heights: HashMap<CustomBlockId, u32>,
17578 autoscroll: Option<Autoscroll>,
17579 cx: &mut Context<Self>,
17580 ) {
17581 self.display_map
17582 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17583 if let Some(autoscroll) = autoscroll {
17584 self.request_autoscroll(autoscroll, cx);
17585 }
17586 cx.notify();
17587 }
17588
17589 pub fn replace_blocks(
17590 &mut self,
17591 renderers: HashMap<CustomBlockId, RenderBlock>,
17592 autoscroll: Option<Autoscroll>,
17593 cx: &mut Context<Self>,
17594 ) {
17595 self.display_map
17596 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17597 if let Some(autoscroll) = autoscroll {
17598 self.request_autoscroll(autoscroll, cx);
17599 }
17600 cx.notify();
17601 }
17602
17603 pub fn remove_blocks(
17604 &mut self,
17605 block_ids: HashSet<CustomBlockId>,
17606 autoscroll: Option<Autoscroll>,
17607 cx: &mut Context<Self>,
17608 ) {
17609 self.display_map.update(cx, |display_map, cx| {
17610 display_map.remove_blocks(block_ids, cx)
17611 });
17612 if let Some(autoscroll) = autoscroll {
17613 self.request_autoscroll(autoscroll, cx);
17614 }
17615 cx.notify();
17616 }
17617
17618 pub fn row_for_block(
17619 &self,
17620 block_id: CustomBlockId,
17621 cx: &mut Context<Self>,
17622 ) -> Option<DisplayRow> {
17623 self.display_map
17624 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17625 }
17626
17627 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17628 self.focused_block = Some(focused_block);
17629 }
17630
17631 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17632 self.focused_block.take()
17633 }
17634
17635 pub fn insert_creases(
17636 &mut self,
17637 creases: impl IntoIterator<Item = Crease<Anchor>>,
17638 cx: &mut Context<Self>,
17639 ) -> Vec<CreaseId> {
17640 self.display_map
17641 .update(cx, |map, cx| map.insert_creases(creases, cx))
17642 }
17643
17644 pub fn remove_creases(
17645 &mut self,
17646 ids: impl IntoIterator<Item = CreaseId>,
17647 cx: &mut Context<Self>,
17648 ) -> Vec<(CreaseId, Range<Anchor>)> {
17649 self.display_map
17650 .update(cx, |map, cx| map.remove_creases(ids, cx))
17651 }
17652
17653 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17654 self.display_map
17655 .update(cx, |map, cx| map.snapshot(cx))
17656 .longest_row()
17657 }
17658
17659 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17660 self.display_map
17661 .update(cx, |map, cx| map.snapshot(cx))
17662 .max_point()
17663 }
17664
17665 pub fn text(&self, cx: &App) -> String {
17666 self.buffer.read(cx).read(cx).text()
17667 }
17668
17669 pub fn is_empty(&self, cx: &App) -> bool {
17670 self.buffer.read(cx).read(cx).is_empty()
17671 }
17672
17673 pub fn text_option(&self, cx: &App) -> Option<String> {
17674 let text = self.text(cx);
17675 let text = text.trim();
17676
17677 if text.is_empty() {
17678 return None;
17679 }
17680
17681 Some(text.to_string())
17682 }
17683
17684 pub fn set_text(
17685 &mut self,
17686 text: impl Into<Arc<str>>,
17687 window: &mut Window,
17688 cx: &mut Context<Self>,
17689 ) {
17690 self.transact(window, cx, |this, _, cx| {
17691 this.buffer
17692 .read(cx)
17693 .as_singleton()
17694 .expect("you can only call set_text on editors for singleton buffers")
17695 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17696 });
17697 }
17698
17699 pub fn display_text(&self, cx: &mut App) -> String {
17700 self.display_map
17701 .update(cx, |map, cx| map.snapshot(cx))
17702 .text()
17703 }
17704
17705 fn create_minimap(
17706 &self,
17707 minimap_settings: MinimapSettings,
17708 window: &mut Window,
17709 cx: &mut Context<Self>,
17710 ) -> Option<Entity<Self>> {
17711 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17712 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17713 }
17714
17715 fn initialize_new_minimap(
17716 &self,
17717 minimap_settings: MinimapSettings,
17718 window: &mut Window,
17719 cx: &mut Context<Self>,
17720 ) -> Entity<Self> {
17721 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17722
17723 let mut minimap = Editor::new_internal(
17724 EditorMode::Minimap {
17725 parent: cx.weak_entity(),
17726 },
17727 self.buffer.clone(),
17728 self.project.clone(),
17729 Some(self.display_map.clone()),
17730 window,
17731 cx,
17732 );
17733 minimap.scroll_manager.clone_state(&self.scroll_manager);
17734 minimap.set_text_style_refinement(TextStyleRefinement {
17735 font_size: Some(MINIMAP_FONT_SIZE),
17736 font_weight: Some(MINIMAP_FONT_WEIGHT),
17737 ..Default::default()
17738 });
17739 minimap.update_minimap_configuration(minimap_settings, cx);
17740 cx.new(|_| minimap)
17741 }
17742
17743 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17744 let current_line_highlight = minimap_settings
17745 .current_line_highlight
17746 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17747 self.set_current_line_highlight(Some(current_line_highlight));
17748 }
17749
17750 pub fn minimap(&self) -> Option<&Entity<Self>> {
17751 self.minimap
17752 .as_ref()
17753 .filter(|_| self.minimap_visibility.visible())
17754 }
17755
17756 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17757 let mut wrap_guides = smallvec![];
17758
17759 if self.show_wrap_guides == Some(false) {
17760 return wrap_guides;
17761 }
17762
17763 let settings = self.buffer.read(cx).language_settings(cx);
17764 if settings.show_wrap_guides {
17765 match self.soft_wrap_mode(cx) {
17766 SoftWrap::Column(soft_wrap) => {
17767 wrap_guides.push((soft_wrap as usize, true));
17768 }
17769 SoftWrap::Bounded(soft_wrap) => {
17770 wrap_guides.push((soft_wrap as usize, true));
17771 }
17772 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17773 }
17774 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17775 }
17776
17777 wrap_guides
17778 }
17779
17780 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17781 let settings = self.buffer.read(cx).language_settings(cx);
17782 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17783 match mode {
17784 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17785 SoftWrap::None
17786 }
17787 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17788 language_settings::SoftWrap::PreferredLineLength => {
17789 SoftWrap::Column(settings.preferred_line_length)
17790 }
17791 language_settings::SoftWrap::Bounded => {
17792 SoftWrap::Bounded(settings.preferred_line_length)
17793 }
17794 }
17795 }
17796
17797 pub fn set_soft_wrap_mode(
17798 &mut self,
17799 mode: language_settings::SoftWrap,
17800
17801 cx: &mut Context<Self>,
17802 ) {
17803 self.soft_wrap_mode_override = Some(mode);
17804 cx.notify();
17805 }
17806
17807 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17808 self.hard_wrap = hard_wrap;
17809 cx.notify();
17810 }
17811
17812 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17813 self.text_style_refinement = Some(style);
17814 }
17815
17816 /// called by the Element so we know what style we were most recently rendered with.
17817 pub(crate) fn set_style(
17818 &mut self,
17819 style: EditorStyle,
17820 window: &mut Window,
17821 cx: &mut Context<Self>,
17822 ) {
17823 // We intentionally do not inform the display map about the minimap style
17824 // so that wrapping is not recalculated and stays consistent for the editor
17825 // and its linked minimap.
17826 if !self.mode.is_minimap() {
17827 let rem_size = window.rem_size();
17828 self.display_map.update(cx, |map, cx| {
17829 map.set_font(
17830 style.text.font(),
17831 style.text.font_size.to_pixels(rem_size),
17832 cx,
17833 )
17834 });
17835 }
17836 self.style = Some(style);
17837 }
17838
17839 pub fn style(&self) -> Option<&EditorStyle> {
17840 self.style.as_ref()
17841 }
17842
17843 // Called by the element. This method is not designed to be called outside of the editor
17844 // element's layout code because it does not notify when rewrapping is computed synchronously.
17845 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17846 self.display_map
17847 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17848 }
17849
17850 pub fn set_soft_wrap(&mut self) {
17851 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17852 }
17853
17854 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17855 if self.soft_wrap_mode_override.is_some() {
17856 self.soft_wrap_mode_override.take();
17857 } else {
17858 let soft_wrap = match self.soft_wrap_mode(cx) {
17859 SoftWrap::GitDiff => return,
17860 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17861 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17862 language_settings::SoftWrap::None
17863 }
17864 };
17865 self.soft_wrap_mode_override = Some(soft_wrap);
17866 }
17867 cx.notify();
17868 }
17869
17870 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17871 let Some(workspace) = self.workspace() else {
17872 return;
17873 };
17874 let fs = workspace.read(cx).app_state().fs.clone();
17875 let current_show = TabBarSettings::get_global(cx).show;
17876 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17877 setting.show = Some(!current_show);
17878 });
17879 }
17880
17881 pub fn toggle_indent_guides(
17882 &mut self,
17883 _: &ToggleIndentGuides,
17884 _: &mut Window,
17885 cx: &mut Context<Self>,
17886 ) {
17887 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17888 self.buffer
17889 .read(cx)
17890 .language_settings(cx)
17891 .indent_guides
17892 .enabled
17893 });
17894 self.show_indent_guides = Some(!currently_enabled);
17895 cx.notify();
17896 }
17897
17898 fn should_show_indent_guides(&self) -> Option<bool> {
17899 self.show_indent_guides
17900 }
17901
17902 pub fn toggle_line_numbers(
17903 &mut self,
17904 _: &ToggleLineNumbers,
17905 _: &mut Window,
17906 cx: &mut Context<Self>,
17907 ) {
17908 let mut editor_settings = EditorSettings::get_global(cx).clone();
17909 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17910 EditorSettings::override_global(editor_settings, cx);
17911 }
17912
17913 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17914 if let Some(show_line_numbers) = self.show_line_numbers {
17915 return show_line_numbers;
17916 }
17917 EditorSettings::get_global(cx).gutter.line_numbers
17918 }
17919
17920 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17921 self.use_relative_line_numbers
17922 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17923 }
17924
17925 pub fn toggle_relative_line_numbers(
17926 &mut self,
17927 _: &ToggleRelativeLineNumbers,
17928 _: &mut Window,
17929 cx: &mut Context<Self>,
17930 ) {
17931 let is_relative = self.should_use_relative_line_numbers(cx);
17932 self.set_relative_line_number(Some(!is_relative), cx)
17933 }
17934
17935 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17936 self.use_relative_line_numbers = is_relative;
17937 cx.notify();
17938 }
17939
17940 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17941 self.show_gutter = show_gutter;
17942 cx.notify();
17943 }
17944
17945 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17946 self.show_scrollbars = ScrollbarAxes {
17947 horizontal: show,
17948 vertical: show,
17949 };
17950 cx.notify();
17951 }
17952
17953 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17954 self.show_scrollbars.vertical = show;
17955 cx.notify();
17956 }
17957
17958 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17959 self.show_scrollbars.horizontal = show;
17960 cx.notify();
17961 }
17962
17963 pub fn set_minimap_visibility(
17964 &mut self,
17965 minimap_visibility: MinimapVisibility,
17966 window: &mut Window,
17967 cx: &mut Context<Self>,
17968 ) {
17969 if self.minimap_visibility != minimap_visibility {
17970 if minimap_visibility.visible() && self.minimap.is_none() {
17971 let minimap_settings = EditorSettings::get_global(cx).minimap;
17972 self.minimap =
17973 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17974 }
17975 self.minimap_visibility = minimap_visibility;
17976 cx.notify();
17977 }
17978 }
17979
17980 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17981 self.set_show_scrollbars(false, cx);
17982 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17983 }
17984
17985 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17986 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17987 }
17988
17989 /// Normally the text in full mode and auto height editors is padded on the
17990 /// left side by roughly half a character width for improved hit testing.
17991 ///
17992 /// Use this method to disable this for cases where this is not wanted (e.g.
17993 /// if you want to align the editor text with some other text above or below)
17994 /// or if you want to add this padding to single-line editors.
17995 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17996 self.offset_content = offset_content;
17997 cx.notify();
17998 }
17999
18000 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18001 self.show_line_numbers = Some(show_line_numbers);
18002 cx.notify();
18003 }
18004
18005 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18006 self.disable_expand_excerpt_buttons = true;
18007 cx.notify();
18008 }
18009
18010 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18011 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18012 cx.notify();
18013 }
18014
18015 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18016 self.show_code_actions = Some(show_code_actions);
18017 cx.notify();
18018 }
18019
18020 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18021 self.show_runnables = Some(show_runnables);
18022 cx.notify();
18023 }
18024
18025 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18026 self.show_breakpoints = Some(show_breakpoints);
18027 cx.notify();
18028 }
18029
18030 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18031 if self.display_map.read(cx).masked != masked {
18032 self.display_map.update(cx, |map, _| map.masked = masked);
18033 }
18034 cx.notify()
18035 }
18036
18037 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18038 self.show_wrap_guides = Some(show_wrap_guides);
18039 cx.notify();
18040 }
18041
18042 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18043 self.show_indent_guides = Some(show_indent_guides);
18044 cx.notify();
18045 }
18046
18047 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18048 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18049 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
18050 if let Some(dir) = file.abs_path(cx).parent() {
18051 return Some(dir.to_owned());
18052 }
18053 }
18054
18055 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18056 return Some(project_path.path.to_path_buf());
18057 }
18058 }
18059
18060 None
18061 }
18062
18063 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18064 self.active_excerpt(cx)?
18065 .1
18066 .read(cx)
18067 .file()
18068 .and_then(|f| f.as_local())
18069 }
18070
18071 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18072 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18073 let buffer = buffer.read(cx);
18074 if let Some(project_path) = buffer.project_path(cx) {
18075 let project = self.project.as_ref()?.read(cx);
18076 project.absolute_path(&project_path, cx)
18077 } else {
18078 buffer
18079 .file()
18080 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18081 }
18082 })
18083 }
18084
18085 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18086 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18087 let project_path = buffer.read(cx).project_path(cx)?;
18088 let project = self.project.as_ref()?.read(cx);
18089 let entry = project.entry_for_path(&project_path, cx)?;
18090 let path = entry.path.to_path_buf();
18091 Some(path)
18092 })
18093 }
18094
18095 pub fn reveal_in_finder(
18096 &mut self,
18097 _: &RevealInFileManager,
18098 _window: &mut Window,
18099 cx: &mut Context<Self>,
18100 ) {
18101 if let Some(target) = self.target_file(cx) {
18102 cx.reveal_path(&target.abs_path(cx));
18103 }
18104 }
18105
18106 pub fn copy_path(
18107 &mut self,
18108 _: &zed_actions::workspace::CopyPath,
18109 _window: &mut Window,
18110 cx: &mut Context<Self>,
18111 ) {
18112 if let Some(path) = self.target_file_abs_path(cx) {
18113 if let Some(path) = path.to_str() {
18114 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18115 }
18116 }
18117 }
18118
18119 pub fn copy_relative_path(
18120 &mut self,
18121 _: &zed_actions::workspace::CopyRelativePath,
18122 _window: &mut Window,
18123 cx: &mut Context<Self>,
18124 ) {
18125 if let Some(path) = self.target_file_path(cx) {
18126 if let Some(path) = path.to_str() {
18127 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18128 }
18129 }
18130 }
18131
18132 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18133 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18134 buffer.read(cx).project_path(cx)
18135 } else {
18136 None
18137 }
18138 }
18139
18140 // Returns true if the editor handled a go-to-line request
18141 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18142 maybe!({
18143 let breakpoint_store = self.breakpoint_store.as_ref()?;
18144
18145 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18146 else {
18147 self.clear_row_highlights::<ActiveDebugLine>();
18148 return None;
18149 };
18150
18151 let position = active_stack_frame.position;
18152 let buffer_id = position.buffer_id?;
18153 let snapshot = self
18154 .project
18155 .as_ref()?
18156 .read(cx)
18157 .buffer_for_id(buffer_id, cx)?
18158 .read(cx)
18159 .snapshot();
18160
18161 let mut handled = false;
18162 for (id, ExcerptRange { context, .. }) in
18163 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18164 {
18165 if context.start.cmp(&position, &snapshot).is_ge()
18166 || context.end.cmp(&position, &snapshot).is_lt()
18167 {
18168 continue;
18169 }
18170 let snapshot = self.buffer.read(cx).snapshot(cx);
18171 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18172
18173 handled = true;
18174 self.clear_row_highlights::<ActiveDebugLine>();
18175
18176 self.go_to_line::<ActiveDebugLine>(
18177 multibuffer_anchor,
18178 Some(cx.theme().colors().editor_debugger_active_line_background),
18179 window,
18180 cx,
18181 );
18182
18183 cx.notify();
18184 }
18185
18186 handled.then_some(())
18187 })
18188 .is_some()
18189 }
18190
18191 pub fn copy_file_name_without_extension(
18192 &mut self,
18193 _: &CopyFileNameWithoutExtension,
18194 _: &mut Window,
18195 cx: &mut Context<Self>,
18196 ) {
18197 if let Some(file) = self.target_file(cx) {
18198 if let Some(file_stem) = file.path().file_stem() {
18199 if let Some(name) = file_stem.to_str() {
18200 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18201 }
18202 }
18203 }
18204 }
18205
18206 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18207 if let Some(file) = self.target_file(cx) {
18208 if let Some(file_name) = file.path().file_name() {
18209 if let Some(name) = file_name.to_str() {
18210 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18211 }
18212 }
18213 }
18214 }
18215
18216 pub fn toggle_git_blame(
18217 &mut self,
18218 _: &::git::Blame,
18219 window: &mut Window,
18220 cx: &mut Context<Self>,
18221 ) {
18222 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18223
18224 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18225 self.start_git_blame(true, window, cx);
18226 }
18227
18228 cx.notify();
18229 }
18230
18231 pub fn toggle_git_blame_inline(
18232 &mut self,
18233 _: &ToggleGitBlameInline,
18234 window: &mut Window,
18235 cx: &mut Context<Self>,
18236 ) {
18237 self.toggle_git_blame_inline_internal(true, window, cx);
18238 cx.notify();
18239 }
18240
18241 pub fn open_git_blame_commit(
18242 &mut self,
18243 _: &OpenGitBlameCommit,
18244 window: &mut Window,
18245 cx: &mut Context<Self>,
18246 ) {
18247 self.open_git_blame_commit_internal(window, cx);
18248 }
18249
18250 fn open_git_blame_commit_internal(
18251 &mut self,
18252 window: &mut Window,
18253 cx: &mut Context<Self>,
18254 ) -> Option<()> {
18255 let blame = self.blame.as_ref()?;
18256 let snapshot = self.snapshot(window, cx);
18257 let cursor = self.selections.newest::<Point>(cx).head();
18258 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18259 let blame_entry = blame
18260 .update(cx, |blame, cx| {
18261 blame
18262 .blame_for_rows(
18263 &[RowInfo {
18264 buffer_id: Some(buffer.remote_id()),
18265 buffer_row: Some(point.row),
18266 ..Default::default()
18267 }],
18268 cx,
18269 )
18270 .next()
18271 })
18272 .flatten()?;
18273 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18274 let repo = blame.read(cx).repository(cx)?;
18275 let workspace = self.workspace()?.downgrade();
18276 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18277 None
18278 }
18279
18280 pub fn git_blame_inline_enabled(&self) -> bool {
18281 self.git_blame_inline_enabled
18282 }
18283
18284 pub fn toggle_selection_menu(
18285 &mut self,
18286 _: &ToggleSelectionMenu,
18287 _: &mut Window,
18288 cx: &mut Context<Self>,
18289 ) {
18290 self.show_selection_menu = self
18291 .show_selection_menu
18292 .map(|show_selections_menu| !show_selections_menu)
18293 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18294
18295 cx.notify();
18296 }
18297
18298 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18299 self.show_selection_menu
18300 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18301 }
18302
18303 fn start_git_blame(
18304 &mut self,
18305 user_triggered: bool,
18306 window: &mut Window,
18307 cx: &mut Context<Self>,
18308 ) {
18309 if let Some(project) = self.project.as_ref() {
18310 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18311 return;
18312 };
18313
18314 if buffer.read(cx).file().is_none() {
18315 return;
18316 }
18317
18318 let focused = self.focus_handle(cx).contains_focused(window, cx);
18319
18320 let project = project.clone();
18321 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18322 self.blame_subscription =
18323 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18324 self.blame = Some(blame);
18325 }
18326 }
18327
18328 fn toggle_git_blame_inline_internal(
18329 &mut self,
18330 user_triggered: bool,
18331 window: &mut Window,
18332 cx: &mut Context<Self>,
18333 ) {
18334 if self.git_blame_inline_enabled {
18335 self.git_blame_inline_enabled = false;
18336 self.show_git_blame_inline = false;
18337 self.show_git_blame_inline_delay_task.take();
18338 } else {
18339 self.git_blame_inline_enabled = true;
18340 self.start_git_blame_inline(user_triggered, window, cx);
18341 }
18342
18343 cx.notify();
18344 }
18345
18346 fn start_git_blame_inline(
18347 &mut self,
18348 user_triggered: bool,
18349 window: &mut Window,
18350 cx: &mut Context<Self>,
18351 ) {
18352 self.start_git_blame(user_triggered, window, cx);
18353
18354 if ProjectSettings::get_global(cx)
18355 .git
18356 .inline_blame_delay()
18357 .is_some()
18358 {
18359 self.start_inline_blame_timer(window, cx);
18360 } else {
18361 self.show_git_blame_inline = true
18362 }
18363 }
18364
18365 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18366 self.blame.as_ref()
18367 }
18368
18369 pub fn show_git_blame_gutter(&self) -> bool {
18370 self.show_git_blame_gutter
18371 }
18372
18373 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18374 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18375 }
18376
18377 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18378 self.show_git_blame_inline
18379 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18380 && !self.newest_selection_head_on_empty_line(cx)
18381 && self.has_blame_entries(cx)
18382 }
18383
18384 fn has_blame_entries(&self, cx: &App) -> bool {
18385 self.blame()
18386 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18387 }
18388
18389 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18390 let cursor_anchor = self.selections.newest_anchor().head();
18391
18392 let snapshot = self.buffer.read(cx).snapshot(cx);
18393 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18394
18395 snapshot.line_len(buffer_row) == 0
18396 }
18397
18398 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18399 let buffer_and_selection = maybe!({
18400 let selection = self.selections.newest::<Point>(cx);
18401 let selection_range = selection.range();
18402
18403 let multi_buffer = self.buffer().read(cx);
18404 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18405 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18406
18407 let (buffer, range, _) = if selection.reversed {
18408 buffer_ranges.first()
18409 } else {
18410 buffer_ranges.last()
18411 }?;
18412
18413 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18414 ..text::ToPoint::to_point(&range.end, &buffer).row;
18415 Some((
18416 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18417 selection,
18418 ))
18419 });
18420
18421 let Some((buffer, selection)) = buffer_and_selection else {
18422 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18423 };
18424
18425 let Some(project) = self.project.as_ref() else {
18426 return Task::ready(Err(anyhow!("editor does not have project")));
18427 };
18428
18429 project.update(cx, |project, cx| {
18430 project.get_permalink_to_line(&buffer, selection, cx)
18431 })
18432 }
18433
18434 pub fn copy_permalink_to_line(
18435 &mut self,
18436 _: &CopyPermalinkToLine,
18437 window: &mut Window,
18438 cx: &mut Context<Self>,
18439 ) {
18440 let permalink_task = self.get_permalink_to_line(cx);
18441 let workspace = self.workspace();
18442
18443 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18444 Ok(permalink) => {
18445 cx.update(|_, cx| {
18446 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18447 })
18448 .ok();
18449 }
18450 Err(err) => {
18451 let message = format!("Failed to copy permalink: {err}");
18452
18453 anyhow::Result::<()>::Err(err).log_err();
18454
18455 if let Some(workspace) = workspace {
18456 workspace
18457 .update_in(cx, |workspace, _, cx| {
18458 struct CopyPermalinkToLine;
18459
18460 workspace.show_toast(
18461 Toast::new(
18462 NotificationId::unique::<CopyPermalinkToLine>(),
18463 message,
18464 ),
18465 cx,
18466 )
18467 })
18468 .ok();
18469 }
18470 }
18471 })
18472 .detach();
18473 }
18474
18475 pub fn copy_file_location(
18476 &mut self,
18477 _: &CopyFileLocation,
18478 _: &mut Window,
18479 cx: &mut Context<Self>,
18480 ) {
18481 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18482 if let Some(file) = self.target_file(cx) {
18483 if let Some(path) = file.path().to_str() {
18484 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18485 }
18486 }
18487 }
18488
18489 pub fn open_permalink_to_line(
18490 &mut self,
18491 _: &OpenPermalinkToLine,
18492 window: &mut Window,
18493 cx: &mut Context<Self>,
18494 ) {
18495 let permalink_task = self.get_permalink_to_line(cx);
18496 let workspace = self.workspace();
18497
18498 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18499 Ok(permalink) => {
18500 cx.update(|_, cx| {
18501 cx.open_url(permalink.as_ref());
18502 })
18503 .ok();
18504 }
18505 Err(err) => {
18506 let message = format!("Failed to open permalink: {err}");
18507
18508 anyhow::Result::<()>::Err(err).log_err();
18509
18510 if let Some(workspace) = workspace {
18511 workspace
18512 .update(cx, |workspace, cx| {
18513 struct OpenPermalinkToLine;
18514
18515 workspace.show_toast(
18516 Toast::new(
18517 NotificationId::unique::<OpenPermalinkToLine>(),
18518 message,
18519 ),
18520 cx,
18521 )
18522 })
18523 .ok();
18524 }
18525 }
18526 })
18527 .detach();
18528 }
18529
18530 pub fn insert_uuid_v4(
18531 &mut self,
18532 _: &InsertUuidV4,
18533 window: &mut Window,
18534 cx: &mut Context<Self>,
18535 ) {
18536 self.insert_uuid(UuidVersion::V4, window, cx);
18537 }
18538
18539 pub fn insert_uuid_v7(
18540 &mut self,
18541 _: &InsertUuidV7,
18542 window: &mut Window,
18543 cx: &mut Context<Self>,
18544 ) {
18545 self.insert_uuid(UuidVersion::V7, window, cx);
18546 }
18547
18548 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18549 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18550 self.transact(window, cx, |this, window, cx| {
18551 let edits = this
18552 .selections
18553 .all::<Point>(cx)
18554 .into_iter()
18555 .map(|selection| {
18556 let uuid = match version {
18557 UuidVersion::V4 => uuid::Uuid::new_v4(),
18558 UuidVersion::V7 => uuid::Uuid::now_v7(),
18559 };
18560
18561 (selection.range(), uuid.to_string())
18562 });
18563 this.edit(edits, cx);
18564 this.refresh_inline_completion(true, false, window, cx);
18565 });
18566 }
18567
18568 pub fn open_selections_in_multibuffer(
18569 &mut self,
18570 _: &OpenSelectionsInMultibuffer,
18571 window: &mut Window,
18572 cx: &mut Context<Self>,
18573 ) {
18574 let multibuffer = self.buffer.read(cx);
18575
18576 let Some(buffer) = multibuffer.as_singleton() else {
18577 return;
18578 };
18579
18580 let Some(workspace) = self.workspace() else {
18581 return;
18582 };
18583
18584 let title = multibuffer.title(cx).to_string();
18585
18586 let locations = self
18587 .selections
18588 .all_anchors(cx)
18589 .into_iter()
18590 .map(|selection| Location {
18591 buffer: buffer.clone(),
18592 range: selection.start.text_anchor..selection.end.text_anchor,
18593 })
18594 .collect::<Vec<_>>();
18595
18596 cx.spawn_in(window, async move |_, cx| {
18597 workspace.update_in(cx, |workspace, window, cx| {
18598 Self::open_locations_in_multibuffer(
18599 workspace,
18600 locations,
18601 format!("Selections for '{title}'"),
18602 false,
18603 MultibufferSelectionMode::All,
18604 window,
18605 cx,
18606 );
18607 })
18608 })
18609 .detach();
18610 }
18611
18612 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18613 /// last highlight added will be used.
18614 ///
18615 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18616 pub fn highlight_rows<T: 'static>(
18617 &mut self,
18618 range: Range<Anchor>,
18619 color: Hsla,
18620 options: RowHighlightOptions,
18621 cx: &mut Context<Self>,
18622 ) {
18623 let snapshot = self.buffer().read(cx).snapshot(cx);
18624 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18625 let ix = row_highlights.binary_search_by(|highlight| {
18626 Ordering::Equal
18627 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18628 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18629 });
18630
18631 if let Err(mut ix) = ix {
18632 let index = post_inc(&mut self.highlight_order);
18633
18634 // If this range intersects with the preceding highlight, then merge it with
18635 // the preceding highlight. Otherwise insert a new highlight.
18636 let mut merged = false;
18637 if ix > 0 {
18638 let prev_highlight = &mut row_highlights[ix - 1];
18639 if prev_highlight
18640 .range
18641 .end
18642 .cmp(&range.start, &snapshot)
18643 .is_ge()
18644 {
18645 ix -= 1;
18646 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18647 prev_highlight.range.end = range.end;
18648 }
18649 merged = true;
18650 prev_highlight.index = index;
18651 prev_highlight.color = color;
18652 prev_highlight.options = options;
18653 }
18654 }
18655
18656 if !merged {
18657 row_highlights.insert(
18658 ix,
18659 RowHighlight {
18660 range: range.clone(),
18661 index,
18662 color,
18663 options,
18664 type_id: TypeId::of::<T>(),
18665 },
18666 );
18667 }
18668
18669 // If any of the following highlights intersect with this one, merge them.
18670 while let Some(next_highlight) = row_highlights.get(ix + 1) {
18671 let highlight = &row_highlights[ix];
18672 if next_highlight
18673 .range
18674 .start
18675 .cmp(&highlight.range.end, &snapshot)
18676 .is_le()
18677 {
18678 if next_highlight
18679 .range
18680 .end
18681 .cmp(&highlight.range.end, &snapshot)
18682 .is_gt()
18683 {
18684 row_highlights[ix].range.end = next_highlight.range.end;
18685 }
18686 row_highlights.remove(ix + 1);
18687 } else {
18688 break;
18689 }
18690 }
18691 }
18692 }
18693
18694 /// Remove any highlighted row ranges of the given type that intersect the
18695 /// given ranges.
18696 pub fn remove_highlighted_rows<T: 'static>(
18697 &mut self,
18698 ranges_to_remove: Vec<Range<Anchor>>,
18699 cx: &mut Context<Self>,
18700 ) {
18701 let snapshot = self.buffer().read(cx).snapshot(cx);
18702 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18703 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18704 row_highlights.retain(|highlight| {
18705 while let Some(range_to_remove) = ranges_to_remove.peek() {
18706 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18707 Ordering::Less | Ordering::Equal => {
18708 ranges_to_remove.next();
18709 }
18710 Ordering::Greater => {
18711 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18712 Ordering::Less | Ordering::Equal => {
18713 return false;
18714 }
18715 Ordering::Greater => break,
18716 }
18717 }
18718 }
18719 }
18720
18721 true
18722 })
18723 }
18724
18725 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18726 pub fn clear_row_highlights<T: 'static>(&mut self) {
18727 self.highlighted_rows.remove(&TypeId::of::<T>());
18728 }
18729
18730 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18731 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18732 self.highlighted_rows
18733 .get(&TypeId::of::<T>())
18734 .map_or(&[] as &[_], |vec| vec.as_slice())
18735 .iter()
18736 .map(|highlight| (highlight.range.clone(), highlight.color))
18737 }
18738
18739 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18740 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18741 /// Allows to ignore certain kinds of highlights.
18742 pub fn highlighted_display_rows(
18743 &self,
18744 window: &mut Window,
18745 cx: &mut App,
18746 ) -> BTreeMap<DisplayRow, LineHighlight> {
18747 let snapshot = self.snapshot(window, cx);
18748 let mut used_highlight_orders = HashMap::default();
18749 self.highlighted_rows
18750 .iter()
18751 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18752 .fold(
18753 BTreeMap::<DisplayRow, LineHighlight>::new(),
18754 |mut unique_rows, highlight| {
18755 let start = highlight.range.start.to_display_point(&snapshot);
18756 let end = highlight.range.end.to_display_point(&snapshot);
18757 let start_row = start.row().0;
18758 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18759 && end.column() == 0
18760 {
18761 end.row().0.saturating_sub(1)
18762 } else {
18763 end.row().0
18764 };
18765 for row in start_row..=end_row {
18766 let used_index =
18767 used_highlight_orders.entry(row).or_insert(highlight.index);
18768 if highlight.index >= *used_index {
18769 *used_index = highlight.index;
18770 unique_rows.insert(
18771 DisplayRow(row),
18772 LineHighlight {
18773 include_gutter: highlight.options.include_gutter,
18774 border: None,
18775 background: highlight.color.into(),
18776 type_id: Some(highlight.type_id),
18777 },
18778 );
18779 }
18780 }
18781 unique_rows
18782 },
18783 )
18784 }
18785
18786 pub fn highlighted_display_row_for_autoscroll(
18787 &self,
18788 snapshot: &DisplaySnapshot,
18789 ) -> Option<DisplayRow> {
18790 self.highlighted_rows
18791 .values()
18792 .flat_map(|highlighted_rows| highlighted_rows.iter())
18793 .filter_map(|highlight| {
18794 if highlight.options.autoscroll {
18795 Some(highlight.range.start.to_display_point(snapshot).row())
18796 } else {
18797 None
18798 }
18799 })
18800 .min()
18801 }
18802
18803 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18804 self.highlight_background::<SearchWithinRange>(
18805 ranges,
18806 |colors| colors.colors().editor_document_highlight_read_background,
18807 cx,
18808 )
18809 }
18810
18811 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18812 self.breadcrumb_header = Some(new_header);
18813 }
18814
18815 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18816 self.clear_background_highlights::<SearchWithinRange>(cx);
18817 }
18818
18819 pub fn highlight_background<T: 'static>(
18820 &mut self,
18821 ranges: &[Range<Anchor>],
18822 color_fetcher: fn(&Theme) -> Hsla,
18823 cx: &mut Context<Self>,
18824 ) {
18825 self.background_highlights.insert(
18826 HighlightKey::Type(TypeId::of::<T>()),
18827 (color_fetcher, Arc::from(ranges)),
18828 );
18829 self.scrollbar_marker_state.dirty = true;
18830 cx.notify();
18831 }
18832
18833 pub fn highlight_background_key<T: 'static>(
18834 &mut self,
18835 key: usize,
18836 ranges: &[Range<Anchor>],
18837 color_fetcher: fn(&Theme) -> Hsla,
18838 cx: &mut Context<Self>,
18839 ) {
18840 self.background_highlights.insert(
18841 HighlightKey::TypePlus(TypeId::of::<T>(), key),
18842 (color_fetcher, Arc::from(ranges)),
18843 );
18844 self.scrollbar_marker_state.dirty = true;
18845 cx.notify();
18846 }
18847
18848 pub fn clear_background_highlights<T: 'static>(
18849 &mut self,
18850 cx: &mut Context<Self>,
18851 ) -> Option<BackgroundHighlight> {
18852 let text_highlights = self
18853 .background_highlights
18854 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
18855 if !text_highlights.1.is_empty() {
18856 self.scrollbar_marker_state.dirty = true;
18857 cx.notify();
18858 }
18859 Some(text_highlights)
18860 }
18861
18862 pub fn highlight_gutter<T: 'static>(
18863 &mut self,
18864 ranges: impl Into<Vec<Range<Anchor>>>,
18865 color_fetcher: fn(&App) -> Hsla,
18866 cx: &mut Context<Self>,
18867 ) {
18868 self.gutter_highlights
18869 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
18870 cx.notify();
18871 }
18872
18873 pub fn clear_gutter_highlights<T: 'static>(
18874 &mut self,
18875 cx: &mut Context<Self>,
18876 ) -> Option<GutterHighlight> {
18877 cx.notify();
18878 self.gutter_highlights.remove(&TypeId::of::<T>())
18879 }
18880
18881 pub fn insert_gutter_highlight<T: 'static>(
18882 &mut self,
18883 range: Range<Anchor>,
18884 color_fetcher: fn(&App) -> Hsla,
18885 cx: &mut Context<Self>,
18886 ) {
18887 let snapshot = self.buffer().read(cx).snapshot(cx);
18888 let mut highlights = self
18889 .gutter_highlights
18890 .remove(&TypeId::of::<T>())
18891 .map(|(_, highlights)| highlights)
18892 .unwrap_or_default();
18893 let ix = highlights.binary_search_by(|highlight| {
18894 Ordering::Equal
18895 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
18896 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
18897 });
18898 if let Err(ix) = ix {
18899 highlights.insert(ix, range);
18900 }
18901 self.gutter_highlights
18902 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
18903 }
18904
18905 pub fn remove_gutter_highlights<T: 'static>(
18906 &mut self,
18907 ranges_to_remove: Vec<Range<Anchor>>,
18908 cx: &mut Context<Self>,
18909 ) {
18910 let snapshot = self.buffer().read(cx).snapshot(cx);
18911 let Some((color_fetcher, mut gutter_highlights)) =
18912 self.gutter_highlights.remove(&TypeId::of::<T>())
18913 else {
18914 return;
18915 };
18916 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18917 gutter_highlights.retain(|highlight| {
18918 while let Some(range_to_remove) = ranges_to_remove.peek() {
18919 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
18920 Ordering::Less | Ordering::Equal => {
18921 ranges_to_remove.next();
18922 }
18923 Ordering::Greater => {
18924 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
18925 Ordering::Less | Ordering::Equal => {
18926 return false;
18927 }
18928 Ordering::Greater => break,
18929 }
18930 }
18931 }
18932 }
18933
18934 true
18935 });
18936 self.gutter_highlights
18937 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
18938 }
18939
18940 #[cfg(feature = "test-support")]
18941 pub fn all_text_highlights(
18942 &self,
18943 window: &mut Window,
18944 cx: &mut Context<Self>,
18945 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
18946 let snapshot = self.snapshot(window, cx);
18947 self.display_map.update(cx, |display_map, _| {
18948 display_map
18949 .all_text_highlights()
18950 .map(|highlight| {
18951 let (style, ranges) = highlight.as_ref();
18952 (
18953 *style,
18954 ranges
18955 .iter()
18956 .map(|range| range.clone().to_display_points(&snapshot))
18957 .collect(),
18958 )
18959 })
18960 .collect()
18961 })
18962 }
18963
18964 #[cfg(feature = "test-support")]
18965 pub fn all_text_background_highlights(
18966 &self,
18967 window: &mut Window,
18968 cx: &mut Context<Self>,
18969 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18970 let snapshot = self.snapshot(window, cx);
18971 let buffer = &snapshot.buffer_snapshot;
18972 let start = buffer.anchor_before(0);
18973 let end = buffer.anchor_after(buffer.len());
18974 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
18975 }
18976
18977 #[cfg(feature = "test-support")]
18978 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18979 let snapshot = self.buffer().read(cx).snapshot(cx);
18980
18981 let highlights = self
18982 .background_highlights
18983 .get(&HighlightKey::Type(TypeId::of::<
18984 items::BufferSearchHighlights,
18985 >()));
18986
18987 if let Some((_color, ranges)) = highlights {
18988 ranges
18989 .iter()
18990 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
18991 .collect_vec()
18992 } else {
18993 vec![]
18994 }
18995 }
18996
18997 fn document_highlights_for_position<'a>(
18998 &'a self,
18999 position: Anchor,
19000 buffer: &'a MultiBufferSnapshot,
19001 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19002 let read_highlights = self
19003 .background_highlights
19004 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19005 .map(|h| &h.1);
19006 let write_highlights = self
19007 .background_highlights
19008 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19009 .map(|h| &h.1);
19010 let left_position = position.bias_left(buffer);
19011 let right_position = position.bias_right(buffer);
19012 read_highlights
19013 .into_iter()
19014 .chain(write_highlights)
19015 .flat_map(move |ranges| {
19016 let start_ix = match ranges.binary_search_by(|probe| {
19017 let cmp = probe.end.cmp(&left_position, buffer);
19018 if cmp.is_ge() {
19019 Ordering::Greater
19020 } else {
19021 Ordering::Less
19022 }
19023 }) {
19024 Ok(i) | Err(i) => i,
19025 };
19026
19027 ranges[start_ix..]
19028 .iter()
19029 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19030 })
19031 }
19032
19033 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19034 self.background_highlights
19035 .get(&HighlightKey::Type(TypeId::of::<T>()))
19036 .map_or(false, |(_, highlights)| !highlights.is_empty())
19037 }
19038
19039 pub fn background_highlights_in_range(
19040 &self,
19041 search_range: Range<Anchor>,
19042 display_snapshot: &DisplaySnapshot,
19043 theme: &Theme,
19044 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19045 let mut results = Vec::new();
19046 for (color_fetcher, ranges) in self.background_highlights.values() {
19047 let color = color_fetcher(theme);
19048 let start_ix = match ranges.binary_search_by(|probe| {
19049 let cmp = probe
19050 .end
19051 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19052 if cmp.is_gt() {
19053 Ordering::Greater
19054 } else {
19055 Ordering::Less
19056 }
19057 }) {
19058 Ok(i) | Err(i) => i,
19059 };
19060 for range in &ranges[start_ix..] {
19061 if range
19062 .start
19063 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19064 .is_ge()
19065 {
19066 break;
19067 }
19068
19069 let start = range.start.to_display_point(display_snapshot);
19070 let end = range.end.to_display_point(display_snapshot);
19071 results.push((start..end, color))
19072 }
19073 }
19074 results
19075 }
19076
19077 pub fn background_highlight_row_ranges<T: 'static>(
19078 &self,
19079 search_range: Range<Anchor>,
19080 display_snapshot: &DisplaySnapshot,
19081 count: usize,
19082 ) -> Vec<RangeInclusive<DisplayPoint>> {
19083 let mut results = Vec::new();
19084 let Some((_, ranges)) = self
19085 .background_highlights
19086 .get(&HighlightKey::Type(TypeId::of::<T>()))
19087 else {
19088 return vec![];
19089 };
19090
19091 let start_ix = match ranges.binary_search_by(|probe| {
19092 let cmp = probe
19093 .end
19094 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19095 if cmp.is_gt() {
19096 Ordering::Greater
19097 } else {
19098 Ordering::Less
19099 }
19100 }) {
19101 Ok(i) | Err(i) => i,
19102 };
19103 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19104 if let (Some(start_display), Some(end_display)) = (start, end) {
19105 results.push(
19106 start_display.to_display_point(display_snapshot)
19107 ..=end_display.to_display_point(display_snapshot),
19108 );
19109 }
19110 };
19111 let mut start_row: Option<Point> = None;
19112 let mut end_row: Option<Point> = None;
19113 if ranges.len() > count {
19114 return Vec::new();
19115 }
19116 for range in &ranges[start_ix..] {
19117 if range
19118 .start
19119 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19120 .is_ge()
19121 {
19122 break;
19123 }
19124 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19125 if let Some(current_row) = &end_row {
19126 if end.row == current_row.row {
19127 continue;
19128 }
19129 }
19130 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19131 if start_row.is_none() {
19132 assert_eq!(end_row, None);
19133 start_row = Some(start);
19134 end_row = Some(end);
19135 continue;
19136 }
19137 if let Some(current_end) = end_row.as_mut() {
19138 if start.row > current_end.row + 1 {
19139 push_region(start_row, end_row);
19140 start_row = Some(start);
19141 end_row = Some(end);
19142 } else {
19143 // Merge two hunks.
19144 *current_end = end;
19145 }
19146 } else {
19147 unreachable!();
19148 }
19149 }
19150 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19151 push_region(start_row, end_row);
19152 results
19153 }
19154
19155 pub fn gutter_highlights_in_range(
19156 &self,
19157 search_range: Range<Anchor>,
19158 display_snapshot: &DisplaySnapshot,
19159 cx: &App,
19160 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19161 let mut results = Vec::new();
19162 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19163 let color = color_fetcher(cx);
19164 let start_ix = match ranges.binary_search_by(|probe| {
19165 let cmp = probe
19166 .end
19167 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19168 if cmp.is_gt() {
19169 Ordering::Greater
19170 } else {
19171 Ordering::Less
19172 }
19173 }) {
19174 Ok(i) | Err(i) => i,
19175 };
19176 for range in &ranges[start_ix..] {
19177 if range
19178 .start
19179 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19180 .is_ge()
19181 {
19182 break;
19183 }
19184
19185 let start = range.start.to_display_point(display_snapshot);
19186 let end = range.end.to_display_point(display_snapshot);
19187 results.push((start..end, color))
19188 }
19189 }
19190 results
19191 }
19192
19193 /// Get the text ranges corresponding to the redaction query
19194 pub fn redacted_ranges(
19195 &self,
19196 search_range: Range<Anchor>,
19197 display_snapshot: &DisplaySnapshot,
19198 cx: &App,
19199 ) -> Vec<Range<DisplayPoint>> {
19200 display_snapshot
19201 .buffer_snapshot
19202 .redacted_ranges(search_range, |file| {
19203 if let Some(file) = file {
19204 file.is_private()
19205 && EditorSettings::get(
19206 Some(SettingsLocation {
19207 worktree_id: file.worktree_id(cx),
19208 path: file.path().as_ref(),
19209 }),
19210 cx,
19211 )
19212 .redact_private_values
19213 } else {
19214 false
19215 }
19216 })
19217 .map(|range| {
19218 range.start.to_display_point(display_snapshot)
19219 ..range.end.to_display_point(display_snapshot)
19220 })
19221 .collect()
19222 }
19223
19224 pub fn highlight_text_key<T: 'static>(
19225 &mut self,
19226 key: usize,
19227 ranges: Vec<Range<Anchor>>,
19228 style: HighlightStyle,
19229 cx: &mut Context<Self>,
19230 ) {
19231 self.display_map.update(cx, |map, _| {
19232 map.highlight_text(
19233 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19234 ranges,
19235 style,
19236 );
19237 });
19238 cx.notify();
19239 }
19240
19241 pub fn highlight_text<T: 'static>(
19242 &mut self,
19243 ranges: Vec<Range<Anchor>>,
19244 style: HighlightStyle,
19245 cx: &mut Context<Self>,
19246 ) {
19247 self.display_map.update(cx, |map, _| {
19248 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19249 });
19250 cx.notify();
19251 }
19252
19253 pub(crate) fn highlight_inlays<T: 'static>(
19254 &mut self,
19255 highlights: Vec<InlayHighlight>,
19256 style: HighlightStyle,
19257 cx: &mut Context<Self>,
19258 ) {
19259 self.display_map.update(cx, |map, _| {
19260 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19261 });
19262 cx.notify();
19263 }
19264
19265 pub fn text_highlights<'a, T: 'static>(
19266 &'a self,
19267 cx: &'a App,
19268 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19269 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19270 }
19271
19272 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19273 let cleared = self
19274 .display_map
19275 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19276 if cleared {
19277 cx.notify();
19278 }
19279 }
19280
19281 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19282 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19283 && self.focus_handle.is_focused(window)
19284 }
19285
19286 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19287 self.show_cursor_when_unfocused = is_enabled;
19288 cx.notify();
19289 }
19290
19291 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19292 cx.notify();
19293 }
19294
19295 fn on_debug_session_event(
19296 &mut self,
19297 _session: Entity<Session>,
19298 event: &SessionEvent,
19299 cx: &mut Context<Self>,
19300 ) {
19301 match event {
19302 SessionEvent::InvalidateInlineValue => {
19303 self.refresh_inline_values(cx);
19304 }
19305 _ => {}
19306 }
19307 }
19308
19309 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19310 let Some(project) = self.project.clone() else {
19311 return;
19312 };
19313
19314 if !self.inline_value_cache.enabled {
19315 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19316 self.splice_inlays(&inlays, Vec::new(), cx);
19317 return;
19318 }
19319
19320 let current_execution_position = self
19321 .highlighted_rows
19322 .get(&TypeId::of::<ActiveDebugLine>())
19323 .and_then(|lines| lines.last().map(|line| line.range.end));
19324
19325 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19326 let inline_values = editor
19327 .update(cx, |editor, cx| {
19328 let Some(current_execution_position) = current_execution_position else {
19329 return Some(Task::ready(Ok(Vec::new())));
19330 };
19331
19332 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19333 let snapshot = buffer.snapshot(cx);
19334
19335 let excerpt = snapshot.excerpt_containing(
19336 current_execution_position..current_execution_position,
19337 )?;
19338
19339 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19340 })?;
19341
19342 let range =
19343 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19344
19345 project.inline_values(buffer, range, cx)
19346 })
19347 .ok()
19348 .flatten()?
19349 .await
19350 .context("refreshing debugger inlays")
19351 .log_err()?;
19352
19353 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19354
19355 for (buffer_id, inline_value) in inline_values
19356 .into_iter()
19357 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19358 {
19359 buffer_inline_values
19360 .entry(buffer_id)
19361 .or_default()
19362 .push(inline_value);
19363 }
19364
19365 editor
19366 .update(cx, |editor, cx| {
19367 let snapshot = editor.buffer.read(cx).snapshot(cx);
19368 let mut new_inlays = Vec::default();
19369
19370 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19371 let buffer_id = buffer_snapshot.remote_id();
19372 buffer_inline_values
19373 .get(&buffer_id)
19374 .into_iter()
19375 .flatten()
19376 .for_each(|hint| {
19377 let inlay = Inlay::debugger(
19378 post_inc(&mut editor.next_inlay_id),
19379 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19380 hint.text(),
19381 );
19382
19383 new_inlays.push(inlay);
19384 });
19385 }
19386
19387 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19388 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19389
19390 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19391 })
19392 .ok()?;
19393 Some(())
19394 });
19395 }
19396
19397 fn on_buffer_event(
19398 &mut self,
19399 multibuffer: &Entity<MultiBuffer>,
19400 event: &multi_buffer::Event,
19401 window: &mut Window,
19402 cx: &mut Context<Self>,
19403 ) {
19404 match event {
19405 multi_buffer::Event::Edited {
19406 singleton_buffer_edited,
19407 edited_buffer,
19408 } => {
19409 self.scrollbar_marker_state.dirty = true;
19410 self.active_indent_guides_state.dirty = true;
19411 self.refresh_active_diagnostics(cx);
19412 self.refresh_code_actions(window, cx);
19413 self.refresh_selected_text_highlights(true, window, cx);
19414 refresh_matching_bracket_highlights(self, window, cx);
19415 if self.has_active_inline_completion() {
19416 self.update_visible_inline_completion(window, cx);
19417 }
19418 if let Some(project) = self.project.as_ref() {
19419 if let Some(edited_buffer) = edited_buffer {
19420 project.update(cx, |project, cx| {
19421 self.registered_buffers
19422 .entry(edited_buffer.read(cx).remote_id())
19423 .or_insert_with(|| {
19424 project
19425 .register_buffer_with_language_servers(&edited_buffer, cx)
19426 });
19427 });
19428 }
19429 }
19430 cx.emit(EditorEvent::BufferEdited);
19431 cx.emit(SearchEvent::MatchesInvalidated);
19432
19433 if let Some(buffer) = edited_buffer {
19434 self.update_lsp_data(None, Some(buffer.read(cx).remote_id()), window, cx);
19435 }
19436
19437 if *singleton_buffer_edited {
19438 if let Some(buffer) = edited_buffer {
19439 if buffer.read(cx).file().is_none() {
19440 cx.emit(EditorEvent::TitleChanged);
19441 }
19442 }
19443 if let Some(project) = &self.project {
19444 #[allow(clippy::mutable_key_type)]
19445 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19446 multibuffer
19447 .all_buffers()
19448 .into_iter()
19449 .filter_map(|buffer| {
19450 buffer.update(cx, |buffer, cx| {
19451 let language = buffer.language()?;
19452 let should_discard = project.update(cx, |project, cx| {
19453 project.is_local()
19454 && !project.has_language_servers_for(buffer, cx)
19455 });
19456 should_discard.not().then_some(language.clone())
19457 })
19458 })
19459 .collect::<HashSet<_>>()
19460 });
19461 if !languages_affected.is_empty() {
19462 self.refresh_inlay_hints(
19463 InlayHintRefreshReason::BufferEdited(languages_affected),
19464 cx,
19465 );
19466 }
19467 }
19468 }
19469
19470 let Some(project) = &self.project else { return };
19471 let (telemetry, is_via_ssh) = {
19472 let project = project.read(cx);
19473 let telemetry = project.client().telemetry().clone();
19474 let is_via_ssh = project.is_via_ssh();
19475 (telemetry, is_via_ssh)
19476 };
19477 refresh_linked_ranges(self, window, cx);
19478 telemetry.log_edit_event("editor", is_via_ssh);
19479 }
19480 multi_buffer::Event::ExcerptsAdded {
19481 buffer,
19482 predecessor,
19483 excerpts,
19484 } => {
19485 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19486 let buffer_id = buffer.read(cx).remote_id();
19487 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19488 if let Some(project) = &self.project {
19489 update_uncommitted_diff_for_buffer(
19490 cx.entity(),
19491 project,
19492 [buffer.clone()],
19493 self.buffer.clone(),
19494 cx,
19495 )
19496 .detach();
19497 }
19498 }
19499 self.update_lsp_data(None, Some(buffer_id), window, cx);
19500 cx.emit(EditorEvent::ExcerptsAdded {
19501 buffer: buffer.clone(),
19502 predecessor: *predecessor,
19503 excerpts: excerpts.clone(),
19504 });
19505 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19506 }
19507 multi_buffer::Event::ExcerptsRemoved {
19508 ids,
19509 removed_buffer_ids,
19510 } => {
19511 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19512 let buffer = self.buffer.read(cx);
19513 self.registered_buffers
19514 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19515 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19516 cx.emit(EditorEvent::ExcerptsRemoved {
19517 ids: ids.clone(),
19518 removed_buffer_ids: removed_buffer_ids.clone(),
19519 });
19520 }
19521 multi_buffer::Event::ExcerptsEdited {
19522 excerpt_ids,
19523 buffer_ids,
19524 } => {
19525 self.display_map.update(cx, |map, cx| {
19526 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19527 });
19528 cx.emit(EditorEvent::ExcerptsEdited {
19529 ids: excerpt_ids.clone(),
19530 });
19531 }
19532 multi_buffer::Event::ExcerptsExpanded { ids } => {
19533 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19534 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19535 }
19536 multi_buffer::Event::Reparsed(buffer_id) => {
19537 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19538 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19539
19540 cx.emit(EditorEvent::Reparsed(*buffer_id));
19541 }
19542 multi_buffer::Event::DiffHunksToggled => {
19543 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19544 }
19545 multi_buffer::Event::LanguageChanged(buffer_id) => {
19546 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19547 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19548 cx.emit(EditorEvent::Reparsed(*buffer_id));
19549 cx.notify();
19550 }
19551 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19552 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19553 multi_buffer::Event::FileHandleChanged
19554 | multi_buffer::Event::Reloaded
19555 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19556 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19557 multi_buffer::Event::DiagnosticsUpdated => {
19558 self.update_diagnostics_state(window, cx);
19559 }
19560 _ => {}
19561 };
19562 }
19563
19564 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19565 if !self.diagnostics_enabled() {
19566 return;
19567 }
19568 self.refresh_active_diagnostics(cx);
19569 self.refresh_inline_diagnostics(true, window, cx);
19570 self.scrollbar_marker_state.dirty = true;
19571 cx.notify();
19572 }
19573
19574 pub fn start_temporary_diff_override(&mut self) {
19575 self.load_diff_task.take();
19576 self.temporary_diff_override = true;
19577 }
19578
19579 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19580 self.temporary_diff_override = false;
19581 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19582 self.buffer.update(cx, |buffer, cx| {
19583 buffer.set_all_diff_hunks_collapsed(cx);
19584 });
19585
19586 if let Some(project) = self.project.clone() {
19587 self.load_diff_task = Some(
19588 update_uncommitted_diff_for_buffer(
19589 cx.entity(),
19590 &project,
19591 self.buffer.read(cx).all_buffers(),
19592 self.buffer.clone(),
19593 cx,
19594 )
19595 .shared(),
19596 );
19597 }
19598 }
19599
19600 fn on_display_map_changed(
19601 &mut self,
19602 _: Entity<DisplayMap>,
19603 _: &mut Window,
19604 cx: &mut Context<Self>,
19605 ) {
19606 cx.notify();
19607 }
19608
19609 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19610 let new_severity = if self.diagnostics_enabled() {
19611 EditorSettings::get_global(cx)
19612 .diagnostics_max_severity
19613 .unwrap_or(DiagnosticSeverity::Hint)
19614 } else {
19615 DiagnosticSeverity::Off
19616 };
19617 self.set_max_diagnostics_severity(new_severity, cx);
19618 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19619 self.update_edit_prediction_settings(cx);
19620 self.refresh_inline_completion(true, false, window, cx);
19621 self.refresh_inlay_hints(
19622 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19623 self.selections.newest_anchor().head(),
19624 &self.buffer.read(cx).snapshot(cx),
19625 cx,
19626 )),
19627 cx,
19628 );
19629
19630 let old_cursor_shape = self.cursor_shape;
19631
19632 {
19633 let editor_settings = EditorSettings::get_global(cx);
19634 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19635 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19636 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19637 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19638 self.drag_and_drop_selection_enabled = editor_settings.drag_and_drop_selection;
19639 }
19640
19641 if old_cursor_shape != self.cursor_shape {
19642 cx.emit(EditorEvent::CursorShapeChanged);
19643 }
19644
19645 let project_settings = ProjectSettings::get_global(cx);
19646 self.serialize_dirty_buffers =
19647 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19648
19649 if self.mode.is_full() {
19650 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19651 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19652 if self.show_inline_diagnostics != show_inline_diagnostics {
19653 self.show_inline_diagnostics = show_inline_diagnostics;
19654 self.refresh_inline_diagnostics(false, window, cx);
19655 }
19656
19657 if self.git_blame_inline_enabled != inline_blame_enabled {
19658 self.toggle_git_blame_inline_internal(false, window, cx);
19659 }
19660
19661 let minimap_settings = EditorSettings::get_global(cx).minimap;
19662 if self.minimap_visibility != MinimapVisibility::Disabled {
19663 if self.minimap_visibility.settings_visibility()
19664 != minimap_settings.minimap_enabled()
19665 {
19666 self.set_minimap_visibility(
19667 MinimapVisibility::for_mode(self.mode(), cx),
19668 window,
19669 cx,
19670 );
19671 } else if let Some(minimap_entity) = self.minimap.as_ref() {
19672 minimap_entity.update(cx, |minimap_editor, cx| {
19673 minimap_editor.update_minimap_configuration(minimap_settings, cx)
19674 })
19675 }
19676 }
19677 }
19678
19679 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
19680 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
19681 }) {
19682 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
19683 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
19684 }
19685 self.refresh_colors(None, None, window, cx);
19686 }
19687
19688 cx.notify();
19689 }
19690
19691 pub fn set_searchable(&mut self, searchable: bool) {
19692 self.searchable = searchable;
19693 }
19694
19695 pub fn searchable(&self) -> bool {
19696 self.searchable
19697 }
19698
19699 fn open_proposed_changes_editor(
19700 &mut self,
19701 _: &OpenProposedChangesEditor,
19702 window: &mut Window,
19703 cx: &mut Context<Self>,
19704 ) {
19705 let Some(workspace) = self.workspace() else {
19706 cx.propagate();
19707 return;
19708 };
19709
19710 let selections = self.selections.all::<usize>(cx);
19711 let multi_buffer = self.buffer.read(cx);
19712 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19713 let mut new_selections_by_buffer = HashMap::default();
19714 for selection in selections {
19715 for (buffer, range, _) in
19716 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
19717 {
19718 let mut range = range.to_point(buffer);
19719 range.start.column = 0;
19720 range.end.column = buffer.line_len(range.end.row);
19721 new_selections_by_buffer
19722 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
19723 .or_insert(Vec::new())
19724 .push(range)
19725 }
19726 }
19727
19728 let proposed_changes_buffers = new_selections_by_buffer
19729 .into_iter()
19730 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
19731 .collect::<Vec<_>>();
19732 let proposed_changes_editor = cx.new(|cx| {
19733 ProposedChangesEditor::new(
19734 "Proposed changes",
19735 proposed_changes_buffers,
19736 self.project.clone(),
19737 window,
19738 cx,
19739 )
19740 });
19741
19742 window.defer(cx, move |window, cx| {
19743 workspace.update(cx, |workspace, cx| {
19744 workspace.active_pane().update(cx, |pane, cx| {
19745 pane.add_item(
19746 Box::new(proposed_changes_editor),
19747 true,
19748 true,
19749 None,
19750 window,
19751 cx,
19752 );
19753 });
19754 });
19755 });
19756 }
19757
19758 pub fn open_excerpts_in_split(
19759 &mut self,
19760 _: &OpenExcerptsSplit,
19761 window: &mut Window,
19762 cx: &mut Context<Self>,
19763 ) {
19764 self.open_excerpts_common(None, true, window, cx)
19765 }
19766
19767 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
19768 self.open_excerpts_common(None, false, window, cx)
19769 }
19770
19771 fn open_excerpts_common(
19772 &mut self,
19773 jump_data: Option<JumpData>,
19774 split: bool,
19775 window: &mut Window,
19776 cx: &mut Context<Self>,
19777 ) {
19778 let Some(workspace) = self.workspace() else {
19779 cx.propagate();
19780 return;
19781 };
19782
19783 if self.buffer.read(cx).is_singleton() {
19784 cx.propagate();
19785 return;
19786 }
19787
19788 let mut new_selections_by_buffer = HashMap::default();
19789 match &jump_data {
19790 Some(JumpData::MultiBufferPoint {
19791 excerpt_id,
19792 position,
19793 anchor,
19794 line_offset_from_top,
19795 }) => {
19796 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19797 if let Some(buffer) = multi_buffer_snapshot
19798 .buffer_id_for_excerpt(*excerpt_id)
19799 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
19800 {
19801 let buffer_snapshot = buffer.read(cx).snapshot();
19802 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
19803 language::ToPoint::to_point(anchor, &buffer_snapshot)
19804 } else {
19805 buffer_snapshot.clip_point(*position, Bias::Left)
19806 };
19807 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
19808 new_selections_by_buffer.insert(
19809 buffer,
19810 (
19811 vec![jump_to_offset..jump_to_offset],
19812 Some(*line_offset_from_top),
19813 ),
19814 );
19815 }
19816 }
19817 Some(JumpData::MultiBufferRow {
19818 row,
19819 line_offset_from_top,
19820 }) => {
19821 let point = MultiBufferPoint::new(row.0, 0);
19822 if let Some((buffer, buffer_point, _)) =
19823 self.buffer.read(cx).point_to_buffer_point(point, cx)
19824 {
19825 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
19826 new_selections_by_buffer
19827 .entry(buffer)
19828 .or_insert((Vec::new(), Some(*line_offset_from_top)))
19829 .0
19830 .push(buffer_offset..buffer_offset)
19831 }
19832 }
19833 None => {
19834 let selections = self.selections.all::<usize>(cx);
19835 let multi_buffer = self.buffer.read(cx);
19836 for selection in selections {
19837 for (snapshot, range, _, anchor) in multi_buffer
19838 .snapshot(cx)
19839 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
19840 {
19841 if let Some(anchor) = anchor {
19842 // selection is in a deleted hunk
19843 let Some(buffer_id) = anchor.buffer_id else {
19844 continue;
19845 };
19846 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
19847 continue;
19848 };
19849 let offset = text::ToOffset::to_offset(
19850 &anchor.text_anchor,
19851 &buffer_handle.read(cx).snapshot(),
19852 );
19853 let range = offset..offset;
19854 new_selections_by_buffer
19855 .entry(buffer_handle)
19856 .or_insert((Vec::new(), None))
19857 .0
19858 .push(range)
19859 } else {
19860 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
19861 else {
19862 continue;
19863 };
19864 new_selections_by_buffer
19865 .entry(buffer_handle)
19866 .or_insert((Vec::new(), None))
19867 .0
19868 .push(range)
19869 }
19870 }
19871 }
19872 }
19873 }
19874
19875 new_selections_by_buffer
19876 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
19877
19878 if new_selections_by_buffer.is_empty() {
19879 return;
19880 }
19881
19882 // We defer the pane interaction because we ourselves are a workspace item
19883 // and activating a new item causes the pane to call a method on us reentrantly,
19884 // which panics if we're on the stack.
19885 window.defer(cx, move |window, cx| {
19886 workspace.update(cx, |workspace, cx| {
19887 let pane = if split {
19888 workspace.adjacent_pane(window, cx)
19889 } else {
19890 workspace.active_pane().clone()
19891 };
19892
19893 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
19894 let editor = buffer
19895 .read(cx)
19896 .file()
19897 .is_none()
19898 .then(|| {
19899 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
19900 // so `workspace.open_project_item` will never find them, always opening a new editor.
19901 // Instead, we try to activate the existing editor in the pane first.
19902 let (editor, pane_item_index) =
19903 pane.read(cx).items().enumerate().find_map(|(i, item)| {
19904 let editor = item.downcast::<Editor>()?;
19905 let singleton_buffer =
19906 editor.read(cx).buffer().read(cx).as_singleton()?;
19907 if singleton_buffer == buffer {
19908 Some((editor, i))
19909 } else {
19910 None
19911 }
19912 })?;
19913 pane.update(cx, |pane, cx| {
19914 pane.activate_item(pane_item_index, true, true, window, cx)
19915 });
19916 Some(editor)
19917 })
19918 .flatten()
19919 .unwrap_or_else(|| {
19920 workspace.open_project_item::<Self>(
19921 pane.clone(),
19922 buffer,
19923 true,
19924 true,
19925 window,
19926 cx,
19927 )
19928 });
19929
19930 editor.update(cx, |editor, cx| {
19931 let autoscroll = match scroll_offset {
19932 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
19933 None => Autoscroll::newest(),
19934 };
19935 let nav_history = editor.nav_history.take();
19936 editor.change_selections(Some(autoscroll), window, cx, |s| {
19937 s.select_ranges(ranges);
19938 });
19939 editor.nav_history = nav_history;
19940 });
19941 }
19942 })
19943 });
19944 }
19945
19946 // For now, don't allow opening excerpts in buffers that aren't backed by
19947 // regular project files.
19948 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
19949 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
19950 }
19951
19952 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
19953 let snapshot = self.buffer.read(cx).read(cx);
19954 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
19955 Some(
19956 ranges
19957 .iter()
19958 .map(move |range| {
19959 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19960 })
19961 .collect(),
19962 )
19963 }
19964
19965 fn selection_replacement_ranges(
19966 &self,
19967 range: Range<OffsetUtf16>,
19968 cx: &mut App,
19969 ) -> Vec<Range<OffsetUtf16>> {
19970 let selections = self.selections.all::<OffsetUtf16>(cx);
19971 let newest_selection = selections
19972 .iter()
19973 .max_by_key(|selection| selection.id)
19974 .unwrap();
19975 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19976 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19977 let snapshot = self.buffer.read(cx).read(cx);
19978 selections
19979 .into_iter()
19980 .map(|mut selection| {
19981 selection.start.0 =
19982 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19983 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19984 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19985 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19986 })
19987 .collect()
19988 }
19989
19990 fn report_editor_event(
19991 &self,
19992 event_type: &'static str,
19993 file_extension: Option<String>,
19994 cx: &App,
19995 ) {
19996 if cfg!(any(test, feature = "test-support")) {
19997 return;
19998 }
19999
20000 let Some(project) = &self.project else { return };
20001
20002 // If None, we are in a file without an extension
20003 let file = self
20004 .buffer
20005 .read(cx)
20006 .as_singleton()
20007 .and_then(|b| b.read(cx).file());
20008 let file_extension = file_extension.or(file
20009 .as_ref()
20010 .and_then(|file| Path::new(file.file_name(cx)).extension())
20011 .and_then(|e| e.to_str())
20012 .map(|a| a.to_string()));
20013
20014 let vim_mode = vim_enabled(cx);
20015
20016 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20017 let copilot_enabled = edit_predictions_provider
20018 == language::language_settings::EditPredictionProvider::Copilot;
20019 let copilot_enabled_for_language = self
20020 .buffer
20021 .read(cx)
20022 .language_settings(cx)
20023 .show_edit_predictions;
20024
20025 let project = project.read(cx);
20026 telemetry::event!(
20027 event_type,
20028 file_extension,
20029 vim_mode,
20030 copilot_enabled,
20031 copilot_enabled_for_language,
20032 edit_predictions_provider,
20033 is_via_ssh = project.is_via_ssh(),
20034 );
20035 }
20036
20037 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20038 /// with each line being an array of {text, highlight} objects.
20039 fn copy_highlight_json(
20040 &mut self,
20041 _: &CopyHighlightJson,
20042 window: &mut Window,
20043 cx: &mut Context<Self>,
20044 ) {
20045 #[derive(Serialize)]
20046 struct Chunk<'a> {
20047 text: String,
20048 highlight: Option<&'a str>,
20049 }
20050
20051 let snapshot = self.buffer.read(cx).snapshot(cx);
20052 let range = self
20053 .selected_text_range(false, window, cx)
20054 .and_then(|selection| {
20055 if selection.range.is_empty() {
20056 None
20057 } else {
20058 Some(selection.range)
20059 }
20060 })
20061 .unwrap_or_else(|| 0..snapshot.len());
20062
20063 let chunks = snapshot.chunks(range, true);
20064 let mut lines = Vec::new();
20065 let mut line: VecDeque<Chunk> = VecDeque::new();
20066
20067 let Some(style) = self.style.as_ref() else {
20068 return;
20069 };
20070
20071 for chunk in chunks {
20072 let highlight = chunk
20073 .syntax_highlight_id
20074 .and_then(|id| id.name(&style.syntax));
20075 let mut chunk_lines = chunk.text.split('\n').peekable();
20076 while let Some(text) = chunk_lines.next() {
20077 let mut merged_with_last_token = false;
20078 if let Some(last_token) = line.back_mut() {
20079 if last_token.highlight == highlight {
20080 last_token.text.push_str(text);
20081 merged_with_last_token = true;
20082 }
20083 }
20084
20085 if !merged_with_last_token {
20086 line.push_back(Chunk {
20087 text: text.into(),
20088 highlight,
20089 });
20090 }
20091
20092 if chunk_lines.peek().is_some() {
20093 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20094 line.pop_front();
20095 }
20096 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20097 line.pop_back();
20098 }
20099
20100 lines.push(mem::take(&mut line));
20101 }
20102 }
20103 }
20104
20105 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20106 return;
20107 };
20108 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20109 }
20110
20111 pub fn open_context_menu(
20112 &mut self,
20113 _: &OpenContextMenu,
20114 window: &mut Window,
20115 cx: &mut Context<Self>,
20116 ) {
20117 self.request_autoscroll(Autoscroll::newest(), cx);
20118 let position = self.selections.newest_display(cx).start;
20119 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20120 }
20121
20122 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20123 &self.inlay_hint_cache
20124 }
20125
20126 pub fn replay_insert_event(
20127 &mut self,
20128 text: &str,
20129 relative_utf16_range: Option<Range<isize>>,
20130 window: &mut Window,
20131 cx: &mut Context<Self>,
20132 ) {
20133 if !self.input_enabled {
20134 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20135 return;
20136 }
20137 if let Some(relative_utf16_range) = relative_utf16_range {
20138 let selections = self.selections.all::<OffsetUtf16>(cx);
20139 self.change_selections(None, window, cx, |s| {
20140 let new_ranges = selections.into_iter().map(|range| {
20141 let start = OffsetUtf16(
20142 range
20143 .head()
20144 .0
20145 .saturating_add_signed(relative_utf16_range.start),
20146 );
20147 let end = OffsetUtf16(
20148 range
20149 .head()
20150 .0
20151 .saturating_add_signed(relative_utf16_range.end),
20152 );
20153 start..end
20154 });
20155 s.select_ranges(new_ranges);
20156 });
20157 }
20158
20159 self.handle_input(text, window, cx);
20160 }
20161
20162 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20163 let Some(provider) = self.semantics_provider.as_ref() else {
20164 return false;
20165 };
20166
20167 let mut supports = false;
20168 self.buffer().update(cx, |this, cx| {
20169 this.for_each_buffer(|buffer| {
20170 supports |= provider.supports_inlay_hints(buffer, cx);
20171 });
20172 });
20173
20174 supports
20175 }
20176
20177 pub fn is_focused(&self, window: &Window) -> bool {
20178 self.focus_handle.is_focused(window)
20179 }
20180
20181 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20182 cx.emit(EditorEvent::Focused);
20183
20184 if let Some(descendant) = self
20185 .last_focused_descendant
20186 .take()
20187 .and_then(|descendant| descendant.upgrade())
20188 {
20189 window.focus(&descendant);
20190 } else {
20191 if let Some(blame) = self.blame.as_ref() {
20192 blame.update(cx, GitBlame::focus)
20193 }
20194
20195 self.blink_manager.update(cx, BlinkManager::enable);
20196 self.show_cursor_names(window, cx);
20197 self.buffer.update(cx, |buffer, cx| {
20198 buffer.finalize_last_transaction(cx);
20199 if self.leader_id.is_none() {
20200 buffer.set_active_selections(
20201 &self.selections.disjoint_anchors(),
20202 self.selections.line_mode,
20203 self.cursor_shape,
20204 cx,
20205 );
20206 }
20207 });
20208 }
20209 }
20210
20211 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20212 cx.emit(EditorEvent::FocusedIn)
20213 }
20214
20215 fn handle_focus_out(
20216 &mut self,
20217 event: FocusOutEvent,
20218 _window: &mut Window,
20219 cx: &mut Context<Self>,
20220 ) {
20221 if event.blurred != self.focus_handle {
20222 self.last_focused_descendant = Some(event.blurred);
20223 }
20224 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20225 }
20226
20227 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20228 self.blink_manager.update(cx, BlinkManager::disable);
20229 self.buffer
20230 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20231
20232 if let Some(blame) = self.blame.as_ref() {
20233 blame.update(cx, GitBlame::blur)
20234 }
20235 if !self.hover_state.focused(window, cx) {
20236 hide_hover(self, cx);
20237 }
20238 if !self
20239 .context_menu
20240 .borrow()
20241 .as_ref()
20242 .is_some_and(|context_menu| context_menu.focused(window, cx))
20243 {
20244 self.hide_context_menu(window, cx);
20245 }
20246 self.discard_inline_completion(false, cx);
20247 cx.emit(EditorEvent::Blurred);
20248 cx.notify();
20249 }
20250
20251 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20252 let mut pending: String = window
20253 .pending_input_keystrokes()
20254 .into_iter()
20255 .flatten()
20256 .filter_map(|keystroke| {
20257 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20258 keystroke.key_char.clone()
20259 } else {
20260 None
20261 }
20262 })
20263 .collect();
20264
20265 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20266 pending = "".to_string();
20267 }
20268
20269 let existing_pending = self
20270 .text_highlights::<PendingInput>(cx)
20271 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20272 if existing_pending.is_none() && pending.is_empty() {
20273 return;
20274 }
20275 let transaction =
20276 self.transact(window, cx, |this, window, cx| {
20277 let selections = this.selections.all::<usize>(cx);
20278 let edits = selections
20279 .iter()
20280 .map(|selection| (selection.end..selection.end, pending.clone()));
20281 this.edit(edits, cx);
20282 this.change_selections(None, window, cx, |s| {
20283 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20284 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20285 }));
20286 });
20287 if let Some(existing_ranges) = existing_pending {
20288 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20289 this.edit(edits, cx);
20290 }
20291 });
20292
20293 let snapshot = self.snapshot(window, cx);
20294 let ranges = self
20295 .selections
20296 .all::<usize>(cx)
20297 .into_iter()
20298 .map(|selection| {
20299 snapshot.buffer_snapshot.anchor_after(selection.end)
20300 ..snapshot
20301 .buffer_snapshot
20302 .anchor_before(selection.end + pending.len())
20303 })
20304 .collect();
20305
20306 if pending.is_empty() {
20307 self.clear_highlights::<PendingInput>(cx);
20308 } else {
20309 self.highlight_text::<PendingInput>(
20310 ranges,
20311 HighlightStyle {
20312 underline: Some(UnderlineStyle {
20313 thickness: px(1.),
20314 color: None,
20315 wavy: false,
20316 }),
20317 ..Default::default()
20318 },
20319 cx,
20320 );
20321 }
20322
20323 self.ime_transaction = self.ime_transaction.or(transaction);
20324 if let Some(transaction) = self.ime_transaction {
20325 self.buffer.update(cx, |buffer, cx| {
20326 buffer.group_until_transaction(transaction, cx);
20327 });
20328 }
20329
20330 if self.text_highlights::<PendingInput>(cx).is_none() {
20331 self.ime_transaction.take();
20332 }
20333 }
20334
20335 pub fn register_action_renderer(
20336 &mut self,
20337 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20338 ) -> Subscription {
20339 let id = self.next_editor_action_id.post_inc();
20340 self.editor_actions
20341 .borrow_mut()
20342 .insert(id, Box::new(listener));
20343
20344 let editor_actions = self.editor_actions.clone();
20345 Subscription::new(move || {
20346 editor_actions.borrow_mut().remove(&id);
20347 })
20348 }
20349
20350 pub fn register_action<A: Action>(
20351 &mut self,
20352 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20353 ) -> Subscription {
20354 let id = self.next_editor_action_id.post_inc();
20355 let listener = Arc::new(listener);
20356 self.editor_actions.borrow_mut().insert(
20357 id,
20358 Box::new(move |_, window, _| {
20359 let listener = listener.clone();
20360 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20361 let action = action.downcast_ref().unwrap();
20362 if phase == DispatchPhase::Bubble {
20363 listener(action, window, cx)
20364 }
20365 })
20366 }),
20367 );
20368
20369 let editor_actions = self.editor_actions.clone();
20370 Subscription::new(move || {
20371 editor_actions.borrow_mut().remove(&id);
20372 })
20373 }
20374
20375 pub fn file_header_size(&self) -> u32 {
20376 FILE_HEADER_HEIGHT
20377 }
20378
20379 pub fn restore(
20380 &mut self,
20381 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20382 window: &mut Window,
20383 cx: &mut Context<Self>,
20384 ) {
20385 let workspace = self.workspace();
20386 let project = self.project.as_ref();
20387 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20388 let mut tasks = Vec::new();
20389 for (buffer_id, changes) in revert_changes {
20390 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20391 buffer.update(cx, |buffer, cx| {
20392 buffer.edit(
20393 changes
20394 .into_iter()
20395 .map(|(range, text)| (range, text.to_string())),
20396 None,
20397 cx,
20398 );
20399 });
20400
20401 if let Some(project) =
20402 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20403 {
20404 project.update(cx, |project, cx| {
20405 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20406 })
20407 }
20408 }
20409 }
20410 tasks
20411 });
20412 cx.spawn_in(window, async move |_, cx| {
20413 for (buffer, task) in save_tasks {
20414 let result = task.await;
20415 if result.is_err() {
20416 let Some(path) = buffer
20417 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20418 .ok()
20419 else {
20420 continue;
20421 };
20422 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20423 let Some(task) = cx
20424 .update_window_entity(&workspace, |workspace, window, cx| {
20425 workspace
20426 .open_path_preview(path, None, false, false, false, window, cx)
20427 })
20428 .ok()
20429 else {
20430 continue;
20431 };
20432 task.await.log_err();
20433 }
20434 }
20435 }
20436 })
20437 .detach();
20438 self.change_selections(None, window, cx, |selections| selections.refresh());
20439 }
20440
20441 pub fn to_pixel_point(
20442 &self,
20443 source: multi_buffer::Anchor,
20444 editor_snapshot: &EditorSnapshot,
20445 window: &mut Window,
20446 ) -> Option<gpui::Point<Pixels>> {
20447 let source_point = source.to_display_point(editor_snapshot);
20448 self.display_to_pixel_point(source_point, editor_snapshot, window)
20449 }
20450
20451 pub fn display_to_pixel_point(
20452 &self,
20453 source: DisplayPoint,
20454 editor_snapshot: &EditorSnapshot,
20455 window: &mut Window,
20456 ) -> Option<gpui::Point<Pixels>> {
20457 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20458 let text_layout_details = self.text_layout_details(window);
20459 let scroll_top = text_layout_details
20460 .scroll_anchor
20461 .scroll_position(editor_snapshot)
20462 .y;
20463
20464 if source.row().as_f32() < scroll_top.floor() {
20465 return None;
20466 }
20467 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20468 let source_y = line_height * (source.row().as_f32() - scroll_top);
20469 Some(gpui::Point::new(source_x, source_y))
20470 }
20471
20472 pub fn has_visible_completions_menu(&self) -> bool {
20473 !self.edit_prediction_preview_is_active()
20474 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20475 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20476 })
20477 }
20478
20479 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20480 if self.mode.is_minimap() {
20481 return;
20482 }
20483 self.addons
20484 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20485 }
20486
20487 pub fn unregister_addon<T: Addon>(&mut self) {
20488 self.addons.remove(&std::any::TypeId::of::<T>());
20489 }
20490
20491 pub fn addon<T: Addon>(&self) -> Option<&T> {
20492 let type_id = std::any::TypeId::of::<T>();
20493 self.addons
20494 .get(&type_id)
20495 .and_then(|item| item.to_any().downcast_ref::<T>())
20496 }
20497
20498 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20499 let type_id = std::any::TypeId::of::<T>();
20500 self.addons
20501 .get_mut(&type_id)
20502 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20503 }
20504
20505 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
20506 let text_layout_details = self.text_layout_details(window);
20507 let style = &text_layout_details.editor_style;
20508 let font_id = window.text_system().resolve_font(&style.text.font());
20509 let font_size = style.text.font_size.to_pixels(window.rem_size());
20510 let line_height = style.text.line_height_in_pixels(window.rem_size());
20511 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20512
20513 gpui::Size::new(em_width, line_height)
20514 }
20515
20516 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20517 self.load_diff_task.clone()
20518 }
20519
20520 fn read_metadata_from_db(
20521 &mut self,
20522 item_id: u64,
20523 workspace_id: WorkspaceId,
20524 window: &mut Window,
20525 cx: &mut Context<Editor>,
20526 ) {
20527 if self.is_singleton(cx)
20528 && !self.mode.is_minimap()
20529 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20530 {
20531 let buffer_snapshot = OnceCell::new();
20532
20533 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20534 if !folds.is_empty() {
20535 let snapshot =
20536 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20537 self.fold_ranges(
20538 folds
20539 .into_iter()
20540 .map(|(start, end)| {
20541 snapshot.clip_offset(start, Bias::Left)
20542 ..snapshot.clip_offset(end, Bias::Right)
20543 })
20544 .collect(),
20545 false,
20546 window,
20547 cx,
20548 );
20549 }
20550 }
20551
20552 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20553 if !selections.is_empty() {
20554 let snapshot =
20555 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20556 // skip adding the initial selection to selection history
20557 self.selection_history.mode = SelectionHistoryMode::Skipping;
20558 self.change_selections(None, window, cx, |s| {
20559 s.select_ranges(selections.into_iter().map(|(start, end)| {
20560 snapshot.clip_offset(start, Bias::Left)
20561 ..snapshot.clip_offset(end, Bias::Right)
20562 }));
20563 });
20564 self.selection_history.mode = SelectionHistoryMode::Normal;
20565 }
20566 };
20567 }
20568
20569 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20570 }
20571
20572 fn update_lsp_data(
20573 &mut self,
20574 for_server_id: Option<LanguageServerId>,
20575 for_buffer: Option<BufferId>,
20576 window: &mut Window,
20577 cx: &mut Context<'_, Self>,
20578 ) {
20579 self.pull_diagnostics(for_buffer, window, cx);
20580 self.refresh_colors(for_server_id, for_buffer, window, cx);
20581 }
20582}
20583
20584fn vim_enabled(cx: &App) -> bool {
20585 cx.global::<SettingsStore>()
20586 .raw_user_settings()
20587 .get("vim_mode")
20588 == Some(&serde_json::Value::Bool(true))
20589}
20590
20591fn process_completion_for_edit(
20592 completion: &Completion,
20593 intent: CompletionIntent,
20594 buffer: &Entity<Buffer>,
20595 cursor_position: &text::Anchor,
20596 cx: &mut Context<Editor>,
20597) -> CompletionEdit {
20598 let buffer = buffer.read(cx);
20599 let buffer_snapshot = buffer.snapshot();
20600 let (snippet, new_text) = if completion.is_snippet() {
20601 // Workaround for typescript language server issues so that methods don't expand within
20602 // strings and functions with type expressions. The previous point is used because the query
20603 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
20604 let mut snippet_source = completion.new_text.clone();
20605 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
20606 previous_point.column = previous_point.column.saturating_sub(1);
20607 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
20608 if scope.prefers_label_for_snippet_in_completion() {
20609 if let Some(label) = completion.label() {
20610 if matches!(
20611 completion.kind(),
20612 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20613 ) {
20614 snippet_source = label;
20615 }
20616 }
20617 }
20618 }
20619 match Snippet::parse(&snippet_source).log_err() {
20620 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20621 None => (None, completion.new_text.clone()),
20622 }
20623 } else {
20624 (None, completion.new_text.clone())
20625 };
20626
20627 let mut range_to_replace = {
20628 let replace_range = &completion.replace_range;
20629 if let CompletionSource::Lsp {
20630 insert_range: Some(insert_range),
20631 ..
20632 } = &completion.source
20633 {
20634 debug_assert_eq!(
20635 insert_range.start, replace_range.start,
20636 "insert_range and replace_range should start at the same position"
20637 );
20638 debug_assert!(
20639 insert_range
20640 .start
20641 .cmp(&cursor_position, &buffer_snapshot)
20642 .is_le(),
20643 "insert_range should start before or at cursor position"
20644 );
20645 debug_assert!(
20646 replace_range
20647 .start
20648 .cmp(&cursor_position, &buffer_snapshot)
20649 .is_le(),
20650 "replace_range should start before or at cursor position"
20651 );
20652 debug_assert!(
20653 insert_range
20654 .end
20655 .cmp(&cursor_position, &buffer_snapshot)
20656 .is_le(),
20657 "insert_range should end before or at cursor position"
20658 );
20659
20660 let should_replace = match intent {
20661 CompletionIntent::CompleteWithInsert => false,
20662 CompletionIntent::CompleteWithReplace => true,
20663 CompletionIntent::Complete | CompletionIntent::Compose => {
20664 let insert_mode =
20665 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
20666 .completions
20667 .lsp_insert_mode;
20668 match insert_mode {
20669 LspInsertMode::Insert => false,
20670 LspInsertMode::Replace => true,
20671 LspInsertMode::ReplaceSubsequence => {
20672 let mut text_to_replace = buffer.chars_for_range(
20673 buffer.anchor_before(replace_range.start)
20674 ..buffer.anchor_after(replace_range.end),
20675 );
20676 let mut current_needle = text_to_replace.next();
20677 for haystack_ch in completion.label.text.chars() {
20678 if let Some(needle_ch) = current_needle {
20679 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
20680 current_needle = text_to_replace.next();
20681 }
20682 }
20683 }
20684 current_needle.is_none()
20685 }
20686 LspInsertMode::ReplaceSuffix => {
20687 if replace_range
20688 .end
20689 .cmp(&cursor_position, &buffer_snapshot)
20690 .is_gt()
20691 {
20692 let range_after_cursor = *cursor_position..replace_range.end;
20693 let text_after_cursor = buffer
20694 .text_for_range(
20695 buffer.anchor_before(range_after_cursor.start)
20696 ..buffer.anchor_after(range_after_cursor.end),
20697 )
20698 .collect::<String>()
20699 .to_ascii_lowercase();
20700 completion
20701 .label
20702 .text
20703 .to_ascii_lowercase()
20704 .ends_with(&text_after_cursor)
20705 } else {
20706 true
20707 }
20708 }
20709 }
20710 }
20711 };
20712
20713 if should_replace {
20714 replace_range.clone()
20715 } else {
20716 insert_range.clone()
20717 }
20718 } else {
20719 replace_range.clone()
20720 }
20721 };
20722
20723 if range_to_replace
20724 .end
20725 .cmp(&cursor_position, &buffer_snapshot)
20726 .is_lt()
20727 {
20728 range_to_replace.end = *cursor_position;
20729 }
20730
20731 CompletionEdit {
20732 new_text,
20733 replace_range: range_to_replace.to_offset(&buffer),
20734 snippet,
20735 }
20736}
20737
20738struct CompletionEdit {
20739 new_text: String,
20740 replace_range: Range<usize>,
20741 snippet: Option<Snippet>,
20742}
20743
20744fn insert_extra_newline_brackets(
20745 buffer: &MultiBufferSnapshot,
20746 range: Range<usize>,
20747 language: &language::LanguageScope,
20748) -> bool {
20749 let leading_whitespace_len = buffer
20750 .reversed_chars_at(range.start)
20751 .take_while(|c| c.is_whitespace() && *c != '\n')
20752 .map(|c| c.len_utf8())
20753 .sum::<usize>();
20754 let trailing_whitespace_len = buffer
20755 .chars_at(range.end)
20756 .take_while(|c| c.is_whitespace() && *c != '\n')
20757 .map(|c| c.len_utf8())
20758 .sum::<usize>();
20759 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
20760
20761 language.brackets().any(|(pair, enabled)| {
20762 let pair_start = pair.start.trim_end();
20763 let pair_end = pair.end.trim_start();
20764
20765 enabled
20766 && pair.newline
20767 && buffer.contains_str_at(range.end, pair_end)
20768 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
20769 })
20770}
20771
20772fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
20773 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
20774 [(buffer, range, _)] => (*buffer, range.clone()),
20775 _ => return false,
20776 };
20777 let pair = {
20778 let mut result: Option<BracketMatch> = None;
20779
20780 for pair in buffer
20781 .all_bracket_ranges(range.clone())
20782 .filter(move |pair| {
20783 pair.open_range.start <= range.start && pair.close_range.end >= range.end
20784 })
20785 {
20786 let len = pair.close_range.end - pair.open_range.start;
20787
20788 if let Some(existing) = &result {
20789 let existing_len = existing.close_range.end - existing.open_range.start;
20790 if len > existing_len {
20791 continue;
20792 }
20793 }
20794
20795 result = Some(pair);
20796 }
20797
20798 result
20799 };
20800 let Some(pair) = pair else {
20801 return false;
20802 };
20803 pair.newline_only
20804 && buffer
20805 .chars_for_range(pair.open_range.end..range.start)
20806 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
20807 .all(|c| c.is_whitespace() && c != '\n')
20808}
20809
20810fn update_uncommitted_diff_for_buffer(
20811 editor: Entity<Editor>,
20812 project: &Entity<Project>,
20813 buffers: impl IntoIterator<Item = Entity<Buffer>>,
20814 buffer: Entity<MultiBuffer>,
20815 cx: &mut App,
20816) -> Task<()> {
20817 let mut tasks = Vec::new();
20818 project.update(cx, |project, cx| {
20819 for buffer in buffers {
20820 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
20821 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
20822 }
20823 }
20824 });
20825 cx.spawn(async move |cx| {
20826 let diffs = future::join_all(tasks).await;
20827 if editor
20828 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
20829 .unwrap_or(false)
20830 {
20831 return;
20832 }
20833
20834 buffer
20835 .update(cx, |buffer, cx| {
20836 for diff in diffs.into_iter().flatten() {
20837 buffer.add_diff(diff, cx);
20838 }
20839 })
20840 .ok();
20841 })
20842}
20843
20844fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
20845 let tab_size = tab_size.get() as usize;
20846 let mut width = offset;
20847
20848 for ch in text.chars() {
20849 width += if ch == '\t' {
20850 tab_size - (width % tab_size)
20851 } else {
20852 1
20853 };
20854 }
20855
20856 width - offset
20857}
20858
20859#[cfg(test)]
20860mod tests {
20861 use super::*;
20862
20863 #[test]
20864 fn test_string_size_with_expanded_tabs() {
20865 let nz = |val| NonZeroU32::new(val).unwrap();
20866 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
20867 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
20868 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
20869 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
20870 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
20871 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
20872 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
20873 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
20874 }
20875}
20876
20877/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
20878struct WordBreakingTokenizer<'a> {
20879 input: &'a str,
20880}
20881
20882impl<'a> WordBreakingTokenizer<'a> {
20883 fn new(input: &'a str) -> Self {
20884 Self { input }
20885 }
20886}
20887
20888fn is_char_ideographic(ch: char) -> bool {
20889 use unicode_script::Script::*;
20890 use unicode_script::UnicodeScript;
20891 matches!(ch.script(), Han | Tangut | Yi)
20892}
20893
20894fn is_grapheme_ideographic(text: &str) -> bool {
20895 text.chars().any(is_char_ideographic)
20896}
20897
20898fn is_grapheme_whitespace(text: &str) -> bool {
20899 text.chars().any(|x| x.is_whitespace())
20900}
20901
20902fn should_stay_with_preceding_ideograph(text: &str) -> bool {
20903 text.chars().next().map_or(false, |ch| {
20904 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
20905 })
20906}
20907
20908#[derive(PartialEq, Eq, Debug, Clone, Copy)]
20909enum WordBreakToken<'a> {
20910 Word { token: &'a str, grapheme_len: usize },
20911 InlineWhitespace { token: &'a str, grapheme_len: usize },
20912 Newline,
20913}
20914
20915impl<'a> Iterator for WordBreakingTokenizer<'a> {
20916 /// Yields a span, the count of graphemes in the token, and whether it was
20917 /// whitespace. Note that it also breaks at word boundaries.
20918 type Item = WordBreakToken<'a>;
20919
20920 fn next(&mut self) -> Option<Self::Item> {
20921 use unicode_segmentation::UnicodeSegmentation;
20922 if self.input.is_empty() {
20923 return None;
20924 }
20925
20926 let mut iter = self.input.graphemes(true).peekable();
20927 let mut offset = 0;
20928 let mut grapheme_len = 0;
20929 if let Some(first_grapheme) = iter.next() {
20930 let is_newline = first_grapheme == "\n";
20931 let is_whitespace = is_grapheme_whitespace(first_grapheme);
20932 offset += first_grapheme.len();
20933 grapheme_len += 1;
20934 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
20935 if let Some(grapheme) = iter.peek().copied() {
20936 if should_stay_with_preceding_ideograph(grapheme) {
20937 offset += grapheme.len();
20938 grapheme_len += 1;
20939 }
20940 }
20941 } else {
20942 let mut words = self.input[offset..].split_word_bound_indices().peekable();
20943 let mut next_word_bound = words.peek().copied();
20944 if next_word_bound.map_or(false, |(i, _)| i == 0) {
20945 next_word_bound = words.next();
20946 }
20947 while let Some(grapheme) = iter.peek().copied() {
20948 if next_word_bound.map_or(false, |(i, _)| i == offset) {
20949 break;
20950 };
20951 if is_grapheme_whitespace(grapheme) != is_whitespace
20952 || (grapheme == "\n") != is_newline
20953 {
20954 break;
20955 };
20956 offset += grapheme.len();
20957 grapheme_len += 1;
20958 iter.next();
20959 }
20960 }
20961 let token = &self.input[..offset];
20962 self.input = &self.input[offset..];
20963 if token == "\n" {
20964 Some(WordBreakToken::Newline)
20965 } else if is_whitespace {
20966 Some(WordBreakToken::InlineWhitespace {
20967 token,
20968 grapheme_len,
20969 })
20970 } else {
20971 Some(WordBreakToken::Word {
20972 token,
20973 grapheme_len,
20974 })
20975 }
20976 } else {
20977 None
20978 }
20979 }
20980}
20981
20982#[test]
20983fn test_word_breaking_tokenizer() {
20984 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
20985 ("", &[]),
20986 (" ", &[whitespace(" ", 2)]),
20987 ("Ʒ", &[word("Ʒ", 1)]),
20988 ("Ǽ", &[word("Ǽ", 1)]),
20989 ("⋑", &[word("⋑", 1)]),
20990 ("⋑⋑", &[word("⋑⋑", 2)]),
20991 (
20992 "原理,进而",
20993 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
20994 ),
20995 (
20996 "hello world",
20997 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
20998 ),
20999 (
21000 "hello, world",
21001 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21002 ),
21003 (
21004 " hello world",
21005 &[
21006 whitespace(" ", 2),
21007 word("hello", 5),
21008 whitespace(" ", 1),
21009 word("world", 5),
21010 ],
21011 ),
21012 (
21013 "这是什么 \n 钢笔",
21014 &[
21015 word("这", 1),
21016 word("是", 1),
21017 word("什", 1),
21018 word("么", 1),
21019 whitespace(" ", 1),
21020 newline(),
21021 whitespace(" ", 1),
21022 word("钢", 1),
21023 word("笔", 1),
21024 ],
21025 ),
21026 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21027 ];
21028
21029 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21030 WordBreakToken::Word {
21031 token,
21032 grapheme_len,
21033 }
21034 }
21035
21036 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21037 WordBreakToken::InlineWhitespace {
21038 token,
21039 grapheme_len,
21040 }
21041 }
21042
21043 fn newline() -> WordBreakToken<'static> {
21044 WordBreakToken::Newline
21045 }
21046
21047 for (input, result) in tests {
21048 assert_eq!(
21049 WordBreakingTokenizer::new(input)
21050 .collect::<Vec<_>>()
21051 .as_slice(),
21052 *result,
21053 );
21054 }
21055}
21056
21057fn wrap_with_prefix(
21058 line_prefix: String,
21059 unwrapped_text: String,
21060 wrap_column: usize,
21061 tab_size: NonZeroU32,
21062 preserve_existing_whitespace: bool,
21063) -> String {
21064 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
21065 let mut wrapped_text = String::new();
21066 let mut current_line = line_prefix.clone();
21067
21068 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21069 let mut current_line_len = line_prefix_len;
21070 let mut in_whitespace = false;
21071 for token in tokenizer {
21072 let have_preceding_whitespace = in_whitespace;
21073 match token {
21074 WordBreakToken::Word {
21075 token,
21076 grapheme_len,
21077 } => {
21078 in_whitespace = false;
21079 if current_line_len + grapheme_len > wrap_column
21080 && current_line_len != line_prefix_len
21081 {
21082 wrapped_text.push_str(current_line.trim_end());
21083 wrapped_text.push('\n');
21084 current_line.truncate(line_prefix.len());
21085 current_line_len = line_prefix_len;
21086 }
21087 current_line.push_str(token);
21088 current_line_len += grapheme_len;
21089 }
21090 WordBreakToken::InlineWhitespace {
21091 mut token,
21092 mut grapheme_len,
21093 } => {
21094 in_whitespace = true;
21095 if have_preceding_whitespace && !preserve_existing_whitespace {
21096 continue;
21097 }
21098 if !preserve_existing_whitespace {
21099 token = " ";
21100 grapheme_len = 1;
21101 }
21102 if current_line_len + grapheme_len > wrap_column {
21103 wrapped_text.push_str(current_line.trim_end());
21104 wrapped_text.push('\n');
21105 current_line.truncate(line_prefix.len());
21106 current_line_len = line_prefix_len;
21107 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
21108 current_line.push_str(token);
21109 current_line_len += grapheme_len;
21110 }
21111 }
21112 WordBreakToken::Newline => {
21113 in_whitespace = true;
21114 if preserve_existing_whitespace {
21115 wrapped_text.push_str(current_line.trim_end());
21116 wrapped_text.push('\n');
21117 current_line.truncate(line_prefix.len());
21118 current_line_len = line_prefix_len;
21119 } else if have_preceding_whitespace {
21120 continue;
21121 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
21122 {
21123 wrapped_text.push_str(current_line.trim_end());
21124 wrapped_text.push('\n');
21125 current_line.truncate(line_prefix.len());
21126 current_line_len = line_prefix_len;
21127 } else if current_line_len != line_prefix_len {
21128 current_line.push(' ');
21129 current_line_len += 1;
21130 }
21131 }
21132 }
21133 }
21134
21135 if !current_line.is_empty() {
21136 wrapped_text.push_str(¤t_line);
21137 }
21138 wrapped_text
21139}
21140
21141#[test]
21142fn test_wrap_with_prefix() {
21143 assert_eq!(
21144 wrap_with_prefix(
21145 "# ".to_string(),
21146 "abcdefg".to_string(),
21147 4,
21148 NonZeroU32::new(4).unwrap(),
21149 false,
21150 ),
21151 "# abcdefg"
21152 );
21153 assert_eq!(
21154 wrap_with_prefix(
21155 "".to_string(),
21156 "\thello world".to_string(),
21157 8,
21158 NonZeroU32::new(4).unwrap(),
21159 false,
21160 ),
21161 "hello\nworld"
21162 );
21163 assert_eq!(
21164 wrap_with_prefix(
21165 "// ".to_string(),
21166 "xx \nyy zz aa bb cc".to_string(),
21167 12,
21168 NonZeroU32::new(4).unwrap(),
21169 false,
21170 ),
21171 "// xx yy zz\n// aa bb cc"
21172 );
21173 assert_eq!(
21174 wrap_with_prefix(
21175 String::new(),
21176 "这是什么 \n 钢笔".to_string(),
21177 3,
21178 NonZeroU32::new(4).unwrap(),
21179 false,
21180 ),
21181 "这是什\n么 钢\n笔"
21182 );
21183}
21184
21185pub trait CollaborationHub {
21186 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21187 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21188 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21189}
21190
21191impl CollaborationHub for Entity<Project> {
21192 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21193 self.read(cx).collaborators()
21194 }
21195
21196 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21197 self.read(cx).user_store().read(cx).participant_indices()
21198 }
21199
21200 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21201 let this = self.read(cx);
21202 let user_ids = this.collaborators().values().map(|c| c.user_id);
21203 this.user_store().read(cx).participant_names(user_ids, cx)
21204 }
21205}
21206
21207pub trait SemanticsProvider {
21208 fn hover(
21209 &self,
21210 buffer: &Entity<Buffer>,
21211 position: text::Anchor,
21212 cx: &mut App,
21213 ) -> Option<Task<Vec<project::Hover>>>;
21214
21215 fn inline_values(
21216 &self,
21217 buffer_handle: Entity<Buffer>,
21218 range: Range<text::Anchor>,
21219 cx: &mut App,
21220 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21221
21222 fn inlay_hints(
21223 &self,
21224 buffer_handle: Entity<Buffer>,
21225 range: Range<text::Anchor>,
21226 cx: &mut App,
21227 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21228
21229 fn resolve_inlay_hint(
21230 &self,
21231 hint: InlayHint,
21232 buffer_handle: Entity<Buffer>,
21233 server_id: LanguageServerId,
21234 cx: &mut App,
21235 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21236
21237 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21238
21239 fn document_highlights(
21240 &self,
21241 buffer: &Entity<Buffer>,
21242 position: text::Anchor,
21243 cx: &mut App,
21244 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21245
21246 fn definitions(
21247 &self,
21248 buffer: &Entity<Buffer>,
21249 position: text::Anchor,
21250 kind: GotoDefinitionKind,
21251 cx: &mut App,
21252 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21253
21254 fn range_for_rename(
21255 &self,
21256 buffer: &Entity<Buffer>,
21257 position: text::Anchor,
21258 cx: &mut App,
21259 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21260
21261 fn perform_rename(
21262 &self,
21263 buffer: &Entity<Buffer>,
21264 position: text::Anchor,
21265 new_name: String,
21266 cx: &mut App,
21267 ) -> Option<Task<Result<ProjectTransaction>>>;
21268}
21269
21270pub trait CompletionProvider {
21271 fn completions(
21272 &self,
21273 excerpt_id: ExcerptId,
21274 buffer: &Entity<Buffer>,
21275 buffer_position: text::Anchor,
21276 trigger: CompletionContext,
21277 window: &mut Window,
21278 cx: &mut Context<Editor>,
21279 ) -> Task<Result<Vec<CompletionResponse>>>;
21280
21281 fn resolve_completions(
21282 &self,
21283 _buffer: Entity<Buffer>,
21284 _completion_indices: Vec<usize>,
21285 _completions: Rc<RefCell<Box<[Completion]>>>,
21286 _cx: &mut Context<Editor>,
21287 ) -> Task<Result<bool>> {
21288 Task::ready(Ok(false))
21289 }
21290
21291 fn apply_additional_edits_for_completion(
21292 &self,
21293 _buffer: Entity<Buffer>,
21294 _completions: Rc<RefCell<Box<[Completion]>>>,
21295 _completion_index: usize,
21296 _push_to_history: bool,
21297 _cx: &mut Context<Editor>,
21298 ) -> Task<Result<Option<language::Transaction>>> {
21299 Task::ready(Ok(None))
21300 }
21301
21302 fn is_completion_trigger(
21303 &self,
21304 buffer: &Entity<Buffer>,
21305 position: language::Anchor,
21306 text: &str,
21307 trigger_in_words: bool,
21308 menu_is_open: bool,
21309 cx: &mut Context<Editor>,
21310 ) -> bool;
21311
21312 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21313
21314 fn sort_completions(&self) -> bool {
21315 true
21316 }
21317
21318 fn filter_completions(&self) -> bool {
21319 true
21320 }
21321}
21322
21323pub trait CodeActionProvider {
21324 fn id(&self) -> Arc<str>;
21325
21326 fn code_actions(
21327 &self,
21328 buffer: &Entity<Buffer>,
21329 range: Range<text::Anchor>,
21330 window: &mut Window,
21331 cx: &mut App,
21332 ) -> Task<Result<Vec<CodeAction>>>;
21333
21334 fn apply_code_action(
21335 &self,
21336 buffer_handle: Entity<Buffer>,
21337 action: CodeAction,
21338 excerpt_id: ExcerptId,
21339 push_to_history: bool,
21340 window: &mut Window,
21341 cx: &mut App,
21342 ) -> Task<Result<ProjectTransaction>>;
21343}
21344
21345impl CodeActionProvider for Entity<Project> {
21346 fn id(&self) -> Arc<str> {
21347 "project".into()
21348 }
21349
21350 fn code_actions(
21351 &self,
21352 buffer: &Entity<Buffer>,
21353 range: Range<text::Anchor>,
21354 _window: &mut Window,
21355 cx: &mut App,
21356 ) -> Task<Result<Vec<CodeAction>>> {
21357 self.update(cx, |project, cx| {
21358 let code_lens = project.code_lens(buffer, range.clone(), cx);
21359 let code_actions = project.code_actions(buffer, range, None, cx);
21360 cx.background_spawn(async move {
21361 let (code_lens, code_actions) = join(code_lens, code_actions).await;
21362 Ok(code_lens
21363 .context("code lens fetch")?
21364 .into_iter()
21365 .chain(code_actions.context("code action fetch")?)
21366 .collect())
21367 })
21368 })
21369 }
21370
21371 fn apply_code_action(
21372 &self,
21373 buffer_handle: Entity<Buffer>,
21374 action: CodeAction,
21375 _excerpt_id: ExcerptId,
21376 push_to_history: bool,
21377 _window: &mut Window,
21378 cx: &mut App,
21379 ) -> Task<Result<ProjectTransaction>> {
21380 self.update(cx, |project, cx| {
21381 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21382 })
21383 }
21384}
21385
21386fn snippet_completions(
21387 project: &Project,
21388 buffer: &Entity<Buffer>,
21389 buffer_position: text::Anchor,
21390 cx: &mut App,
21391) -> Task<Result<CompletionResponse>> {
21392 let languages = buffer.read(cx).languages_at(buffer_position);
21393 let snippet_store = project.snippets().read(cx);
21394
21395 let scopes: Vec<_> = languages
21396 .iter()
21397 .filter_map(|language| {
21398 let language_name = language.lsp_id();
21399 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21400
21401 if snippets.is_empty() {
21402 None
21403 } else {
21404 Some((language.default_scope(), snippets))
21405 }
21406 })
21407 .collect();
21408
21409 if scopes.is_empty() {
21410 return Task::ready(Ok(CompletionResponse {
21411 completions: vec![],
21412 is_incomplete: false,
21413 }));
21414 }
21415
21416 let snapshot = buffer.read(cx).text_snapshot();
21417 let chars: String = snapshot
21418 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
21419 .collect();
21420 let executor = cx.background_executor().clone();
21421
21422 cx.background_spawn(async move {
21423 let mut is_incomplete = false;
21424 let mut completions: Vec<Completion> = Vec::new();
21425 for (scope, snippets) in scopes.into_iter() {
21426 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
21427 let mut last_word = chars
21428 .chars()
21429 .take_while(|c| classifier.is_word(*c))
21430 .collect::<String>();
21431 last_word = last_word.chars().rev().collect();
21432
21433 if last_word.is_empty() {
21434 return Ok(CompletionResponse {
21435 completions: vec![],
21436 is_incomplete: true,
21437 });
21438 }
21439
21440 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
21441 let to_lsp = |point: &text::Anchor| {
21442 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
21443 point_to_lsp(end)
21444 };
21445 let lsp_end = to_lsp(&buffer_position);
21446
21447 let candidates = snippets
21448 .iter()
21449 .enumerate()
21450 .flat_map(|(ix, snippet)| {
21451 snippet
21452 .prefix
21453 .iter()
21454 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21455 })
21456 .collect::<Vec<StringMatchCandidate>>();
21457
21458 const MAX_RESULTS: usize = 100;
21459 let mut matches = fuzzy::match_strings(
21460 &candidates,
21461 &last_word,
21462 last_word.chars().any(|c| c.is_uppercase()),
21463 true,
21464 MAX_RESULTS,
21465 &Default::default(),
21466 executor.clone(),
21467 )
21468 .await;
21469
21470 if matches.len() >= MAX_RESULTS {
21471 is_incomplete = true;
21472 }
21473
21474 // Remove all candidates where the query's start does not match the start of any word in the candidate
21475 if let Some(query_start) = last_word.chars().next() {
21476 matches.retain(|string_match| {
21477 split_words(&string_match.string).any(|word| {
21478 // Check that the first codepoint of the word as lowercase matches the first
21479 // codepoint of the query as lowercase
21480 word.chars()
21481 .flat_map(|codepoint| codepoint.to_lowercase())
21482 .zip(query_start.to_lowercase())
21483 .all(|(word_cp, query_cp)| word_cp == query_cp)
21484 })
21485 });
21486 }
21487
21488 let matched_strings = matches
21489 .into_iter()
21490 .map(|m| m.string)
21491 .collect::<HashSet<_>>();
21492
21493 completions.extend(snippets.iter().filter_map(|snippet| {
21494 let matching_prefix = snippet
21495 .prefix
21496 .iter()
21497 .find(|prefix| matched_strings.contains(*prefix))?;
21498 let start = as_offset - last_word.len();
21499 let start = snapshot.anchor_before(start);
21500 let range = start..buffer_position;
21501 let lsp_start = to_lsp(&start);
21502 let lsp_range = lsp::Range {
21503 start: lsp_start,
21504 end: lsp_end,
21505 };
21506 Some(Completion {
21507 replace_range: range,
21508 new_text: snippet.body.clone(),
21509 source: CompletionSource::Lsp {
21510 insert_range: None,
21511 server_id: LanguageServerId(usize::MAX),
21512 resolved: true,
21513 lsp_completion: Box::new(lsp::CompletionItem {
21514 label: snippet.prefix.first().unwrap().clone(),
21515 kind: Some(CompletionItemKind::SNIPPET),
21516 label_details: snippet.description.as_ref().map(|description| {
21517 lsp::CompletionItemLabelDetails {
21518 detail: Some(description.clone()),
21519 description: None,
21520 }
21521 }),
21522 insert_text_format: Some(InsertTextFormat::SNIPPET),
21523 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21524 lsp::InsertReplaceEdit {
21525 new_text: snippet.body.clone(),
21526 insert: lsp_range,
21527 replace: lsp_range,
21528 },
21529 )),
21530 filter_text: Some(snippet.body.clone()),
21531 sort_text: Some(char::MAX.to_string()),
21532 ..lsp::CompletionItem::default()
21533 }),
21534 lsp_defaults: None,
21535 },
21536 label: CodeLabel {
21537 text: matching_prefix.clone(),
21538 runs: Vec::new(),
21539 filter_range: 0..matching_prefix.len(),
21540 },
21541 icon_path: None,
21542 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
21543 single_line: snippet.name.clone().into(),
21544 plain_text: snippet
21545 .description
21546 .clone()
21547 .map(|description| description.into()),
21548 }),
21549 insert_text_mode: None,
21550 confirm: None,
21551 })
21552 }))
21553 }
21554
21555 Ok(CompletionResponse {
21556 completions,
21557 is_incomplete,
21558 })
21559 })
21560}
21561
21562impl CompletionProvider for Entity<Project> {
21563 fn completions(
21564 &self,
21565 _excerpt_id: ExcerptId,
21566 buffer: &Entity<Buffer>,
21567 buffer_position: text::Anchor,
21568 options: CompletionContext,
21569 _window: &mut Window,
21570 cx: &mut Context<Editor>,
21571 ) -> Task<Result<Vec<CompletionResponse>>> {
21572 self.update(cx, |project, cx| {
21573 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21574 let project_completions = project.completions(buffer, buffer_position, options, cx);
21575 cx.background_spawn(async move {
21576 let mut responses = project_completions.await?;
21577 let snippets = snippets.await?;
21578 if !snippets.completions.is_empty() {
21579 responses.push(snippets);
21580 }
21581 Ok(responses)
21582 })
21583 })
21584 }
21585
21586 fn resolve_completions(
21587 &self,
21588 buffer: Entity<Buffer>,
21589 completion_indices: Vec<usize>,
21590 completions: Rc<RefCell<Box<[Completion]>>>,
21591 cx: &mut Context<Editor>,
21592 ) -> Task<Result<bool>> {
21593 self.update(cx, |project, cx| {
21594 project.lsp_store().update(cx, |lsp_store, cx| {
21595 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21596 })
21597 })
21598 }
21599
21600 fn apply_additional_edits_for_completion(
21601 &self,
21602 buffer: Entity<Buffer>,
21603 completions: Rc<RefCell<Box<[Completion]>>>,
21604 completion_index: usize,
21605 push_to_history: bool,
21606 cx: &mut Context<Editor>,
21607 ) -> Task<Result<Option<language::Transaction>>> {
21608 self.update(cx, |project, cx| {
21609 project.lsp_store().update(cx, |lsp_store, cx| {
21610 lsp_store.apply_additional_edits_for_completion(
21611 buffer,
21612 completions,
21613 completion_index,
21614 push_to_history,
21615 cx,
21616 )
21617 })
21618 })
21619 }
21620
21621 fn is_completion_trigger(
21622 &self,
21623 buffer: &Entity<Buffer>,
21624 position: language::Anchor,
21625 text: &str,
21626 trigger_in_words: bool,
21627 menu_is_open: bool,
21628 cx: &mut Context<Editor>,
21629 ) -> bool {
21630 let mut chars = text.chars();
21631 let char = if let Some(char) = chars.next() {
21632 char
21633 } else {
21634 return false;
21635 };
21636 if chars.next().is_some() {
21637 return false;
21638 }
21639
21640 let buffer = buffer.read(cx);
21641 let snapshot = buffer.snapshot();
21642 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
21643 return false;
21644 }
21645 let classifier = snapshot.char_classifier_at(position).for_completion(true);
21646 if trigger_in_words && classifier.is_word(char) {
21647 return true;
21648 }
21649
21650 buffer.completion_triggers().contains(text)
21651 }
21652}
21653
21654impl SemanticsProvider for Entity<Project> {
21655 fn hover(
21656 &self,
21657 buffer: &Entity<Buffer>,
21658 position: text::Anchor,
21659 cx: &mut App,
21660 ) -> Option<Task<Vec<project::Hover>>> {
21661 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
21662 }
21663
21664 fn document_highlights(
21665 &self,
21666 buffer: &Entity<Buffer>,
21667 position: text::Anchor,
21668 cx: &mut App,
21669 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
21670 Some(self.update(cx, |project, cx| {
21671 project.document_highlights(buffer, position, cx)
21672 }))
21673 }
21674
21675 fn definitions(
21676 &self,
21677 buffer: &Entity<Buffer>,
21678 position: text::Anchor,
21679 kind: GotoDefinitionKind,
21680 cx: &mut App,
21681 ) -> Option<Task<Result<Vec<LocationLink>>>> {
21682 Some(self.update(cx, |project, cx| match kind {
21683 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
21684 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
21685 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
21686 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
21687 }))
21688 }
21689
21690 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
21691 // TODO: make this work for remote projects
21692 self.update(cx, |project, cx| {
21693 if project
21694 .active_debug_session(cx)
21695 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
21696 {
21697 return true;
21698 }
21699
21700 buffer.update(cx, |buffer, cx| {
21701 project.any_language_server_supports_inlay_hints(buffer, cx)
21702 })
21703 })
21704 }
21705
21706 fn inline_values(
21707 &self,
21708 buffer_handle: Entity<Buffer>,
21709 range: Range<text::Anchor>,
21710 cx: &mut App,
21711 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21712 self.update(cx, |project, cx| {
21713 let (session, active_stack_frame) = project.active_debug_session(cx)?;
21714
21715 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
21716 })
21717 }
21718
21719 fn inlay_hints(
21720 &self,
21721 buffer_handle: Entity<Buffer>,
21722 range: Range<text::Anchor>,
21723 cx: &mut App,
21724 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21725 Some(self.update(cx, |project, cx| {
21726 project.inlay_hints(buffer_handle, range, cx)
21727 }))
21728 }
21729
21730 fn resolve_inlay_hint(
21731 &self,
21732 hint: InlayHint,
21733 buffer_handle: Entity<Buffer>,
21734 server_id: LanguageServerId,
21735 cx: &mut App,
21736 ) -> Option<Task<anyhow::Result<InlayHint>>> {
21737 Some(self.update(cx, |project, cx| {
21738 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
21739 }))
21740 }
21741
21742 fn range_for_rename(
21743 &self,
21744 buffer: &Entity<Buffer>,
21745 position: text::Anchor,
21746 cx: &mut App,
21747 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
21748 Some(self.update(cx, |project, cx| {
21749 let buffer = buffer.clone();
21750 let task = project.prepare_rename(buffer.clone(), position, cx);
21751 cx.spawn(async move |_, cx| {
21752 Ok(match task.await? {
21753 PrepareRenameResponse::Success(range) => Some(range),
21754 PrepareRenameResponse::InvalidPosition => None,
21755 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
21756 // Fallback on using TreeSitter info to determine identifier range
21757 buffer.read_with(cx, |buffer, _| {
21758 let snapshot = buffer.snapshot();
21759 let (range, kind) = snapshot.surrounding_word(position);
21760 if kind != Some(CharKind::Word) {
21761 return None;
21762 }
21763 Some(
21764 snapshot.anchor_before(range.start)
21765 ..snapshot.anchor_after(range.end),
21766 )
21767 })?
21768 }
21769 })
21770 })
21771 }))
21772 }
21773
21774 fn perform_rename(
21775 &self,
21776 buffer: &Entity<Buffer>,
21777 position: text::Anchor,
21778 new_name: String,
21779 cx: &mut App,
21780 ) -> Option<Task<Result<ProjectTransaction>>> {
21781 Some(self.update(cx, |project, cx| {
21782 project.perform_rename(buffer.clone(), position, new_name, cx)
21783 }))
21784 }
21785}
21786
21787fn inlay_hint_settings(
21788 location: Anchor,
21789 snapshot: &MultiBufferSnapshot,
21790 cx: &mut Context<Editor>,
21791) -> InlayHintSettings {
21792 let file = snapshot.file_at(location);
21793 let language = snapshot.language_at(location).map(|l| l.name());
21794 language_settings(language, file, cx).inlay_hints
21795}
21796
21797fn consume_contiguous_rows(
21798 contiguous_row_selections: &mut Vec<Selection<Point>>,
21799 selection: &Selection<Point>,
21800 display_map: &DisplaySnapshot,
21801 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
21802) -> (MultiBufferRow, MultiBufferRow) {
21803 contiguous_row_selections.push(selection.clone());
21804 let start_row = MultiBufferRow(selection.start.row);
21805 let mut end_row = ending_row(selection, display_map);
21806
21807 while let Some(next_selection) = selections.peek() {
21808 if next_selection.start.row <= end_row.0 {
21809 end_row = ending_row(next_selection, display_map);
21810 contiguous_row_selections.push(selections.next().unwrap().clone());
21811 } else {
21812 break;
21813 }
21814 }
21815 (start_row, end_row)
21816}
21817
21818fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
21819 if next_selection.end.column > 0 || next_selection.is_empty() {
21820 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
21821 } else {
21822 MultiBufferRow(next_selection.end.row)
21823 }
21824}
21825
21826impl EditorSnapshot {
21827 pub fn remote_selections_in_range<'a>(
21828 &'a self,
21829 range: &'a Range<Anchor>,
21830 collaboration_hub: &dyn CollaborationHub,
21831 cx: &'a App,
21832 ) -> impl 'a + Iterator<Item = RemoteSelection> {
21833 let participant_names = collaboration_hub.user_names(cx);
21834 let participant_indices = collaboration_hub.user_participant_indices(cx);
21835 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
21836 let collaborators_by_replica_id = collaborators_by_peer_id
21837 .values()
21838 .map(|collaborator| (collaborator.replica_id, collaborator))
21839 .collect::<HashMap<_, _>>();
21840 self.buffer_snapshot
21841 .selections_in_range(range, false)
21842 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
21843 if replica_id == AGENT_REPLICA_ID {
21844 Some(RemoteSelection {
21845 replica_id,
21846 selection,
21847 cursor_shape,
21848 line_mode,
21849 collaborator_id: CollaboratorId::Agent,
21850 user_name: Some("Agent".into()),
21851 color: cx.theme().players().agent(),
21852 })
21853 } else {
21854 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
21855 let participant_index = participant_indices.get(&collaborator.user_id).copied();
21856 let user_name = participant_names.get(&collaborator.user_id).cloned();
21857 Some(RemoteSelection {
21858 replica_id,
21859 selection,
21860 cursor_shape,
21861 line_mode,
21862 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
21863 user_name,
21864 color: if let Some(index) = participant_index {
21865 cx.theme().players().color_for_participant(index.0)
21866 } else {
21867 cx.theme().players().absent()
21868 },
21869 })
21870 }
21871 })
21872 }
21873
21874 pub fn hunks_for_ranges(
21875 &self,
21876 ranges: impl IntoIterator<Item = Range<Point>>,
21877 ) -> Vec<MultiBufferDiffHunk> {
21878 let mut hunks = Vec::new();
21879 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
21880 HashMap::default();
21881 for query_range in ranges {
21882 let query_rows =
21883 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
21884 for hunk in self.buffer_snapshot.diff_hunks_in_range(
21885 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
21886 ) {
21887 // Include deleted hunks that are adjacent to the query range, because
21888 // otherwise they would be missed.
21889 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
21890 if hunk.status().is_deleted() {
21891 intersects_range |= hunk.row_range.start == query_rows.end;
21892 intersects_range |= hunk.row_range.end == query_rows.start;
21893 }
21894 if intersects_range {
21895 if !processed_buffer_rows
21896 .entry(hunk.buffer_id)
21897 .or_default()
21898 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
21899 {
21900 continue;
21901 }
21902 hunks.push(hunk);
21903 }
21904 }
21905 }
21906
21907 hunks
21908 }
21909
21910 fn display_diff_hunks_for_rows<'a>(
21911 &'a self,
21912 display_rows: Range<DisplayRow>,
21913 folded_buffers: &'a HashSet<BufferId>,
21914 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
21915 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
21916 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
21917
21918 self.buffer_snapshot
21919 .diff_hunks_in_range(buffer_start..buffer_end)
21920 .filter_map(|hunk| {
21921 if folded_buffers.contains(&hunk.buffer_id) {
21922 return None;
21923 }
21924
21925 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
21926 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
21927
21928 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
21929 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
21930
21931 let display_hunk = if hunk_display_start.column() != 0 {
21932 DisplayDiffHunk::Folded {
21933 display_row: hunk_display_start.row(),
21934 }
21935 } else {
21936 let mut end_row = hunk_display_end.row();
21937 if hunk_display_end.column() > 0 {
21938 end_row.0 += 1;
21939 }
21940 let is_created_file = hunk.is_created_file();
21941 DisplayDiffHunk::Unfolded {
21942 status: hunk.status(),
21943 diff_base_byte_range: hunk.diff_base_byte_range,
21944 display_row_range: hunk_display_start.row()..end_row,
21945 multi_buffer_range: Anchor::range_in_buffer(
21946 hunk.excerpt_id,
21947 hunk.buffer_id,
21948 hunk.buffer_range,
21949 ),
21950 is_created_file,
21951 }
21952 };
21953
21954 Some(display_hunk)
21955 })
21956 }
21957
21958 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
21959 self.display_snapshot.buffer_snapshot.language_at(position)
21960 }
21961
21962 pub fn is_focused(&self) -> bool {
21963 self.is_focused
21964 }
21965
21966 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
21967 self.placeholder_text.as_ref()
21968 }
21969
21970 pub fn scroll_position(&self) -> gpui::Point<f32> {
21971 self.scroll_anchor.scroll_position(&self.display_snapshot)
21972 }
21973
21974 fn gutter_dimensions(
21975 &self,
21976 font_id: FontId,
21977 font_size: Pixels,
21978 max_line_number_width: Pixels,
21979 cx: &App,
21980 ) -> Option<GutterDimensions> {
21981 if !self.show_gutter {
21982 return None;
21983 }
21984
21985 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
21986 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
21987
21988 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
21989 matches!(
21990 ProjectSettings::get_global(cx).git.git_gutter,
21991 Some(GitGutterSetting::TrackedFiles)
21992 )
21993 });
21994 let gutter_settings = EditorSettings::get_global(cx).gutter;
21995 let show_line_numbers = self
21996 .show_line_numbers
21997 .unwrap_or(gutter_settings.line_numbers);
21998 let line_gutter_width = if show_line_numbers {
21999 // Avoid flicker-like gutter resizes when the line number gains another digit by
22000 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22001 let min_width_for_number_on_gutter =
22002 ch_advance * gutter_settings.min_line_number_digits as f32;
22003 max_line_number_width.max(min_width_for_number_on_gutter)
22004 } else {
22005 0.0.into()
22006 };
22007
22008 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22009 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22010
22011 let git_blame_entries_width =
22012 self.git_blame_gutter_max_author_length
22013 .map(|max_author_length| {
22014 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22015 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22016
22017 /// The number of characters to dedicate to gaps and margins.
22018 const SPACING_WIDTH: usize = 4;
22019
22020 let max_char_count = max_author_length.min(renderer.max_author_length())
22021 + ::git::SHORT_SHA_LENGTH
22022 + MAX_RELATIVE_TIMESTAMP.len()
22023 + SPACING_WIDTH;
22024
22025 ch_advance * max_char_count
22026 });
22027
22028 let is_singleton = self.buffer_snapshot.is_singleton();
22029
22030 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22031 left_padding += if !is_singleton {
22032 ch_width * 4.0
22033 } else if show_runnables || show_breakpoints {
22034 ch_width * 3.0
22035 } else if show_git_gutter && show_line_numbers {
22036 ch_width * 2.0
22037 } else if show_git_gutter || show_line_numbers {
22038 ch_width
22039 } else {
22040 px(0.)
22041 };
22042
22043 let shows_folds = is_singleton && gutter_settings.folds;
22044
22045 let right_padding = if shows_folds && show_line_numbers {
22046 ch_width * 4.0
22047 } else if shows_folds || (!is_singleton && show_line_numbers) {
22048 ch_width * 3.0
22049 } else if show_line_numbers {
22050 ch_width
22051 } else {
22052 px(0.)
22053 };
22054
22055 Some(GutterDimensions {
22056 left_padding,
22057 right_padding,
22058 width: line_gutter_width + left_padding + right_padding,
22059 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22060 git_blame_entries_width,
22061 })
22062 }
22063
22064 pub fn render_crease_toggle(
22065 &self,
22066 buffer_row: MultiBufferRow,
22067 row_contains_cursor: bool,
22068 editor: Entity<Editor>,
22069 window: &mut Window,
22070 cx: &mut App,
22071 ) -> Option<AnyElement> {
22072 let folded = self.is_line_folded(buffer_row);
22073 let mut is_foldable = false;
22074
22075 if let Some(crease) = self
22076 .crease_snapshot
22077 .query_row(buffer_row, &self.buffer_snapshot)
22078 {
22079 is_foldable = true;
22080 match crease {
22081 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22082 if let Some(render_toggle) = render_toggle {
22083 let toggle_callback =
22084 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22085 if folded {
22086 editor.update(cx, |editor, cx| {
22087 editor.fold_at(buffer_row, window, cx)
22088 });
22089 } else {
22090 editor.update(cx, |editor, cx| {
22091 editor.unfold_at(buffer_row, window, cx)
22092 });
22093 }
22094 });
22095 return Some((render_toggle)(
22096 buffer_row,
22097 folded,
22098 toggle_callback,
22099 window,
22100 cx,
22101 ));
22102 }
22103 }
22104 }
22105 }
22106
22107 is_foldable |= self.starts_indent(buffer_row);
22108
22109 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22110 Some(
22111 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22112 .toggle_state(folded)
22113 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22114 if folded {
22115 this.unfold_at(buffer_row, window, cx);
22116 } else {
22117 this.fold_at(buffer_row, window, cx);
22118 }
22119 }))
22120 .into_any_element(),
22121 )
22122 } else {
22123 None
22124 }
22125 }
22126
22127 pub fn render_crease_trailer(
22128 &self,
22129 buffer_row: MultiBufferRow,
22130 window: &mut Window,
22131 cx: &mut App,
22132 ) -> Option<AnyElement> {
22133 let folded = self.is_line_folded(buffer_row);
22134 if let Crease::Inline { render_trailer, .. } = self
22135 .crease_snapshot
22136 .query_row(buffer_row, &self.buffer_snapshot)?
22137 {
22138 let render_trailer = render_trailer.as_ref()?;
22139 Some(render_trailer(buffer_row, folded, window, cx))
22140 } else {
22141 None
22142 }
22143 }
22144}
22145
22146impl Deref for EditorSnapshot {
22147 type Target = DisplaySnapshot;
22148
22149 fn deref(&self) -> &Self::Target {
22150 &self.display_snapshot
22151 }
22152}
22153
22154#[derive(Clone, Debug, PartialEq, Eq)]
22155pub enum EditorEvent {
22156 InputIgnored {
22157 text: Arc<str>,
22158 },
22159 InputHandled {
22160 utf16_range_to_replace: Option<Range<isize>>,
22161 text: Arc<str>,
22162 },
22163 ExcerptsAdded {
22164 buffer: Entity<Buffer>,
22165 predecessor: ExcerptId,
22166 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22167 },
22168 ExcerptsRemoved {
22169 ids: Vec<ExcerptId>,
22170 removed_buffer_ids: Vec<BufferId>,
22171 },
22172 BufferFoldToggled {
22173 ids: Vec<ExcerptId>,
22174 folded: bool,
22175 },
22176 ExcerptsEdited {
22177 ids: Vec<ExcerptId>,
22178 },
22179 ExcerptsExpanded {
22180 ids: Vec<ExcerptId>,
22181 },
22182 BufferEdited,
22183 Edited {
22184 transaction_id: clock::Lamport,
22185 },
22186 Reparsed(BufferId),
22187 Focused,
22188 FocusedIn,
22189 Blurred,
22190 DirtyChanged,
22191 Saved,
22192 TitleChanged,
22193 DiffBaseChanged,
22194 SelectionsChanged {
22195 local: bool,
22196 },
22197 ScrollPositionChanged {
22198 local: bool,
22199 autoscroll: bool,
22200 },
22201 Closed,
22202 TransactionUndone {
22203 transaction_id: clock::Lamport,
22204 },
22205 TransactionBegun {
22206 transaction_id: clock::Lamport,
22207 },
22208 Reloaded,
22209 CursorShapeChanged,
22210 PushedToNavHistory {
22211 anchor: Anchor,
22212 is_deactivate: bool,
22213 },
22214}
22215
22216impl EventEmitter<EditorEvent> for Editor {}
22217
22218impl Focusable for Editor {
22219 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22220 self.focus_handle.clone()
22221 }
22222}
22223
22224impl Render for Editor {
22225 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22226 let settings = ThemeSettings::get_global(cx);
22227
22228 let mut text_style = match self.mode {
22229 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22230 color: cx.theme().colors().editor_foreground,
22231 font_family: settings.ui_font.family.clone(),
22232 font_features: settings.ui_font.features.clone(),
22233 font_fallbacks: settings.ui_font.fallbacks.clone(),
22234 font_size: rems(0.875).into(),
22235 font_weight: settings.ui_font.weight,
22236 line_height: relative(settings.buffer_line_height.value()),
22237 ..Default::default()
22238 },
22239 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22240 color: cx.theme().colors().editor_foreground,
22241 font_family: settings.buffer_font.family.clone(),
22242 font_features: settings.buffer_font.features.clone(),
22243 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22244 font_size: settings.buffer_font_size(cx).into(),
22245 font_weight: settings.buffer_font.weight,
22246 line_height: relative(settings.buffer_line_height.value()),
22247 ..Default::default()
22248 },
22249 };
22250 if let Some(text_style_refinement) = &self.text_style_refinement {
22251 text_style.refine(text_style_refinement)
22252 }
22253
22254 let background = match self.mode {
22255 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22256 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22257 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22258 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22259 };
22260
22261 EditorElement::new(
22262 &cx.entity(),
22263 EditorStyle {
22264 background,
22265 local_player: cx.theme().players().local(),
22266 text: text_style,
22267 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22268 syntax: cx.theme().syntax().clone(),
22269 status: cx.theme().status().clone(),
22270 inlay_hints_style: make_inlay_hints_style(cx),
22271 inline_completion_styles: make_suggestion_styles(cx),
22272 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22273 show_underlines: self.diagnostics_enabled(),
22274 },
22275 )
22276 }
22277}
22278
22279impl EntityInputHandler for Editor {
22280 fn text_for_range(
22281 &mut self,
22282 range_utf16: Range<usize>,
22283 adjusted_range: &mut Option<Range<usize>>,
22284 _: &mut Window,
22285 cx: &mut Context<Self>,
22286 ) -> Option<String> {
22287 let snapshot = self.buffer.read(cx).read(cx);
22288 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22289 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22290 if (start.0..end.0) != range_utf16 {
22291 adjusted_range.replace(start.0..end.0);
22292 }
22293 Some(snapshot.text_for_range(start..end).collect())
22294 }
22295
22296 fn selected_text_range(
22297 &mut self,
22298 ignore_disabled_input: bool,
22299 _: &mut Window,
22300 cx: &mut Context<Self>,
22301 ) -> Option<UTF16Selection> {
22302 // Prevent the IME menu from appearing when holding down an alphabetic key
22303 // while input is disabled.
22304 if !ignore_disabled_input && !self.input_enabled {
22305 return None;
22306 }
22307
22308 let selection = self.selections.newest::<OffsetUtf16>(cx);
22309 let range = selection.range();
22310
22311 Some(UTF16Selection {
22312 range: range.start.0..range.end.0,
22313 reversed: selection.reversed,
22314 })
22315 }
22316
22317 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22318 let snapshot = self.buffer.read(cx).read(cx);
22319 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22320 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22321 }
22322
22323 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22324 self.clear_highlights::<InputComposition>(cx);
22325 self.ime_transaction.take();
22326 }
22327
22328 fn replace_text_in_range(
22329 &mut self,
22330 range_utf16: Option<Range<usize>>,
22331 text: &str,
22332 window: &mut Window,
22333 cx: &mut Context<Self>,
22334 ) {
22335 if !self.input_enabled {
22336 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22337 return;
22338 }
22339
22340 self.transact(window, cx, |this, window, cx| {
22341 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22342 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22343 Some(this.selection_replacement_ranges(range_utf16, cx))
22344 } else {
22345 this.marked_text_ranges(cx)
22346 };
22347
22348 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22349 let newest_selection_id = this.selections.newest_anchor().id;
22350 this.selections
22351 .all::<OffsetUtf16>(cx)
22352 .iter()
22353 .zip(ranges_to_replace.iter())
22354 .find_map(|(selection, range)| {
22355 if selection.id == newest_selection_id {
22356 Some(
22357 (range.start.0 as isize - selection.head().0 as isize)
22358 ..(range.end.0 as isize - selection.head().0 as isize),
22359 )
22360 } else {
22361 None
22362 }
22363 })
22364 });
22365
22366 cx.emit(EditorEvent::InputHandled {
22367 utf16_range_to_replace: range_to_replace,
22368 text: text.into(),
22369 });
22370
22371 if let Some(new_selected_ranges) = new_selected_ranges {
22372 this.change_selections(None, window, cx, |selections| {
22373 selections.select_ranges(new_selected_ranges)
22374 });
22375 this.backspace(&Default::default(), window, cx);
22376 }
22377
22378 this.handle_input(text, window, cx);
22379 });
22380
22381 if let Some(transaction) = self.ime_transaction {
22382 self.buffer.update(cx, |buffer, cx| {
22383 buffer.group_until_transaction(transaction, cx);
22384 });
22385 }
22386
22387 self.unmark_text(window, cx);
22388 }
22389
22390 fn replace_and_mark_text_in_range(
22391 &mut self,
22392 range_utf16: Option<Range<usize>>,
22393 text: &str,
22394 new_selected_range_utf16: Option<Range<usize>>,
22395 window: &mut Window,
22396 cx: &mut Context<Self>,
22397 ) {
22398 if !self.input_enabled {
22399 return;
22400 }
22401
22402 let transaction = self.transact(window, cx, |this, window, cx| {
22403 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22404 let snapshot = this.buffer.read(cx).read(cx);
22405 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22406 for marked_range in &mut marked_ranges {
22407 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22408 marked_range.start.0 += relative_range_utf16.start;
22409 marked_range.start =
22410 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22411 marked_range.end =
22412 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22413 }
22414 }
22415 Some(marked_ranges)
22416 } else if let Some(range_utf16) = range_utf16 {
22417 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22418 Some(this.selection_replacement_ranges(range_utf16, cx))
22419 } else {
22420 None
22421 };
22422
22423 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22424 let newest_selection_id = this.selections.newest_anchor().id;
22425 this.selections
22426 .all::<OffsetUtf16>(cx)
22427 .iter()
22428 .zip(ranges_to_replace.iter())
22429 .find_map(|(selection, range)| {
22430 if selection.id == newest_selection_id {
22431 Some(
22432 (range.start.0 as isize - selection.head().0 as isize)
22433 ..(range.end.0 as isize - selection.head().0 as isize),
22434 )
22435 } else {
22436 None
22437 }
22438 })
22439 });
22440
22441 cx.emit(EditorEvent::InputHandled {
22442 utf16_range_to_replace: range_to_replace,
22443 text: text.into(),
22444 });
22445
22446 if let Some(ranges) = ranges_to_replace {
22447 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
22448 }
22449
22450 let marked_ranges = {
22451 let snapshot = this.buffer.read(cx).read(cx);
22452 this.selections
22453 .disjoint_anchors()
22454 .iter()
22455 .map(|selection| {
22456 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22457 })
22458 .collect::<Vec<_>>()
22459 };
22460
22461 if text.is_empty() {
22462 this.unmark_text(window, cx);
22463 } else {
22464 this.highlight_text::<InputComposition>(
22465 marked_ranges.clone(),
22466 HighlightStyle {
22467 underline: Some(UnderlineStyle {
22468 thickness: px(1.),
22469 color: None,
22470 wavy: false,
22471 }),
22472 ..Default::default()
22473 },
22474 cx,
22475 );
22476 }
22477
22478 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22479 let use_autoclose = this.use_autoclose;
22480 let use_auto_surround = this.use_auto_surround;
22481 this.set_use_autoclose(false);
22482 this.set_use_auto_surround(false);
22483 this.handle_input(text, window, cx);
22484 this.set_use_autoclose(use_autoclose);
22485 this.set_use_auto_surround(use_auto_surround);
22486
22487 if let Some(new_selected_range) = new_selected_range_utf16 {
22488 let snapshot = this.buffer.read(cx).read(cx);
22489 let new_selected_ranges = marked_ranges
22490 .into_iter()
22491 .map(|marked_range| {
22492 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22493 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22494 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22495 snapshot.clip_offset_utf16(new_start, Bias::Left)
22496 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22497 })
22498 .collect::<Vec<_>>();
22499
22500 drop(snapshot);
22501 this.change_selections(None, window, cx, |selections| {
22502 selections.select_ranges(new_selected_ranges)
22503 });
22504 }
22505 });
22506
22507 self.ime_transaction = self.ime_transaction.or(transaction);
22508 if let Some(transaction) = self.ime_transaction {
22509 self.buffer.update(cx, |buffer, cx| {
22510 buffer.group_until_transaction(transaction, cx);
22511 });
22512 }
22513
22514 if self.text_highlights::<InputComposition>(cx).is_none() {
22515 self.ime_transaction.take();
22516 }
22517 }
22518
22519 fn bounds_for_range(
22520 &mut self,
22521 range_utf16: Range<usize>,
22522 element_bounds: gpui::Bounds<Pixels>,
22523 window: &mut Window,
22524 cx: &mut Context<Self>,
22525 ) -> Option<gpui::Bounds<Pixels>> {
22526 let text_layout_details = self.text_layout_details(window);
22527 let gpui::Size {
22528 width: em_width,
22529 height: line_height,
22530 } = self.character_size(window);
22531
22532 let snapshot = self.snapshot(window, cx);
22533 let scroll_position = snapshot.scroll_position();
22534 let scroll_left = scroll_position.x * em_width;
22535
22536 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22537 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22538 + self.gutter_dimensions.width
22539 + self.gutter_dimensions.margin;
22540 let y = line_height * (start.row().as_f32() - scroll_position.y);
22541
22542 Some(Bounds {
22543 origin: element_bounds.origin + point(x, y),
22544 size: size(em_width, line_height),
22545 })
22546 }
22547
22548 fn character_index_for_point(
22549 &mut self,
22550 point: gpui::Point<Pixels>,
22551 _window: &mut Window,
22552 _cx: &mut Context<Self>,
22553 ) -> Option<usize> {
22554 let position_map = self.last_position_map.as_ref()?;
22555 if !position_map.text_hitbox.contains(&point) {
22556 return None;
22557 }
22558 let display_point = position_map.point_for_position(point).previous_valid;
22559 let anchor = position_map
22560 .snapshot
22561 .display_point_to_anchor(display_point, Bias::Left);
22562 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22563 Some(utf16_offset.0)
22564 }
22565}
22566
22567trait SelectionExt {
22568 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22569 fn spanned_rows(
22570 &self,
22571 include_end_if_at_line_start: bool,
22572 map: &DisplaySnapshot,
22573 ) -> Range<MultiBufferRow>;
22574}
22575
22576impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22577 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22578 let start = self
22579 .start
22580 .to_point(&map.buffer_snapshot)
22581 .to_display_point(map);
22582 let end = self
22583 .end
22584 .to_point(&map.buffer_snapshot)
22585 .to_display_point(map);
22586 if self.reversed {
22587 end..start
22588 } else {
22589 start..end
22590 }
22591 }
22592
22593 fn spanned_rows(
22594 &self,
22595 include_end_if_at_line_start: bool,
22596 map: &DisplaySnapshot,
22597 ) -> Range<MultiBufferRow> {
22598 let start = self.start.to_point(&map.buffer_snapshot);
22599 let mut end = self.end.to_point(&map.buffer_snapshot);
22600 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22601 end.row -= 1;
22602 }
22603
22604 let buffer_start = map.prev_line_boundary(start).0;
22605 let buffer_end = map.next_line_boundary(end).0;
22606 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22607 }
22608}
22609
22610impl<T: InvalidationRegion> InvalidationStack<T> {
22611 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22612 where
22613 S: Clone + ToOffset,
22614 {
22615 while let Some(region) = self.last() {
22616 let all_selections_inside_invalidation_ranges =
22617 if selections.len() == region.ranges().len() {
22618 selections
22619 .iter()
22620 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
22621 .all(|(selection, invalidation_range)| {
22622 let head = selection.head().to_offset(buffer);
22623 invalidation_range.start <= head && invalidation_range.end >= head
22624 })
22625 } else {
22626 false
22627 };
22628
22629 if all_selections_inside_invalidation_ranges {
22630 break;
22631 } else {
22632 self.pop();
22633 }
22634 }
22635 }
22636}
22637
22638impl<T> Default for InvalidationStack<T> {
22639 fn default() -> Self {
22640 Self(Default::default())
22641 }
22642}
22643
22644impl<T> Deref for InvalidationStack<T> {
22645 type Target = Vec<T>;
22646
22647 fn deref(&self) -> &Self::Target {
22648 &self.0
22649 }
22650}
22651
22652impl<T> DerefMut for InvalidationStack<T> {
22653 fn deref_mut(&mut self) -> &mut Self::Target {
22654 &mut self.0
22655 }
22656}
22657
22658impl InvalidationRegion for SnippetState {
22659 fn ranges(&self) -> &[Range<Anchor>] {
22660 &self.ranges[self.active_index]
22661 }
22662}
22663
22664fn inline_completion_edit_text(
22665 current_snapshot: &BufferSnapshot,
22666 edits: &[(Range<Anchor>, String)],
22667 edit_preview: &EditPreview,
22668 include_deletions: bool,
22669 cx: &App,
22670) -> HighlightedText {
22671 let edits = edits
22672 .iter()
22673 .map(|(anchor, text)| {
22674 (
22675 anchor.start.text_anchor..anchor.end.text_anchor,
22676 text.clone(),
22677 )
22678 })
22679 .collect::<Vec<_>>();
22680
22681 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
22682}
22683
22684pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
22685 match severity {
22686 lsp::DiagnosticSeverity::ERROR => colors.error,
22687 lsp::DiagnosticSeverity::WARNING => colors.warning,
22688 lsp::DiagnosticSeverity::INFORMATION => colors.info,
22689 lsp::DiagnosticSeverity::HINT => colors.info,
22690 _ => colors.ignored,
22691 }
22692}
22693
22694pub fn styled_runs_for_code_label<'a>(
22695 label: &'a CodeLabel,
22696 syntax_theme: &'a theme::SyntaxTheme,
22697) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
22698 let fade_out = HighlightStyle {
22699 fade_out: Some(0.35),
22700 ..Default::default()
22701 };
22702
22703 let mut prev_end = label.filter_range.end;
22704 label
22705 .runs
22706 .iter()
22707 .enumerate()
22708 .flat_map(move |(ix, (range, highlight_id))| {
22709 let style = if let Some(style) = highlight_id.style(syntax_theme) {
22710 style
22711 } else {
22712 return Default::default();
22713 };
22714 let mut muted_style = style;
22715 muted_style.highlight(fade_out);
22716
22717 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
22718 if range.start >= label.filter_range.end {
22719 if range.start > prev_end {
22720 runs.push((prev_end..range.start, fade_out));
22721 }
22722 runs.push((range.clone(), muted_style));
22723 } else if range.end <= label.filter_range.end {
22724 runs.push((range.clone(), style));
22725 } else {
22726 runs.push((range.start..label.filter_range.end, style));
22727 runs.push((label.filter_range.end..range.end, muted_style));
22728 }
22729 prev_end = cmp::max(prev_end, range.end);
22730
22731 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
22732 runs.push((prev_end..label.text.len(), fade_out));
22733 }
22734
22735 runs
22736 })
22737}
22738
22739pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
22740 let mut prev_index = 0;
22741 let mut prev_codepoint: Option<char> = None;
22742 text.char_indices()
22743 .chain([(text.len(), '\0')])
22744 .filter_map(move |(index, codepoint)| {
22745 let prev_codepoint = prev_codepoint.replace(codepoint)?;
22746 let is_boundary = index == text.len()
22747 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
22748 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
22749 if is_boundary {
22750 let chunk = &text[prev_index..index];
22751 prev_index = index;
22752 Some(chunk)
22753 } else {
22754 None
22755 }
22756 })
22757}
22758
22759pub trait RangeToAnchorExt: Sized {
22760 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
22761
22762 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
22763 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
22764 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
22765 }
22766}
22767
22768impl<T: ToOffset> RangeToAnchorExt for Range<T> {
22769 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
22770 let start_offset = self.start.to_offset(snapshot);
22771 let end_offset = self.end.to_offset(snapshot);
22772 if start_offset == end_offset {
22773 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
22774 } else {
22775 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
22776 }
22777 }
22778}
22779
22780pub trait RowExt {
22781 fn as_f32(&self) -> f32;
22782
22783 fn next_row(&self) -> Self;
22784
22785 fn previous_row(&self) -> Self;
22786
22787 fn minus(&self, other: Self) -> u32;
22788}
22789
22790impl RowExt for DisplayRow {
22791 fn as_f32(&self) -> f32 {
22792 self.0 as f32
22793 }
22794
22795 fn next_row(&self) -> Self {
22796 Self(self.0 + 1)
22797 }
22798
22799 fn previous_row(&self) -> Self {
22800 Self(self.0.saturating_sub(1))
22801 }
22802
22803 fn minus(&self, other: Self) -> u32 {
22804 self.0 - other.0
22805 }
22806}
22807
22808impl RowExt for MultiBufferRow {
22809 fn as_f32(&self) -> f32 {
22810 self.0 as f32
22811 }
22812
22813 fn next_row(&self) -> Self {
22814 Self(self.0 + 1)
22815 }
22816
22817 fn previous_row(&self) -> Self {
22818 Self(self.0.saturating_sub(1))
22819 }
22820
22821 fn minus(&self, other: Self) -> u32 {
22822 self.0 - other.0
22823 }
22824}
22825
22826trait RowRangeExt {
22827 type Row;
22828
22829 fn len(&self) -> usize;
22830
22831 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
22832}
22833
22834impl RowRangeExt for Range<MultiBufferRow> {
22835 type Row = MultiBufferRow;
22836
22837 fn len(&self) -> usize {
22838 (self.end.0 - self.start.0) as usize
22839 }
22840
22841 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
22842 (self.start.0..self.end.0).map(MultiBufferRow)
22843 }
22844}
22845
22846impl RowRangeExt for Range<DisplayRow> {
22847 type Row = DisplayRow;
22848
22849 fn len(&self) -> usize {
22850 (self.end.0 - self.start.0) as usize
22851 }
22852
22853 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
22854 (self.start.0..self.end.0).map(DisplayRow)
22855 }
22856}
22857
22858/// If select range has more than one line, we
22859/// just point the cursor to range.start.
22860fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
22861 if range.start.row == range.end.row {
22862 range
22863 } else {
22864 range.start..range.start
22865 }
22866}
22867pub struct KillRing(ClipboardItem);
22868impl Global for KillRing {}
22869
22870const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
22871
22872enum BreakpointPromptEditAction {
22873 Log,
22874 Condition,
22875 HitCondition,
22876}
22877
22878struct BreakpointPromptEditor {
22879 pub(crate) prompt: Entity<Editor>,
22880 editor: WeakEntity<Editor>,
22881 breakpoint_anchor: Anchor,
22882 breakpoint: Breakpoint,
22883 edit_action: BreakpointPromptEditAction,
22884 block_ids: HashSet<CustomBlockId>,
22885 editor_margins: Arc<Mutex<EditorMargins>>,
22886 _subscriptions: Vec<Subscription>,
22887}
22888
22889impl BreakpointPromptEditor {
22890 const MAX_LINES: u8 = 4;
22891
22892 fn new(
22893 editor: WeakEntity<Editor>,
22894 breakpoint_anchor: Anchor,
22895 breakpoint: Breakpoint,
22896 edit_action: BreakpointPromptEditAction,
22897 window: &mut Window,
22898 cx: &mut Context<Self>,
22899 ) -> Self {
22900 let base_text = match edit_action {
22901 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
22902 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
22903 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
22904 }
22905 .map(|msg| msg.to_string())
22906 .unwrap_or_default();
22907
22908 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
22909 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
22910
22911 let prompt = cx.new(|cx| {
22912 let mut prompt = Editor::new(
22913 EditorMode::AutoHeight {
22914 min_lines: 1,
22915 max_lines: Some(Self::MAX_LINES as usize),
22916 },
22917 buffer,
22918 None,
22919 window,
22920 cx,
22921 );
22922 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
22923 prompt.set_show_cursor_when_unfocused(false, cx);
22924 prompt.set_placeholder_text(
22925 match edit_action {
22926 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
22927 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
22928 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
22929 },
22930 cx,
22931 );
22932
22933 prompt
22934 });
22935
22936 Self {
22937 prompt,
22938 editor,
22939 breakpoint_anchor,
22940 breakpoint,
22941 edit_action,
22942 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
22943 block_ids: Default::default(),
22944 _subscriptions: vec![],
22945 }
22946 }
22947
22948 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
22949 self.block_ids.extend(block_ids)
22950 }
22951
22952 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
22953 if let Some(editor) = self.editor.upgrade() {
22954 let message = self
22955 .prompt
22956 .read(cx)
22957 .buffer
22958 .read(cx)
22959 .as_singleton()
22960 .expect("A multi buffer in breakpoint prompt isn't possible")
22961 .read(cx)
22962 .as_rope()
22963 .to_string();
22964
22965 editor.update(cx, |editor, cx| {
22966 editor.edit_breakpoint_at_anchor(
22967 self.breakpoint_anchor,
22968 self.breakpoint.clone(),
22969 match self.edit_action {
22970 BreakpointPromptEditAction::Log => {
22971 BreakpointEditAction::EditLogMessage(message.into())
22972 }
22973 BreakpointPromptEditAction::Condition => {
22974 BreakpointEditAction::EditCondition(message.into())
22975 }
22976 BreakpointPromptEditAction::HitCondition => {
22977 BreakpointEditAction::EditHitCondition(message.into())
22978 }
22979 },
22980 cx,
22981 );
22982
22983 editor.remove_blocks(self.block_ids.clone(), None, cx);
22984 cx.focus_self(window);
22985 });
22986 }
22987 }
22988
22989 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
22990 self.editor
22991 .update(cx, |editor, cx| {
22992 editor.remove_blocks(self.block_ids.clone(), None, cx);
22993 window.focus(&editor.focus_handle);
22994 })
22995 .log_err();
22996 }
22997
22998 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
22999 let settings = ThemeSettings::get_global(cx);
23000 let text_style = TextStyle {
23001 color: if self.prompt.read(cx).read_only(cx) {
23002 cx.theme().colors().text_disabled
23003 } else {
23004 cx.theme().colors().text
23005 },
23006 font_family: settings.buffer_font.family.clone(),
23007 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23008 font_size: settings.buffer_font_size(cx).into(),
23009 font_weight: settings.buffer_font.weight,
23010 line_height: relative(settings.buffer_line_height.value()),
23011 ..Default::default()
23012 };
23013 EditorElement::new(
23014 &self.prompt,
23015 EditorStyle {
23016 background: cx.theme().colors().editor_background,
23017 local_player: cx.theme().players().local(),
23018 text: text_style,
23019 ..Default::default()
23020 },
23021 )
23022 }
23023}
23024
23025impl Render for BreakpointPromptEditor {
23026 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23027 let editor_margins = *self.editor_margins.lock();
23028 let gutter_dimensions = editor_margins.gutter;
23029 h_flex()
23030 .key_context("Editor")
23031 .bg(cx.theme().colors().editor_background)
23032 .border_y_1()
23033 .border_color(cx.theme().status().info_border)
23034 .size_full()
23035 .py(window.line_height() / 2.5)
23036 .on_action(cx.listener(Self::confirm))
23037 .on_action(cx.listener(Self::cancel))
23038 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23039 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23040 }
23041}
23042
23043impl Focusable for BreakpointPromptEditor {
23044 fn focus_handle(&self, cx: &App) -> FocusHandle {
23045 self.prompt.focus_handle(cx)
23046 }
23047}
23048
23049fn all_edits_insertions_or_deletions(
23050 edits: &Vec<(Range<Anchor>, String)>,
23051 snapshot: &MultiBufferSnapshot,
23052) -> bool {
23053 let mut all_insertions = true;
23054 let mut all_deletions = true;
23055
23056 for (range, new_text) in edits.iter() {
23057 let range_is_empty = range.to_offset(&snapshot).is_empty();
23058 let text_is_empty = new_text.is_empty();
23059
23060 if range_is_empty != text_is_empty {
23061 if range_is_empty {
23062 all_deletions = false;
23063 } else {
23064 all_insertions = false;
23065 }
23066 } else {
23067 return false;
23068 }
23069
23070 if !all_insertions && !all_deletions {
23071 return false;
23072 }
23073 }
23074 all_insertions || all_deletions
23075}
23076
23077struct MissingEditPredictionKeybindingTooltip;
23078
23079impl Render for MissingEditPredictionKeybindingTooltip {
23080 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23081 ui::tooltip_container(window, cx, |container, _, cx| {
23082 container
23083 .flex_shrink_0()
23084 .max_w_80()
23085 .min_h(rems_from_px(124.))
23086 .justify_between()
23087 .child(
23088 v_flex()
23089 .flex_1()
23090 .text_ui_sm(cx)
23091 .child(Label::new("Conflict with Accept Keybinding"))
23092 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23093 )
23094 .child(
23095 h_flex()
23096 .pb_1()
23097 .gap_1()
23098 .items_end()
23099 .w_full()
23100 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23101 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23102 }))
23103 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23104 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23105 })),
23106 )
23107 })
23108 }
23109}
23110
23111#[derive(Debug, Clone, Copy, PartialEq)]
23112pub struct LineHighlight {
23113 pub background: Background,
23114 pub border: Option<gpui::Hsla>,
23115 pub include_gutter: bool,
23116 pub type_id: Option<TypeId>,
23117}
23118
23119struct LineManipulationResult {
23120 pub new_text: String,
23121 pub line_count_before: usize,
23122 pub line_count_after: usize,
23123}
23124
23125fn render_diff_hunk_controls(
23126 row: u32,
23127 status: &DiffHunkStatus,
23128 hunk_range: Range<Anchor>,
23129 is_created_file: bool,
23130 line_height: Pixels,
23131 editor: &Entity<Editor>,
23132 _window: &mut Window,
23133 cx: &mut App,
23134) -> AnyElement {
23135 h_flex()
23136 .h(line_height)
23137 .mr_1()
23138 .gap_1()
23139 .px_0p5()
23140 .pb_1()
23141 .border_x_1()
23142 .border_b_1()
23143 .border_color(cx.theme().colors().border_variant)
23144 .rounded_b_lg()
23145 .bg(cx.theme().colors().editor_background)
23146 .gap_1()
23147 .block_mouse_except_scroll()
23148 .shadow_md()
23149 .child(if status.has_secondary_hunk() {
23150 Button::new(("stage", row as u64), "Stage")
23151 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23152 .tooltip({
23153 let focus_handle = editor.focus_handle(cx);
23154 move |window, cx| {
23155 Tooltip::for_action_in(
23156 "Stage Hunk",
23157 &::git::ToggleStaged,
23158 &focus_handle,
23159 window,
23160 cx,
23161 )
23162 }
23163 })
23164 .on_click({
23165 let editor = editor.clone();
23166 move |_event, _window, cx| {
23167 editor.update(cx, |editor, cx| {
23168 editor.stage_or_unstage_diff_hunks(
23169 true,
23170 vec![hunk_range.start..hunk_range.start],
23171 cx,
23172 );
23173 });
23174 }
23175 })
23176 } else {
23177 Button::new(("unstage", row as u64), "Unstage")
23178 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23179 .tooltip({
23180 let focus_handle = editor.focus_handle(cx);
23181 move |window, cx| {
23182 Tooltip::for_action_in(
23183 "Unstage Hunk",
23184 &::git::ToggleStaged,
23185 &focus_handle,
23186 window,
23187 cx,
23188 )
23189 }
23190 })
23191 .on_click({
23192 let editor = editor.clone();
23193 move |_event, _window, cx| {
23194 editor.update(cx, |editor, cx| {
23195 editor.stage_or_unstage_diff_hunks(
23196 false,
23197 vec![hunk_range.start..hunk_range.start],
23198 cx,
23199 );
23200 });
23201 }
23202 })
23203 })
23204 .child(
23205 Button::new(("restore", row as u64), "Restore")
23206 .tooltip({
23207 let focus_handle = editor.focus_handle(cx);
23208 move |window, cx| {
23209 Tooltip::for_action_in(
23210 "Restore Hunk",
23211 &::git::Restore,
23212 &focus_handle,
23213 window,
23214 cx,
23215 )
23216 }
23217 })
23218 .on_click({
23219 let editor = editor.clone();
23220 move |_event, window, cx| {
23221 editor.update(cx, |editor, cx| {
23222 let snapshot = editor.snapshot(window, cx);
23223 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23224 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23225 });
23226 }
23227 })
23228 .disabled(is_created_file),
23229 )
23230 .when(
23231 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23232 |el| {
23233 el.child(
23234 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23235 .shape(IconButtonShape::Square)
23236 .icon_size(IconSize::Small)
23237 // .disabled(!has_multiple_hunks)
23238 .tooltip({
23239 let focus_handle = editor.focus_handle(cx);
23240 move |window, cx| {
23241 Tooltip::for_action_in(
23242 "Next Hunk",
23243 &GoToHunk,
23244 &focus_handle,
23245 window,
23246 cx,
23247 )
23248 }
23249 })
23250 .on_click({
23251 let editor = editor.clone();
23252 move |_event, window, cx| {
23253 editor.update(cx, |editor, cx| {
23254 let snapshot = editor.snapshot(window, cx);
23255 let position =
23256 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23257 editor.go_to_hunk_before_or_after_position(
23258 &snapshot,
23259 position,
23260 Direction::Next,
23261 window,
23262 cx,
23263 );
23264 editor.expand_selected_diff_hunks(cx);
23265 });
23266 }
23267 }),
23268 )
23269 .child(
23270 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23271 .shape(IconButtonShape::Square)
23272 .icon_size(IconSize::Small)
23273 // .disabled(!has_multiple_hunks)
23274 .tooltip({
23275 let focus_handle = editor.focus_handle(cx);
23276 move |window, cx| {
23277 Tooltip::for_action_in(
23278 "Previous Hunk",
23279 &GoToPreviousHunk,
23280 &focus_handle,
23281 window,
23282 cx,
23283 )
23284 }
23285 })
23286 .on_click({
23287 let editor = editor.clone();
23288 move |_event, window, cx| {
23289 editor.update(cx, |editor, cx| {
23290 let snapshot = editor.snapshot(window, cx);
23291 let point =
23292 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23293 editor.go_to_hunk_before_or_after_position(
23294 &snapshot,
23295 point,
23296 Direction::Prev,
23297 window,
23298 cx,
23299 );
23300 editor.expand_selected_diff_hunks(cx);
23301 });
23302 }
23303 }),
23304 )
23305 },
23306 )
23307 .into_any_element()
23308}