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 *editor.context_menu.borrow_mut() =
5980 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5981 buffer,
5982 actions: CodeActionContents::new(
5983 resolved_tasks,
5984 code_actions,
5985 debug_scenarios,
5986 task_context.unwrap_or_default(),
5987 ),
5988 selected_item: Default::default(),
5989 scroll_handle: UniformListScrollHandle::default(),
5990 deployed_from,
5991 }));
5992 cx.notify();
5993 if spawn_straight_away {
5994 if let Some(task) = editor.confirm_code_action(
5995 &ConfirmCodeAction { item_ix: Some(0) },
5996 window,
5997 cx,
5998 ) {
5999 return task;
6000 }
6001 }
6002
6003 Task::ready(Ok(()))
6004 })
6005 })
6006 .detach_and_log_err(cx);
6007 }
6008
6009 fn debug_scenarios(
6010 &mut self,
6011 resolved_tasks: &Option<ResolvedTasks>,
6012 buffer: &Entity<Buffer>,
6013 cx: &mut App,
6014 ) -> Task<Vec<task::DebugScenario>> {
6015 maybe!({
6016 let project = self.project.as_ref()?;
6017 let dap_store = project.read(cx).dap_store();
6018 let mut scenarios = vec![];
6019 let resolved_tasks = resolved_tasks.as_ref()?;
6020 let buffer = buffer.read(cx);
6021 let language = buffer.language()?;
6022 let file = buffer.file();
6023 let debug_adapter = language_settings(language.name().into(), file, cx)
6024 .debuggers
6025 .first()
6026 .map(SharedString::from)
6027 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6028
6029 dap_store.update(cx, |dap_store, cx| {
6030 for (_, task) in &resolved_tasks.templates {
6031 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6032 task.original_task().clone(),
6033 debug_adapter.clone().into(),
6034 task.display_label().to_owned().into(),
6035 cx,
6036 );
6037 scenarios.push(maybe_scenario);
6038 }
6039 });
6040 Some(cx.background_spawn(async move {
6041 let scenarios = futures::future::join_all(scenarios)
6042 .await
6043 .into_iter()
6044 .flatten()
6045 .collect::<Vec<_>>();
6046 scenarios
6047 }))
6048 })
6049 .unwrap_or_else(|| Task::ready(vec![]))
6050 }
6051
6052 fn code_actions(
6053 &mut self,
6054 buffer_row: u32,
6055 window: &mut Window,
6056 cx: &mut Context<Self>,
6057 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6058 let mut task = self.code_actions_task.take();
6059 cx.spawn_in(window, async move |editor, cx| {
6060 while let Some(prev_task) = task {
6061 prev_task.await.log_err();
6062 task = editor
6063 .update(cx, |this, _| this.code_actions_task.take())
6064 .ok()?;
6065 }
6066
6067 editor
6068 .update(cx, |editor, cx| {
6069 editor
6070 .available_code_actions
6071 .clone()
6072 .and_then(|(location, code_actions)| {
6073 let snapshot = location.buffer.read(cx).snapshot();
6074 let point_range = location.range.to_point(&snapshot);
6075 let point_range = point_range.start.row..=point_range.end.row;
6076 if point_range.contains(&buffer_row) {
6077 Some(code_actions)
6078 } else {
6079 None
6080 }
6081 })
6082 })
6083 .ok()
6084 .flatten()
6085 })
6086 }
6087
6088 pub fn confirm_code_action(
6089 &mut self,
6090 action: &ConfirmCodeAction,
6091 window: &mut Window,
6092 cx: &mut Context<Self>,
6093 ) -> Option<Task<Result<()>>> {
6094 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6095
6096 let actions_menu =
6097 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6098 menu
6099 } else {
6100 return None;
6101 };
6102
6103 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6104 let action = actions_menu.actions.get(action_ix)?;
6105 let title = action.label();
6106 let buffer = actions_menu.buffer;
6107 let workspace = self.workspace()?;
6108
6109 match action {
6110 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6111 workspace.update(cx, |workspace, cx| {
6112 workspace.schedule_resolved_task(
6113 task_source_kind,
6114 resolved_task,
6115 false,
6116 window,
6117 cx,
6118 );
6119
6120 Some(Task::ready(Ok(())))
6121 })
6122 }
6123 CodeActionsItem::CodeAction {
6124 excerpt_id,
6125 action,
6126 provider,
6127 } => {
6128 let apply_code_action =
6129 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6130 let workspace = workspace.downgrade();
6131 Some(cx.spawn_in(window, async move |editor, cx| {
6132 let project_transaction = apply_code_action.await?;
6133 Self::open_project_transaction(
6134 &editor,
6135 workspace,
6136 project_transaction,
6137 title,
6138 cx,
6139 )
6140 .await
6141 }))
6142 }
6143 CodeActionsItem::DebugScenario(scenario) => {
6144 let context = actions_menu.actions.context.clone();
6145
6146 workspace.update(cx, |workspace, cx| {
6147 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6148 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
6149 });
6150 Some(Task::ready(Ok(())))
6151 }
6152 }
6153 }
6154
6155 pub async fn open_project_transaction(
6156 this: &WeakEntity<Editor>,
6157 workspace: WeakEntity<Workspace>,
6158 transaction: ProjectTransaction,
6159 title: String,
6160 cx: &mut AsyncWindowContext,
6161 ) -> Result<()> {
6162 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6163 cx.update(|_, cx| {
6164 entries.sort_unstable_by_key(|(buffer, _)| {
6165 buffer.read(cx).file().map(|f| f.path().clone())
6166 });
6167 })?;
6168
6169 // If the project transaction's edits are all contained within this editor, then
6170 // avoid opening a new editor to display them.
6171
6172 if let Some((buffer, transaction)) = entries.first() {
6173 if entries.len() == 1 {
6174 let excerpt = this.update(cx, |editor, cx| {
6175 editor
6176 .buffer()
6177 .read(cx)
6178 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6179 })?;
6180 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6181 if excerpted_buffer == *buffer {
6182 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6183 let excerpt_range = excerpt_range.to_offset(buffer);
6184 buffer
6185 .edited_ranges_for_transaction::<usize>(transaction)
6186 .all(|range| {
6187 excerpt_range.start <= range.start
6188 && excerpt_range.end >= range.end
6189 })
6190 })?;
6191
6192 if all_edits_within_excerpt {
6193 return Ok(());
6194 }
6195 }
6196 }
6197 }
6198 } else {
6199 return Ok(());
6200 }
6201
6202 let mut ranges_to_highlight = Vec::new();
6203 let excerpt_buffer = cx.new(|cx| {
6204 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6205 for (buffer_handle, transaction) in &entries {
6206 let edited_ranges = buffer_handle
6207 .read(cx)
6208 .edited_ranges_for_transaction::<Point>(transaction)
6209 .collect::<Vec<_>>();
6210 let (ranges, _) = multibuffer.set_excerpts_for_path(
6211 PathKey::for_buffer(buffer_handle, cx),
6212 buffer_handle.clone(),
6213 edited_ranges,
6214 DEFAULT_MULTIBUFFER_CONTEXT,
6215 cx,
6216 );
6217
6218 ranges_to_highlight.extend(ranges);
6219 }
6220 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6221 multibuffer
6222 })?;
6223
6224 workspace.update_in(cx, |workspace, window, cx| {
6225 let project = workspace.project().clone();
6226 let editor =
6227 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6228 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6229 editor.update(cx, |editor, cx| {
6230 editor.highlight_background::<Self>(
6231 &ranges_to_highlight,
6232 |theme| theme.colors().editor_highlighted_line_background,
6233 cx,
6234 );
6235 });
6236 })?;
6237
6238 Ok(())
6239 }
6240
6241 pub fn clear_code_action_providers(&mut self) {
6242 self.code_action_providers.clear();
6243 self.available_code_actions.take();
6244 }
6245
6246 pub fn add_code_action_provider(
6247 &mut self,
6248 provider: Rc<dyn CodeActionProvider>,
6249 window: &mut Window,
6250 cx: &mut Context<Self>,
6251 ) {
6252 if self
6253 .code_action_providers
6254 .iter()
6255 .any(|existing_provider| existing_provider.id() == provider.id())
6256 {
6257 return;
6258 }
6259
6260 self.code_action_providers.push(provider);
6261 self.refresh_code_actions(window, cx);
6262 }
6263
6264 pub fn remove_code_action_provider(
6265 &mut self,
6266 id: Arc<str>,
6267 window: &mut Window,
6268 cx: &mut Context<Self>,
6269 ) {
6270 self.code_action_providers
6271 .retain(|provider| provider.id() != id);
6272 self.refresh_code_actions(window, cx);
6273 }
6274
6275 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6276 !self.code_action_providers.is_empty()
6277 && EditorSettings::get_global(cx).toolbar.code_actions
6278 }
6279
6280 pub fn has_available_code_actions(&self) -> bool {
6281 self.available_code_actions
6282 .as_ref()
6283 .is_some_and(|(_, actions)| !actions.is_empty())
6284 }
6285
6286 fn render_inline_code_actions(
6287 &self,
6288 icon_size: ui::IconSize,
6289 display_row: DisplayRow,
6290 is_active: bool,
6291 cx: &mut Context<Self>,
6292 ) -> AnyElement {
6293 let show_tooltip = !self.context_menu_visible();
6294 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6295 .icon_size(icon_size)
6296 .shape(ui::IconButtonShape::Square)
6297 .style(ButtonStyle::Transparent)
6298 .icon_color(ui::Color::Hidden)
6299 .toggle_state(is_active)
6300 .when(show_tooltip, |this| {
6301 this.tooltip({
6302 let focus_handle = self.focus_handle.clone();
6303 move |window, cx| {
6304 Tooltip::for_action_in(
6305 "Toggle Code Actions",
6306 &ToggleCodeActions {
6307 deployed_from: None,
6308 quick_launch: false,
6309 },
6310 &focus_handle,
6311 window,
6312 cx,
6313 )
6314 }
6315 })
6316 })
6317 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6318 window.focus(&editor.focus_handle(cx));
6319 editor.toggle_code_actions(
6320 &crate::actions::ToggleCodeActions {
6321 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6322 display_row,
6323 )),
6324 quick_launch: false,
6325 },
6326 window,
6327 cx,
6328 );
6329 }))
6330 .into_any_element()
6331 }
6332
6333 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6334 &self.context_menu
6335 }
6336
6337 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6338 let newest_selection = self.selections.newest_anchor().clone();
6339 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6340 let buffer = self.buffer.read(cx);
6341 if newest_selection.head().diff_base_anchor.is_some() {
6342 return None;
6343 }
6344 let (start_buffer, start) =
6345 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6346 let (end_buffer, end) =
6347 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6348 if start_buffer != end_buffer {
6349 return None;
6350 }
6351
6352 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6353 cx.background_executor()
6354 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6355 .await;
6356
6357 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6358 let providers = this.code_action_providers.clone();
6359 let tasks = this
6360 .code_action_providers
6361 .iter()
6362 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6363 .collect::<Vec<_>>();
6364 (providers, tasks)
6365 })?;
6366
6367 let mut actions = Vec::new();
6368 for (provider, provider_actions) in
6369 providers.into_iter().zip(future::join_all(tasks).await)
6370 {
6371 if let Some(provider_actions) = provider_actions.log_err() {
6372 actions.extend(provider_actions.into_iter().map(|action| {
6373 AvailableCodeAction {
6374 excerpt_id: newest_selection.start.excerpt_id,
6375 action,
6376 provider: provider.clone(),
6377 }
6378 }));
6379 }
6380 }
6381
6382 this.update(cx, |this, cx| {
6383 this.available_code_actions = if actions.is_empty() {
6384 None
6385 } else {
6386 Some((
6387 Location {
6388 buffer: start_buffer,
6389 range: start..end,
6390 },
6391 actions.into(),
6392 ))
6393 };
6394 cx.notify();
6395 })
6396 }));
6397 None
6398 }
6399
6400 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6401 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6402 self.show_git_blame_inline = false;
6403
6404 self.show_git_blame_inline_delay_task =
6405 Some(cx.spawn_in(window, async move |this, cx| {
6406 cx.background_executor().timer(delay).await;
6407
6408 this.update(cx, |this, cx| {
6409 this.show_git_blame_inline = true;
6410 cx.notify();
6411 })
6412 .log_err();
6413 }));
6414 }
6415 }
6416
6417 fn show_blame_popover(
6418 &mut self,
6419 blame_entry: &BlameEntry,
6420 position: gpui::Point<Pixels>,
6421 cx: &mut Context<Self>,
6422 ) {
6423 if let Some(state) = &mut self.inline_blame_popover {
6424 state.hide_task.take();
6425 } else {
6426 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6427 let blame_entry = blame_entry.clone();
6428 let show_task = cx.spawn(async move |editor, cx| {
6429 cx.background_executor()
6430 .timer(std::time::Duration::from_millis(delay))
6431 .await;
6432 editor
6433 .update(cx, |editor, cx| {
6434 editor.inline_blame_popover_show_task.take();
6435 let Some(blame) = editor.blame.as_ref() else {
6436 return;
6437 };
6438 let blame = blame.read(cx);
6439 let details = blame.details_for_entry(&blame_entry);
6440 let markdown = cx.new(|cx| {
6441 Markdown::new(
6442 details
6443 .as_ref()
6444 .map(|message| message.message.clone())
6445 .unwrap_or_default(),
6446 None,
6447 None,
6448 cx,
6449 )
6450 });
6451 editor.inline_blame_popover = Some(InlineBlamePopover {
6452 position,
6453 hide_task: None,
6454 popover_bounds: None,
6455 popover_state: InlineBlamePopoverState {
6456 scroll_handle: ScrollHandle::new(),
6457 commit_message: details,
6458 markdown,
6459 },
6460 });
6461 cx.notify();
6462 })
6463 .ok();
6464 });
6465 self.inline_blame_popover_show_task = Some(show_task);
6466 }
6467 }
6468
6469 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6470 self.inline_blame_popover_show_task.take();
6471 if let Some(state) = &mut self.inline_blame_popover {
6472 let hide_task = cx.spawn(async move |editor, cx| {
6473 cx.background_executor()
6474 .timer(std::time::Duration::from_millis(100))
6475 .await;
6476 editor
6477 .update(cx, |editor, cx| {
6478 editor.inline_blame_popover.take();
6479 cx.notify();
6480 })
6481 .ok();
6482 });
6483 state.hide_task = Some(hide_task);
6484 }
6485 }
6486
6487 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6488 if self.pending_rename.is_some() {
6489 return None;
6490 }
6491
6492 let provider = self.semantics_provider.clone()?;
6493 let buffer = self.buffer.read(cx);
6494 let newest_selection = self.selections.newest_anchor().clone();
6495 let cursor_position = newest_selection.head();
6496 let (cursor_buffer, cursor_buffer_position) =
6497 buffer.text_anchor_for_position(cursor_position, cx)?;
6498 let (tail_buffer, tail_buffer_position) =
6499 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6500 if cursor_buffer != tail_buffer {
6501 return None;
6502 }
6503
6504 let snapshot = cursor_buffer.read(cx).snapshot();
6505 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6506 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6507 if start_word_range != end_word_range {
6508 self.document_highlights_task.take();
6509 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6510 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6511 return None;
6512 }
6513
6514 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6515 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6516 cx.background_executor()
6517 .timer(Duration::from_millis(debounce))
6518 .await;
6519
6520 let highlights = if let Some(highlights) = cx
6521 .update(|cx| {
6522 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6523 })
6524 .ok()
6525 .flatten()
6526 {
6527 highlights.await.log_err()
6528 } else {
6529 None
6530 };
6531
6532 if let Some(highlights) = highlights {
6533 this.update(cx, |this, cx| {
6534 if this.pending_rename.is_some() {
6535 return;
6536 }
6537
6538 let buffer_id = cursor_position.buffer_id;
6539 let buffer = this.buffer.read(cx);
6540 if !buffer
6541 .text_anchor_for_position(cursor_position, cx)
6542 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6543 {
6544 return;
6545 }
6546
6547 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6548 let mut write_ranges = Vec::new();
6549 let mut read_ranges = Vec::new();
6550 for highlight in highlights {
6551 for (excerpt_id, excerpt_range) in
6552 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6553 {
6554 let start = highlight
6555 .range
6556 .start
6557 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6558 let end = highlight
6559 .range
6560 .end
6561 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6562 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6563 continue;
6564 }
6565
6566 let range = Anchor {
6567 buffer_id,
6568 excerpt_id,
6569 text_anchor: start,
6570 diff_base_anchor: None,
6571 }..Anchor {
6572 buffer_id,
6573 excerpt_id,
6574 text_anchor: end,
6575 diff_base_anchor: None,
6576 };
6577 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6578 write_ranges.push(range);
6579 } else {
6580 read_ranges.push(range);
6581 }
6582 }
6583 }
6584
6585 this.highlight_background::<DocumentHighlightRead>(
6586 &read_ranges,
6587 |theme| theme.colors().editor_document_highlight_read_background,
6588 cx,
6589 );
6590 this.highlight_background::<DocumentHighlightWrite>(
6591 &write_ranges,
6592 |theme| theme.colors().editor_document_highlight_write_background,
6593 cx,
6594 );
6595 cx.notify();
6596 })
6597 .log_err();
6598 }
6599 }));
6600 None
6601 }
6602
6603 fn prepare_highlight_query_from_selection(
6604 &mut self,
6605 cx: &mut Context<Editor>,
6606 ) -> Option<(String, Range<Anchor>)> {
6607 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6608 return None;
6609 }
6610 if !EditorSettings::get_global(cx).selection_highlight {
6611 return None;
6612 }
6613 if self.selections.count() != 1 || self.selections.line_mode {
6614 return None;
6615 }
6616 let selection = self.selections.newest::<Point>(cx);
6617 if selection.is_empty() || selection.start.row != selection.end.row {
6618 return None;
6619 }
6620 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6621 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6622 let query = multi_buffer_snapshot
6623 .text_for_range(selection_anchor_range.clone())
6624 .collect::<String>();
6625 if query.trim().is_empty() {
6626 return None;
6627 }
6628 Some((query, selection_anchor_range))
6629 }
6630
6631 fn update_selection_occurrence_highlights(
6632 &mut self,
6633 query_text: String,
6634 query_range: Range<Anchor>,
6635 multi_buffer_range_to_query: Range<Point>,
6636 use_debounce: bool,
6637 window: &mut Window,
6638 cx: &mut Context<Editor>,
6639 ) -> Task<()> {
6640 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6641 cx.spawn_in(window, async move |editor, cx| {
6642 if use_debounce {
6643 cx.background_executor()
6644 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6645 .await;
6646 }
6647 let match_task = cx.background_spawn(async move {
6648 let buffer_ranges = multi_buffer_snapshot
6649 .range_to_buffer_ranges(multi_buffer_range_to_query)
6650 .into_iter()
6651 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6652 let mut match_ranges = Vec::new();
6653 let Ok(regex) = project::search::SearchQuery::text(
6654 query_text.clone(),
6655 false,
6656 false,
6657 false,
6658 Default::default(),
6659 Default::default(),
6660 false,
6661 None,
6662 ) else {
6663 return Vec::default();
6664 };
6665 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6666 match_ranges.extend(
6667 regex
6668 .search(&buffer_snapshot, Some(search_range.clone()))
6669 .await
6670 .into_iter()
6671 .filter_map(|match_range| {
6672 let match_start = buffer_snapshot
6673 .anchor_after(search_range.start + match_range.start);
6674 let match_end = buffer_snapshot
6675 .anchor_before(search_range.start + match_range.end);
6676 let match_anchor_range = Anchor::range_in_buffer(
6677 excerpt_id,
6678 buffer_snapshot.remote_id(),
6679 match_start..match_end,
6680 );
6681 (match_anchor_range != query_range).then_some(match_anchor_range)
6682 }),
6683 );
6684 }
6685 match_ranges
6686 });
6687 let match_ranges = match_task.await;
6688 editor
6689 .update_in(cx, |editor, _, cx| {
6690 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6691 if !match_ranges.is_empty() {
6692 editor.highlight_background::<SelectedTextHighlight>(
6693 &match_ranges,
6694 |theme| theme.colors().editor_document_highlight_bracket_background,
6695 cx,
6696 )
6697 }
6698 })
6699 .log_err();
6700 })
6701 }
6702
6703 fn refresh_selected_text_highlights(
6704 &mut self,
6705 on_buffer_edit: bool,
6706 window: &mut Window,
6707 cx: &mut Context<Editor>,
6708 ) {
6709 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6710 else {
6711 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6712 self.quick_selection_highlight_task.take();
6713 self.debounced_selection_highlight_task.take();
6714 return;
6715 };
6716 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6717 if on_buffer_edit
6718 || self
6719 .quick_selection_highlight_task
6720 .as_ref()
6721 .map_or(true, |(prev_anchor_range, _)| {
6722 prev_anchor_range != &query_range
6723 })
6724 {
6725 let multi_buffer_visible_start = self
6726 .scroll_manager
6727 .anchor()
6728 .anchor
6729 .to_point(&multi_buffer_snapshot);
6730 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6731 multi_buffer_visible_start
6732 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6733 Bias::Left,
6734 );
6735 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6736 self.quick_selection_highlight_task = Some((
6737 query_range.clone(),
6738 self.update_selection_occurrence_highlights(
6739 query_text.clone(),
6740 query_range.clone(),
6741 multi_buffer_visible_range,
6742 false,
6743 window,
6744 cx,
6745 ),
6746 ));
6747 }
6748 if on_buffer_edit
6749 || self
6750 .debounced_selection_highlight_task
6751 .as_ref()
6752 .map_or(true, |(prev_anchor_range, _)| {
6753 prev_anchor_range != &query_range
6754 })
6755 {
6756 let multi_buffer_start = multi_buffer_snapshot
6757 .anchor_before(0)
6758 .to_point(&multi_buffer_snapshot);
6759 let multi_buffer_end = multi_buffer_snapshot
6760 .anchor_after(multi_buffer_snapshot.len())
6761 .to_point(&multi_buffer_snapshot);
6762 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6763 self.debounced_selection_highlight_task = Some((
6764 query_range.clone(),
6765 self.update_selection_occurrence_highlights(
6766 query_text,
6767 query_range,
6768 multi_buffer_full_range,
6769 true,
6770 window,
6771 cx,
6772 ),
6773 ));
6774 }
6775 }
6776
6777 pub fn refresh_inline_completion(
6778 &mut self,
6779 debounce: bool,
6780 user_requested: bool,
6781 window: &mut Window,
6782 cx: &mut Context<Self>,
6783 ) -> Option<()> {
6784 let provider = self.edit_prediction_provider()?;
6785 let cursor = self.selections.newest_anchor().head();
6786 let (buffer, cursor_buffer_position) =
6787 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6788
6789 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6790 self.discard_inline_completion(false, cx);
6791 return None;
6792 }
6793
6794 if !user_requested
6795 && (!self.should_show_edit_predictions()
6796 || !self.is_focused(window)
6797 || buffer.read(cx).is_empty())
6798 {
6799 self.discard_inline_completion(false, cx);
6800 return None;
6801 }
6802
6803 self.update_visible_inline_completion(window, cx);
6804 provider.refresh(
6805 self.project.clone(),
6806 buffer,
6807 cursor_buffer_position,
6808 debounce,
6809 cx,
6810 );
6811 Some(())
6812 }
6813
6814 fn show_edit_predictions_in_menu(&self) -> bool {
6815 match self.edit_prediction_settings {
6816 EditPredictionSettings::Disabled => false,
6817 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6818 }
6819 }
6820
6821 pub fn edit_predictions_enabled(&self) -> bool {
6822 match self.edit_prediction_settings {
6823 EditPredictionSettings::Disabled => false,
6824 EditPredictionSettings::Enabled { .. } => true,
6825 }
6826 }
6827
6828 fn edit_prediction_requires_modifier(&self) -> bool {
6829 match self.edit_prediction_settings {
6830 EditPredictionSettings::Disabled => false,
6831 EditPredictionSettings::Enabled {
6832 preview_requires_modifier,
6833 ..
6834 } => preview_requires_modifier,
6835 }
6836 }
6837
6838 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6839 if self.edit_prediction_provider.is_none() {
6840 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6841 } else {
6842 let selection = self.selections.newest_anchor();
6843 let cursor = selection.head();
6844
6845 if let Some((buffer, cursor_buffer_position)) =
6846 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6847 {
6848 self.edit_prediction_settings =
6849 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6850 }
6851 }
6852 }
6853
6854 fn edit_prediction_settings_at_position(
6855 &self,
6856 buffer: &Entity<Buffer>,
6857 buffer_position: language::Anchor,
6858 cx: &App,
6859 ) -> EditPredictionSettings {
6860 if !self.mode.is_full()
6861 || !self.show_inline_completions_override.unwrap_or(true)
6862 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6863 {
6864 return EditPredictionSettings::Disabled;
6865 }
6866
6867 let buffer = buffer.read(cx);
6868
6869 let file = buffer.file();
6870
6871 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6872 return EditPredictionSettings::Disabled;
6873 };
6874
6875 let by_provider = matches!(
6876 self.menu_inline_completions_policy,
6877 MenuInlineCompletionsPolicy::ByProvider
6878 );
6879
6880 let show_in_menu = by_provider
6881 && self
6882 .edit_prediction_provider
6883 .as_ref()
6884 .map_or(false, |provider| {
6885 provider.provider.show_completions_in_menu()
6886 });
6887
6888 let preview_requires_modifier =
6889 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6890
6891 EditPredictionSettings::Enabled {
6892 show_in_menu,
6893 preview_requires_modifier,
6894 }
6895 }
6896
6897 fn should_show_edit_predictions(&self) -> bool {
6898 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6899 }
6900
6901 pub fn edit_prediction_preview_is_active(&self) -> bool {
6902 matches!(
6903 self.edit_prediction_preview,
6904 EditPredictionPreview::Active { .. }
6905 )
6906 }
6907
6908 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6909 let cursor = self.selections.newest_anchor().head();
6910 if let Some((buffer, cursor_position)) =
6911 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6912 {
6913 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6914 } else {
6915 false
6916 }
6917 }
6918
6919 pub fn supports_minimap(&self, cx: &App) -> bool {
6920 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6921 }
6922
6923 fn edit_predictions_enabled_in_buffer(
6924 &self,
6925 buffer: &Entity<Buffer>,
6926 buffer_position: language::Anchor,
6927 cx: &App,
6928 ) -> bool {
6929 maybe!({
6930 if self.read_only(cx) {
6931 return Some(false);
6932 }
6933 let provider = self.edit_prediction_provider()?;
6934 if !provider.is_enabled(&buffer, buffer_position, cx) {
6935 return Some(false);
6936 }
6937 let buffer = buffer.read(cx);
6938 let Some(file) = buffer.file() else {
6939 return Some(true);
6940 };
6941 let settings = all_language_settings(Some(file), cx);
6942 Some(settings.edit_predictions_enabled_for_file(file, cx))
6943 })
6944 .unwrap_or(false)
6945 }
6946
6947 fn cycle_inline_completion(
6948 &mut self,
6949 direction: Direction,
6950 window: &mut Window,
6951 cx: &mut Context<Self>,
6952 ) -> Option<()> {
6953 let provider = self.edit_prediction_provider()?;
6954 let cursor = self.selections.newest_anchor().head();
6955 let (buffer, cursor_buffer_position) =
6956 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6957 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6958 return None;
6959 }
6960
6961 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6962 self.update_visible_inline_completion(window, cx);
6963
6964 Some(())
6965 }
6966
6967 pub fn show_inline_completion(
6968 &mut self,
6969 _: &ShowEditPrediction,
6970 window: &mut Window,
6971 cx: &mut Context<Self>,
6972 ) {
6973 if !self.has_active_inline_completion() {
6974 self.refresh_inline_completion(false, true, window, cx);
6975 return;
6976 }
6977
6978 self.update_visible_inline_completion(window, cx);
6979 }
6980
6981 pub fn display_cursor_names(
6982 &mut self,
6983 _: &DisplayCursorNames,
6984 window: &mut Window,
6985 cx: &mut Context<Self>,
6986 ) {
6987 self.show_cursor_names(window, cx);
6988 }
6989
6990 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6991 self.show_cursor_names = true;
6992 cx.notify();
6993 cx.spawn_in(window, async move |this, cx| {
6994 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6995 this.update(cx, |this, cx| {
6996 this.show_cursor_names = false;
6997 cx.notify()
6998 })
6999 .ok()
7000 })
7001 .detach();
7002 }
7003
7004 pub fn next_edit_prediction(
7005 &mut self,
7006 _: &NextEditPrediction,
7007 window: &mut Window,
7008 cx: &mut Context<Self>,
7009 ) {
7010 if self.has_active_inline_completion() {
7011 self.cycle_inline_completion(Direction::Next, window, cx);
7012 } else {
7013 let is_copilot_disabled = self
7014 .refresh_inline_completion(false, true, window, cx)
7015 .is_none();
7016 if is_copilot_disabled {
7017 cx.propagate();
7018 }
7019 }
7020 }
7021
7022 pub fn previous_edit_prediction(
7023 &mut self,
7024 _: &PreviousEditPrediction,
7025 window: &mut Window,
7026 cx: &mut Context<Self>,
7027 ) {
7028 if self.has_active_inline_completion() {
7029 self.cycle_inline_completion(Direction::Prev, window, cx);
7030 } else {
7031 let is_copilot_disabled = self
7032 .refresh_inline_completion(false, true, window, cx)
7033 .is_none();
7034 if is_copilot_disabled {
7035 cx.propagate();
7036 }
7037 }
7038 }
7039
7040 pub fn accept_edit_prediction(
7041 &mut self,
7042 _: &AcceptEditPrediction,
7043 window: &mut Window,
7044 cx: &mut Context<Self>,
7045 ) {
7046 if self.show_edit_predictions_in_menu() {
7047 self.hide_context_menu(window, cx);
7048 }
7049
7050 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7051 return;
7052 };
7053
7054 self.report_inline_completion_event(
7055 active_inline_completion.completion_id.clone(),
7056 true,
7057 cx,
7058 );
7059
7060 match &active_inline_completion.completion {
7061 InlineCompletion::Move { target, .. } => {
7062 let target = *target;
7063
7064 if let Some(position_map) = &self.last_position_map {
7065 if position_map
7066 .visible_row_range
7067 .contains(&target.to_display_point(&position_map.snapshot).row())
7068 || !self.edit_prediction_requires_modifier()
7069 {
7070 self.unfold_ranges(&[target..target], true, false, cx);
7071 // Note that this is also done in vim's handler of the Tab action.
7072 self.change_selections(
7073 Some(Autoscroll::newest()),
7074 window,
7075 cx,
7076 |selections| {
7077 selections.select_anchor_ranges([target..target]);
7078 },
7079 );
7080 self.clear_row_highlights::<EditPredictionPreview>();
7081
7082 self.edit_prediction_preview
7083 .set_previous_scroll_position(None);
7084 } else {
7085 self.edit_prediction_preview
7086 .set_previous_scroll_position(Some(
7087 position_map.snapshot.scroll_anchor,
7088 ));
7089
7090 self.highlight_rows::<EditPredictionPreview>(
7091 target..target,
7092 cx.theme().colors().editor_highlighted_line_background,
7093 RowHighlightOptions {
7094 autoscroll: true,
7095 ..Default::default()
7096 },
7097 cx,
7098 );
7099 self.request_autoscroll(Autoscroll::fit(), cx);
7100 }
7101 }
7102 }
7103 InlineCompletion::Edit { edits, .. } => {
7104 if let Some(provider) = self.edit_prediction_provider() {
7105 provider.accept(cx);
7106 }
7107
7108 // Store the transaction ID and selections before applying the edit
7109 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7110
7111 let snapshot = self.buffer.read(cx).snapshot(cx);
7112 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7113
7114 self.buffer.update(cx, |buffer, cx| {
7115 buffer.edit(edits.iter().cloned(), None, cx)
7116 });
7117
7118 self.change_selections(None, window, cx, |s| {
7119 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7120 });
7121
7122 let selections = self.selections.disjoint_anchors();
7123 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7124 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7125 if has_new_transaction {
7126 self.selection_history
7127 .insert_transaction(transaction_id_now, selections);
7128 }
7129 }
7130
7131 self.update_visible_inline_completion(window, cx);
7132 if self.active_inline_completion.is_none() {
7133 self.refresh_inline_completion(true, true, window, cx);
7134 }
7135
7136 cx.notify();
7137 }
7138 }
7139
7140 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7141 }
7142
7143 pub fn accept_partial_inline_completion(
7144 &mut self,
7145 _: &AcceptPartialEditPrediction,
7146 window: &mut Window,
7147 cx: &mut Context<Self>,
7148 ) {
7149 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7150 return;
7151 };
7152 if self.selections.count() != 1 {
7153 return;
7154 }
7155
7156 self.report_inline_completion_event(
7157 active_inline_completion.completion_id.clone(),
7158 true,
7159 cx,
7160 );
7161
7162 match &active_inline_completion.completion {
7163 InlineCompletion::Move { target, .. } => {
7164 let target = *target;
7165 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
7166 selections.select_anchor_ranges([target..target]);
7167 });
7168 }
7169 InlineCompletion::Edit { edits, .. } => {
7170 // Find an insertion that starts at the cursor position.
7171 let snapshot = self.buffer.read(cx).snapshot(cx);
7172 let cursor_offset = self.selections.newest::<usize>(cx).head();
7173 let insertion = edits.iter().find_map(|(range, text)| {
7174 let range = range.to_offset(&snapshot);
7175 if range.is_empty() && range.start == cursor_offset {
7176 Some(text)
7177 } else {
7178 None
7179 }
7180 });
7181
7182 if let Some(text) = insertion {
7183 let mut partial_completion = text
7184 .chars()
7185 .by_ref()
7186 .take_while(|c| c.is_alphabetic())
7187 .collect::<String>();
7188 if partial_completion.is_empty() {
7189 partial_completion = text
7190 .chars()
7191 .by_ref()
7192 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7193 .collect::<String>();
7194 }
7195
7196 cx.emit(EditorEvent::InputHandled {
7197 utf16_range_to_replace: None,
7198 text: partial_completion.clone().into(),
7199 });
7200
7201 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7202
7203 self.refresh_inline_completion(true, true, window, cx);
7204 cx.notify();
7205 } else {
7206 self.accept_edit_prediction(&Default::default(), window, cx);
7207 }
7208 }
7209 }
7210 }
7211
7212 fn discard_inline_completion(
7213 &mut self,
7214 should_report_inline_completion_event: bool,
7215 cx: &mut Context<Self>,
7216 ) -> bool {
7217 if should_report_inline_completion_event {
7218 let completion_id = self
7219 .active_inline_completion
7220 .as_ref()
7221 .and_then(|active_completion| active_completion.completion_id.clone());
7222
7223 self.report_inline_completion_event(completion_id, false, cx);
7224 }
7225
7226 if let Some(provider) = self.edit_prediction_provider() {
7227 provider.discard(cx);
7228 }
7229
7230 self.take_active_inline_completion(cx)
7231 }
7232
7233 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7234 let Some(provider) = self.edit_prediction_provider() else {
7235 return;
7236 };
7237
7238 let Some((_, buffer, _)) = self
7239 .buffer
7240 .read(cx)
7241 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7242 else {
7243 return;
7244 };
7245
7246 let extension = buffer
7247 .read(cx)
7248 .file()
7249 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7250
7251 let event_type = match accepted {
7252 true => "Edit Prediction Accepted",
7253 false => "Edit Prediction Discarded",
7254 };
7255 telemetry::event!(
7256 event_type,
7257 provider = provider.name(),
7258 prediction_id = id,
7259 suggestion_accepted = accepted,
7260 file_extension = extension,
7261 );
7262 }
7263
7264 pub fn has_active_inline_completion(&self) -> bool {
7265 self.active_inline_completion.is_some()
7266 }
7267
7268 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7269 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7270 return false;
7271 };
7272
7273 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7274 self.clear_highlights::<InlineCompletionHighlight>(cx);
7275 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7276 true
7277 }
7278
7279 /// Returns true when we're displaying the edit prediction popover below the cursor
7280 /// like we are not previewing and the LSP autocomplete menu is visible
7281 /// or we are in `when_holding_modifier` mode.
7282 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7283 if self.edit_prediction_preview_is_active()
7284 || !self.show_edit_predictions_in_menu()
7285 || !self.edit_predictions_enabled()
7286 {
7287 return false;
7288 }
7289
7290 if self.has_visible_completions_menu() {
7291 return true;
7292 }
7293
7294 has_completion && self.edit_prediction_requires_modifier()
7295 }
7296
7297 fn handle_modifiers_changed(
7298 &mut self,
7299 modifiers: Modifiers,
7300 position_map: &PositionMap,
7301 window: &mut Window,
7302 cx: &mut Context<Self>,
7303 ) {
7304 if self.show_edit_predictions_in_menu() {
7305 self.update_edit_prediction_preview(&modifiers, window, cx);
7306 }
7307
7308 self.update_selection_mode(&modifiers, position_map, window, cx);
7309
7310 let mouse_position = window.mouse_position();
7311 if !position_map.text_hitbox.is_hovered(window) {
7312 return;
7313 }
7314
7315 self.update_hovered_link(
7316 position_map.point_for_position(mouse_position),
7317 &position_map.snapshot,
7318 modifiers,
7319 window,
7320 cx,
7321 )
7322 }
7323
7324 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7325 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7326 if invert {
7327 match multi_cursor_setting {
7328 MultiCursorModifier::Alt => modifiers.alt,
7329 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7330 }
7331 } else {
7332 match multi_cursor_setting {
7333 MultiCursorModifier::Alt => modifiers.secondary(),
7334 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7335 }
7336 }
7337 }
7338
7339 fn columnar_selection_mode(
7340 modifiers: &Modifiers,
7341 cx: &mut Context<Self>,
7342 ) -> Option<ColumnarMode> {
7343 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7344 if Self::multi_cursor_modifier(false, modifiers, cx) {
7345 Some(ColumnarMode::FromMouse)
7346 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7347 Some(ColumnarMode::FromSelection)
7348 } else {
7349 None
7350 }
7351 } else {
7352 None
7353 }
7354 }
7355
7356 fn update_selection_mode(
7357 &mut self,
7358 modifiers: &Modifiers,
7359 position_map: &PositionMap,
7360 window: &mut Window,
7361 cx: &mut Context<Self>,
7362 ) {
7363 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7364 return;
7365 };
7366 if self.selections.pending.is_none() {
7367 return;
7368 }
7369
7370 let mouse_position = window.mouse_position();
7371 let point_for_position = position_map.point_for_position(mouse_position);
7372 let position = point_for_position.previous_valid;
7373
7374 self.select(
7375 SelectPhase::BeginColumnar {
7376 position,
7377 reset: false,
7378 mode,
7379 goal_column: point_for_position.exact_unclipped.column(),
7380 },
7381 window,
7382 cx,
7383 );
7384 }
7385
7386 fn update_edit_prediction_preview(
7387 &mut self,
7388 modifiers: &Modifiers,
7389 window: &mut Window,
7390 cx: &mut Context<Self>,
7391 ) {
7392 let mut modifiers_held = false;
7393 if let Some(accept_keystroke) = self
7394 .accept_edit_prediction_keybind(false, window, cx)
7395 .keystroke()
7396 {
7397 modifiers_held = modifiers_held
7398 || (&accept_keystroke.modifiers == modifiers
7399 && accept_keystroke.modifiers.modified());
7400 };
7401 if let Some(accept_partial_keystroke) = self
7402 .accept_edit_prediction_keybind(true, window, cx)
7403 .keystroke()
7404 {
7405 modifiers_held = modifiers_held
7406 || (&accept_partial_keystroke.modifiers == modifiers
7407 && accept_partial_keystroke.modifiers.modified());
7408 }
7409
7410 if modifiers_held {
7411 if matches!(
7412 self.edit_prediction_preview,
7413 EditPredictionPreview::Inactive { .. }
7414 ) {
7415 self.edit_prediction_preview = EditPredictionPreview::Active {
7416 previous_scroll_position: None,
7417 since: Instant::now(),
7418 };
7419
7420 self.update_visible_inline_completion(window, cx);
7421 cx.notify();
7422 }
7423 } else if let EditPredictionPreview::Active {
7424 previous_scroll_position,
7425 since,
7426 } = self.edit_prediction_preview
7427 {
7428 if let (Some(previous_scroll_position), Some(position_map)) =
7429 (previous_scroll_position, self.last_position_map.as_ref())
7430 {
7431 self.set_scroll_position(
7432 previous_scroll_position
7433 .scroll_position(&position_map.snapshot.display_snapshot),
7434 window,
7435 cx,
7436 );
7437 }
7438
7439 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7440 released_too_fast: since.elapsed() < Duration::from_millis(200),
7441 };
7442 self.clear_row_highlights::<EditPredictionPreview>();
7443 self.update_visible_inline_completion(window, cx);
7444 cx.notify();
7445 }
7446 }
7447
7448 fn update_visible_inline_completion(
7449 &mut self,
7450 _window: &mut Window,
7451 cx: &mut Context<Self>,
7452 ) -> Option<()> {
7453 let selection = self.selections.newest_anchor();
7454 let cursor = selection.head();
7455 let multibuffer = self.buffer.read(cx).snapshot(cx);
7456 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7457 let excerpt_id = cursor.excerpt_id;
7458
7459 let show_in_menu = self.show_edit_predictions_in_menu();
7460 let completions_menu_has_precedence = !show_in_menu
7461 && (self.context_menu.borrow().is_some()
7462 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7463
7464 if completions_menu_has_precedence
7465 || !offset_selection.is_empty()
7466 || self
7467 .active_inline_completion
7468 .as_ref()
7469 .map_or(false, |completion| {
7470 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7471 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7472 !invalidation_range.contains(&offset_selection.head())
7473 })
7474 {
7475 self.discard_inline_completion(false, cx);
7476 return None;
7477 }
7478
7479 self.take_active_inline_completion(cx);
7480 let Some(provider) = self.edit_prediction_provider() else {
7481 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7482 return None;
7483 };
7484
7485 let (buffer, cursor_buffer_position) =
7486 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7487
7488 self.edit_prediction_settings =
7489 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7490
7491 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7492
7493 if self.edit_prediction_indent_conflict {
7494 let cursor_point = cursor.to_point(&multibuffer);
7495
7496 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7497
7498 if let Some((_, indent)) = indents.iter().next() {
7499 if indent.len == cursor_point.column {
7500 self.edit_prediction_indent_conflict = false;
7501 }
7502 }
7503 }
7504
7505 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7506 let edits = inline_completion
7507 .edits
7508 .into_iter()
7509 .flat_map(|(range, new_text)| {
7510 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7511 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7512 Some((start..end, new_text))
7513 })
7514 .collect::<Vec<_>>();
7515 if edits.is_empty() {
7516 return None;
7517 }
7518
7519 let first_edit_start = edits.first().unwrap().0.start;
7520 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7521 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7522
7523 let last_edit_end = edits.last().unwrap().0.end;
7524 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7525 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7526
7527 let cursor_row = cursor.to_point(&multibuffer).row;
7528
7529 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7530
7531 let mut inlay_ids = Vec::new();
7532 let invalidation_row_range;
7533 let move_invalidation_row_range = if cursor_row < edit_start_row {
7534 Some(cursor_row..edit_end_row)
7535 } else if cursor_row > edit_end_row {
7536 Some(edit_start_row..cursor_row)
7537 } else {
7538 None
7539 };
7540 let is_move =
7541 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7542 let completion = if is_move {
7543 invalidation_row_range =
7544 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7545 let target = first_edit_start;
7546 InlineCompletion::Move { target, snapshot }
7547 } else {
7548 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7549 && !self.inline_completions_hidden_for_vim_mode;
7550
7551 if show_completions_in_buffer {
7552 if edits
7553 .iter()
7554 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7555 {
7556 let mut inlays = Vec::new();
7557 for (range, new_text) in &edits {
7558 let inlay = Inlay::inline_completion(
7559 post_inc(&mut self.next_inlay_id),
7560 range.start,
7561 new_text.as_str(),
7562 );
7563 inlay_ids.push(inlay.id);
7564 inlays.push(inlay);
7565 }
7566
7567 self.splice_inlays(&[], inlays, cx);
7568 } else {
7569 let background_color = cx.theme().status().deleted_background;
7570 self.highlight_text::<InlineCompletionHighlight>(
7571 edits.iter().map(|(range, _)| range.clone()).collect(),
7572 HighlightStyle {
7573 background_color: Some(background_color),
7574 ..Default::default()
7575 },
7576 cx,
7577 );
7578 }
7579 }
7580
7581 invalidation_row_range = edit_start_row..edit_end_row;
7582
7583 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7584 if provider.show_tab_accept_marker() {
7585 EditDisplayMode::TabAccept
7586 } else {
7587 EditDisplayMode::Inline
7588 }
7589 } else {
7590 EditDisplayMode::DiffPopover
7591 };
7592
7593 InlineCompletion::Edit {
7594 edits,
7595 edit_preview: inline_completion.edit_preview,
7596 display_mode,
7597 snapshot,
7598 }
7599 };
7600
7601 let invalidation_range = multibuffer
7602 .anchor_before(Point::new(invalidation_row_range.start, 0))
7603 ..multibuffer.anchor_after(Point::new(
7604 invalidation_row_range.end,
7605 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7606 ));
7607
7608 self.stale_inline_completion_in_menu = None;
7609 self.active_inline_completion = Some(InlineCompletionState {
7610 inlay_ids,
7611 completion,
7612 completion_id: inline_completion.id,
7613 invalidation_range,
7614 });
7615
7616 cx.notify();
7617
7618 Some(())
7619 }
7620
7621 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7622 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7623 }
7624
7625 fn clear_tasks(&mut self) {
7626 self.tasks.clear()
7627 }
7628
7629 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7630 if self.tasks.insert(key, value).is_some() {
7631 // This case should hopefully be rare, but just in case...
7632 log::error!(
7633 "multiple different run targets found on a single line, only the last target will be rendered"
7634 )
7635 }
7636 }
7637
7638 /// Get all display points of breakpoints that will be rendered within editor
7639 ///
7640 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7641 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7642 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7643 fn active_breakpoints(
7644 &self,
7645 range: Range<DisplayRow>,
7646 window: &mut Window,
7647 cx: &mut Context<Self>,
7648 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7649 let mut breakpoint_display_points = HashMap::default();
7650
7651 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7652 return breakpoint_display_points;
7653 };
7654
7655 let snapshot = self.snapshot(window, cx);
7656
7657 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7658 let Some(project) = self.project.as_ref() else {
7659 return breakpoint_display_points;
7660 };
7661
7662 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7663 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7664
7665 for (buffer_snapshot, range, excerpt_id) in
7666 multi_buffer_snapshot.range_to_buffer_ranges(range)
7667 {
7668 let Some(buffer) = project
7669 .read(cx)
7670 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7671 else {
7672 continue;
7673 };
7674 let breakpoints = breakpoint_store.read(cx).breakpoints(
7675 &buffer,
7676 Some(
7677 buffer_snapshot.anchor_before(range.start)
7678 ..buffer_snapshot.anchor_after(range.end),
7679 ),
7680 buffer_snapshot,
7681 cx,
7682 );
7683 for (breakpoint, state) in breakpoints {
7684 let multi_buffer_anchor =
7685 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7686 let position = multi_buffer_anchor
7687 .to_point(&multi_buffer_snapshot)
7688 .to_display_point(&snapshot);
7689
7690 breakpoint_display_points.insert(
7691 position.row(),
7692 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7693 );
7694 }
7695 }
7696
7697 breakpoint_display_points
7698 }
7699
7700 fn breakpoint_context_menu(
7701 &self,
7702 anchor: Anchor,
7703 window: &mut Window,
7704 cx: &mut Context<Self>,
7705 ) -> Entity<ui::ContextMenu> {
7706 let weak_editor = cx.weak_entity();
7707 let focus_handle = self.focus_handle(cx);
7708
7709 let row = self
7710 .buffer
7711 .read(cx)
7712 .snapshot(cx)
7713 .summary_for_anchor::<Point>(&anchor)
7714 .row;
7715
7716 let breakpoint = self
7717 .breakpoint_at_row(row, window, cx)
7718 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7719
7720 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7721 "Edit Log Breakpoint"
7722 } else {
7723 "Set Log Breakpoint"
7724 };
7725
7726 let condition_breakpoint_msg = if breakpoint
7727 .as_ref()
7728 .is_some_and(|bp| bp.1.condition.is_some())
7729 {
7730 "Edit Condition Breakpoint"
7731 } else {
7732 "Set Condition Breakpoint"
7733 };
7734
7735 let hit_condition_breakpoint_msg = if breakpoint
7736 .as_ref()
7737 .is_some_and(|bp| bp.1.hit_condition.is_some())
7738 {
7739 "Edit Hit Condition Breakpoint"
7740 } else {
7741 "Set Hit Condition Breakpoint"
7742 };
7743
7744 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7745 "Unset Breakpoint"
7746 } else {
7747 "Set Breakpoint"
7748 };
7749
7750 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7751
7752 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7753 BreakpointState::Enabled => Some("Disable"),
7754 BreakpointState::Disabled => Some("Enable"),
7755 });
7756
7757 let (anchor, breakpoint) =
7758 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7759
7760 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7761 menu.on_blur_subscription(Subscription::new(|| {}))
7762 .context(focus_handle)
7763 .when(run_to_cursor, |this| {
7764 let weak_editor = weak_editor.clone();
7765 this.entry("Run to cursor", None, move |window, cx| {
7766 weak_editor
7767 .update(cx, |editor, cx| {
7768 editor.change_selections(None, window, cx, |s| {
7769 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7770 });
7771 })
7772 .ok();
7773
7774 window.dispatch_action(Box::new(RunToCursor), cx);
7775 })
7776 .separator()
7777 })
7778 .when_some(toggle_state_msg, |this, msg| {
7779 this.entry(msg, None, {
7780 let weak_editor = weak_editor.clone();
7781 let breakpoint = breakpoint.clone();
7782 move |_window, cx| {
7783 weak_editor
7784 .update(cx, |this, cx| {
7785 this.edit_breakpoint_at_anchor(
7786 anchor,
7787 breakpoint.as_ref().clone(),
7788 BreakpointEditAction::InvertState,
7789 cx,
7790 );
7791 })
7792 .log_err();
7793 }
7794 })
7795 })
7796 .entry(set_breakpoint_msg, None, {
7797 let weak_editor = weak_editor.clone();
7798 let breakpoint = breakpoint.clone();
7799 move |_window, cx| {
7800 weak_editor
7801 .update(cx, |this, cx| {
7802 this.edit_breakpoint_at_anchor(
7803 anchor,
7804 breakpoint.as_ref().clone(),
7805 BreakpointEditAction::Toggle,
7806 cx,
7807 );
7808 })
7809 .log_err();
7810 }
7811 })
7812 .entry(log_breakpoint_msg, None, {
7813 let breakpoint = breakpoint.clone();
7814 let weak_editor = weak_editor.clone();
7815 move |window, cx| {
7816 weak_editor
7817 .update(cx, |this, cx| {
7818 this.add_edit_breakpoint_block(
7819 anchor,
7820 breakpoint.as_ref(),
7821 BreakpointPromptEditAction::Log,
7822 window,
7823 cx,
7824 );
7825 })
7826 .log_err();
7827 }
7828 })
7829 .entry(condition_breakpoint_msg, None, {
7830 let breakpoint = breakpoint.clone();
7831 let weak_editor = weak_editor.clone();
7832 move |window, cx| {
7833 weak_editor
7834 .update(cx, |this, cx| {
7835 this.add_edit_breakpoint_block(
7836 anchor,
7837 breakpoint.as_ref(),
7838 BreakpointPromptEditAction::Condition,
7839 window,
7840 cx,
7841 );
7842 })
7843 .log_err();
7844 }
7845 })
7846 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7847 weak_editor
7848 .update(cx, |this, cx| {
7849 this.add_edit_breakpoint_block(
7850 anchor,
7851 breakpoint.as_ref(),
7852 BreakpointPromptEditAction::HitCondition,
7853 window,
7854 cx,
7855 );
7856 })
7857 .log_err();
7858 })
7859 })
7860 }
7861
7862 fn render_breakpoint(
7863 &self,
7864 position: Anchor,
7865 row: DisplayRow,
7866 breakpoint: &Breakpoint,
7867 state: Option<BreakpointSessionState>,
7868 cx: &mut Context<Self>,
7869 ) -> IconButton {
7870 let is_rejected = state.is_some_and(|s| !s.verified);
7871 // Is it a breakpoint that shows up when hovering over gutter?
7872 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7873 (false, false),
7874 |PhantomBreakpointIndicator {
7875 is_active,
7876 display_row,
7877 collides_with_existing_breakpoint,
7878 }| {
7879 (
7880 is_active && display_row == row,
7881 collides_with_existing_breakpoint,
7882 )
7883 },
7884 );
7885
7886 let (color, icon) = {
7887 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7888 (false, false) => ui::IconName::DebugBreakpoint,
7889 (true, false) => ui::IconName::DebugLogBreakpoint,
7890 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7891 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7892 };
7893
7894 let color = if is_phantom {
7895 Color::Hint
7896 } else if is_rejected {
7897 Color::Disabled
7898 } else {
7899 Color::Debugger
7900 };
7901
7902 (color, icon)
7903 };
7904
7905 let breakpoint = Arc::from(breakpoint.clone());
7906
7907 let alt_as_text = gpui::Keystroke {
7908 modifiers: Modifiers::secondary_key(),
7909 ..Default::default()
7910 };
7911 let primary_action_text = if breakpoint.is_disabled() {
7912 "Enable breakpoint"
7913 } else if is_phantom && !collides_with_existing {
7914 "Set breakpoint"
7915 } else {
7916 "Unset breakpoint"
7917 };
7918 let focus_handle = self.focus_handle.clone();
7919
7920 let meta = if is_rejected {
7921 SharedString::from("No executable code is associated with this line.")
7922 } else if collides_with_existing && !breakpoint.is_disabled() {
7923 SharedString::from(format!(
7924 "{alt_as_text}-click to disable,\nright-click for more options."
7925 ))
7926 } else {
7927 SharedString::from("Right-click for more options.")
7928 };
7929 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7930 .icon_size(IconSize::XSmall)
7931 .size(ui::ButtonSize::None)
7932 .when(is_rejected, |this| {
7933 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7934 })
7935 .icon_color(color)
7936 .style(ButtonStyle::Transparent)
7937 .on_click(cx.listener({
7938 let breakpoint = breakpoint.clone();
7939
7940 move |editor, event: &ClickEvent, window, cx| {
7941 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7942 BreakpointEditAction::InvertState
7943 } else {
7944 BreakpointEditAction::Toggle
7945 };
7946
7947 window.focus(&editor.focus_handle(cx));
7948 editor.edit_breakpoint_at_anchor(
7949 position,
7950 breakpoint.as_ref().clone(),
7951 edit_action,
7952 cx,
7953 );
7954 }
7955 }))
7956 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7957 editor.set_breakpoint_context_menu(
7958 row,
7959 Some(position),
7960 event.down.position,
7961 window,
7962 cx,
7963 );
7964 }))
7965 .tooltip(move |window, cx| {
7966 Tooltip::with_meta_in(
7967 primary_action_text,
7968 Some(&ToggleBreakpoint),
7969 meta.clone(),
7970 &focus_handle,
7971 window,
7972 cx,
7973 )
7974 })
7975 }
7976
7977 fn build_tasks_context(
7978 project: &Entity<Project>,
7979 buffer: &Entity<Buffer>,
7980 buffer_row: u32,
7981 tasks: &Arc<RunnableTasks>,
7982 cx: &mut Context<Self>,
7983 ) -> Task<Option<task::TaskContext>> {
7984 let position = Point::new(buffer_row, tasks.column);
7985 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7986 let location = Location {
7987 buffer: buffer.clone(),
7988 range: range_start..range_start,
7989 };
7990 // Fill in the environmental variables from the tree-sitter captures
7991 let mut captured_task_variables = TaskVariables::default();
7992 for (capture_name, value) in tasks.extra_variables.clone() {
7993 captured_task_variables.insert(
7994 task::VariableName::Custom(capture_name.into()),
7995 value.clone(),
7996 );
7997 }
7998 project.update(cx, |project, cx| {
7999 project.task_store().update(cx, |task_store, cx| {
8000 task_store.task_context_for_location(captured_task_variables, location, cx)
8001 })
8002 })
8003 }
8004
8005 pub fn spawn_nearest_task(
8006 &mut self,
8007 action: &SpawnNearestTask,
8008 window: &mut Window,
8009 cx: &mut Context<Self>,
8010 ) {
8011 let Some((workspace, _)) = self.workspace.clone() else {
8012 return;
8013 };
8014 let Some(project) = self.project.clone() else {
8015 return;
8016 };
8017
8018 // Try to find a closest, enclosing node using tree-sitter that has a
8019 // task
8020 let Some((buffer, buffer_row, tasks)) = self
8021 .find_enclosing_node_task(cx)
8022 // Or find the task that's closest in row-distance.
8023 .or_else(|| self.find_closest_task(cx))
8024 else {
8025 return;
8026 };
8027
8028 let reveal_strategy = action.reveal;
8029 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8030 cx.spawn_in(window, async move |_, cx| {
8031 let context = task_context.await?;
8032 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8033
8034 let resolved = &mut resolved_task.resolved;
8035 resolved.reveal = reveal_strategy;
8036
8037 workspace
8038 .update_in(cx, |workspace, window, cx| {
8039 workspace.schedule_resolved_task(
8040 task_source_kind,
8041 resolved_task,
8042 false,
8043 window,
8044 cx,
8045 );
8046 })
8047 .ok()
8048 })
8049 .detach();
8050 }
8051
8052 fn find_closest_task(
8053 &mut self,
8054 cx: &mut Context<Self>,
8055 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8056 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8057
8058 let ((buffer_id, row), tasks) = self
8059 .tasks
8060 .iter()
8061 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8062
8063 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8064 let tasks = Arc::new(tasks.to_owned());
8065 Some((buffer, *row, tasks))
8066 }
8067
8068 fn find_enclosing_node_task(
8069 &mut self,
8070 cx: &mut Context<Self>,
8071 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8072 let snapshot = self.buffer.read(cx).snapshot(cx);
8073 let offset = self.selections.newest::<usize>(cx).head();
8074 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8075 let buffer_id = excerpt.buffer().remote_id();
8076
8077 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8078 let mut cursor = layer.node().walk();
8079
8080 while cursor.goto_first_child_for_byte(offset).is_some() {
8081 if cursor.node().end_byte() == offset {
8082 cursor.goto_next_sibling();
8083 }
8084 }
8085
8086 // Ascend to the smallest ancestor that contains the range and has a task.
8087 loop {
8088 let node = cursor.node();
8089 let node_range = node.byte_range();
8090 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8091
8092 // Check if this node contains our offset
8093 if node_range.start <= offset && node_range.end >= offset {
8094 // If it contains offset, check for task
8095 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8096 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8097 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8098 }
8099 }
8100
8101 if !cursor.goto_parent() {
8102 break;
8103 }
8104 }
8105 None
8106 }
8107
8108 fn render_run_indicator(
8109 &self,
8110 _style: &EditorStyle,
8111 is_active: bool,
8112 row: DisplayRow,
8113 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8114 cx: &mut Context<Self>,
8115 ) -> IconButton {
8116 let color = Color::Muted;
8117 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8118
8119 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
8120 .shape(ui::IconButtonShape::Square)
8121 .icon_size(IconSize::XSmall)
8122 .icon_color(color)
8123 .toggle_state(is_active)
8124 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8125 let quick_launch = e.down.button == MouseButton::Left;
8126 window.focus(&editor.focus_handle(cx));
8127 editor.toggle_code_actions(
8128 &ToggleCodeActions {
8129 deployed_from: Some(CodeActionSource::RunMenu(row)),
8130 quick_launch,
8131 },
8132 window,
8133 cx,
8134 );
8135 }))
8136 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8137 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
8138 }))
8139 }
8140
8141 pub fn context_menu_visible(&self) -> bool {
8142 !self.edit_prediction_preview_is_active()
8143 && self
8144 .context_menu
8145 .borrow()
8146 .as_ref()
8147 .map_or(false, |menu| menu.visible())
8148 }
8149
8150 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8151 self.context_menu
8152 .borrow()
8153 .as_ref()
8154 .map(|menu| menu.origin())
8155 }
8156
8157 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8158 self.context_menu_options = Some(options);
8159 }
8160
8161 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8162 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8163
8164 fn render_edit_prediction_popover(
8165 &mut self,
8166 text_bounds: &Bounds<Pixels>,
8167 content_origin: gpui::Point<Pixels>,
8168 right_margin: Pixels,
8169 editor_snapshot: &EditorSnapshot,
8170 visible_row_range: Range<DisplayRow>,
8171 scroll_top: f32,
8172 scroll_bottom: f32,
8173 line_layouts: &[LineWithInvisibles],
8174 line_height: Pixels,
8175 scroll_pixel_position: gpui::Point<Pixels>,
8176 newest_selection_head: Option<DisplayPoint>,
8177 editor_width: Pixels,
8178 style: &EditorStyle,
8179 window: &mut Window,
8180 cx: &mut App,
8181 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8182 if self.mode().is_minimap() {
8183 return None;
8184 }
8185 let active_inline_completion = self.active_inline_completion.as_ref()?;
8186
8187 if self.edit_prediction_visible_in_cursor_popover(true) {
8188 return None;
8189 }
8190
8191 match &active_inline_completion.completion {
8192 InlineCompletion::Move { target, .. } => {
8193 let target_display_point = target.to_display_point(editor_snapshot);
8194
8195 if self.edit_prediction_requires_modifier() {
8196 if !self.edit_prediction_preview_is_active() {
8197 return None;
8198 }
8199
8200 self.render_edit_prediction_modifier_jump_popover(
8201 text_bounds,
8202 content_origin,
8203 visible_row_range,
8204 line_layouts,
8205 line_height,
8206 scroll_pixel_position,
8207 newest_selection_head,
8208 target_display_point,
8209 window,
8210 cx,
8211 )
8212 } else {
8213 self.render_edit_prediction_eager_jump_popover(
8214 text_bounds,
8215 content_origin,
8216 editor_snapshot,
8217 visible_row_range,
8218 scroll_top,
8219 scroll_bottom,
8220 line_height,
8221 scroll_pixel_position,
8222 target_display_point,
8223 editor_width,
8224 window,
8225 cx,
8226 )
8227 }
8228 }
8229 InlineCompletion::Edit {
8230 display_mode: EditDisplayMode::Inline,
8231 ..
8232 } => None,
8233 InlineCompletion::Edit {
8234 display_mode: EditDisplayMode::TabAccept,
8235 edits,
8236 ..
8237 } => {
8238 let range = &edits.first()?.0;
8239 let target_display_point = range.end.to_display_point(editor_snapshot);
8240
8241 self.render_edit_prediction_end_of_line_popover(
8242 "Accept",
8243 editor_snapshot,
8244 visible_row_range,
8245 target_display_point,
8246 line_height,
8247 scroll_pixel_position,
8248 content_origin,
8249 editor_width,
8250 window,
8251 cx,
8252 )
8253 }
8254 InlineCompletion::Edit {
8255 edits,
8256 edit_preview,
8257 display_mode: EditDisplayMode::DiffPopover,
8258 snapshot,
8259 } => self.render_edit_prediction_diff_popover(
8260 text_bounds,
8261 content_origin,
8262 right_margin,
8263 editor_snapshot,
8264 visible_row_range,
8265 line_layouts,
8266 line_height,
8267 scroll_pixel_position,
8268 newest_selection_head,
8269 editor_width,
8270 style,
8271 edits,
8272 edit_preview,
8273 snapshot,
8274 window,
8275 cx,
8276 ),
8277 }
8278 }
8279
8280 fn render_edit_prediction_modifier_jump_popover(
8281 &mut self,
8282 text_bounds: &Bounds<Pixels>,
8283 content_origin: gpui::Point<Pixels>,
8284 visible_row_range: Range<DisplayRow>,
8285 line_layouts: &[LineWithInvisibles],
8286 line_height: Pixels,
8287 scroll_pixel_position: gpui::Point<Pixels>,
8288 newest_selection_head: Option<DisplayPoint>,
8289 target_display_point: DisplayPoint,
8290 window: &mut Window,
8291 cx: &mut App,
8292 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8293 let scrolled_content_origin =
8294 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8295
8296 const SCROLL_PADDING_Y: Pixels = px(12.);
8297
8298 if target_display_point.row() < visible_row_range.start {
8299 return self.render_edit_prediction_scroll_popover(
8300 |_| SCROLL_PADDING_Y,
8301 IconName::ArrowUp,
8302 visible_row_range,
8303 line_layouts,
8304 newest_selection_head,
8305 scrolled_content_origin,
8306 window,
8307 cx,
8308 );
8309 } else if target_display_point.row() >= visible_row_range.end {
8310 return self.render_edit_prediction_scroll_popover(
8311 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8312 IconName::ArrowDown,
8313 visible_row_range,
8314 line_layouts,
8315 newest_selection_head,
8316 scrolled_content_origin,
8317 window,
8318 cx,
8319 );
8320 }
8321
8322 const POLE_WIDTH: Pixels = px(2.);
8323
8324 let line_layout =
8325 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8326 let target_column = target_display_point.column() as usize;
8327
8328 let target_x = line_layout.x_for_index(target_column);
8329 let target_y =
8330 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8331
8332 let flag_on_right = target_x < text_bounds.size.width / 2.;
8333
8334 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8335 border_color.l += 0.001;
8336
8337 let mut element = v_flex()
8338 .items_end()
8339 .when(flag_on_right, |el| el.items_start())
8340 .child(if flag_on_right {
8341 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8342 .rounded_bl(px(0.))
8343 .rounded_tl(px(0.))
8344 .border_l_2()
8345 .border_color(border_color)
8346 } else {
8347 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8348 .rounded_br(px(0.))
8349 .rounded_tr(px(0.))
8350 .border_r_2()
8351 .border_color(border_color)
8352 })
8353 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8354 .into_any();
8355
8356 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8357
8358 let mut origin = scrolled_content_origin + point(target_x, target_y)
8359 - point(
8360 if flag_on_right {
8361 POLE_WIDTH
8362 } else {
8363 size.width - POLE_WIDTH
8364 },
8365 size.height - line_height,
8366 );
8367
8368 origin.x = origin.x.max(content_origin.x);
8369
8370 element.prepaint_at(origin, window, cx);
8371
8372 Some((element, origin))
8373 }
8374
8375 fn render_edit_prediction_scroll_popover(
8376 &mut self,
8377 to_y: impl Fn(Size<Pixels>) -> Pixels,
8378 scroll_icon: IconName,
8379 visible_row_range: Range<DisplayRow>,
8380 line_layouts: &[LineWithInvisibles],
8381 newest_selection_head: Option<DisplayPoint>,
8382 scrolled_content_origin: gpui::Point<Pixels>,
8383 window: &mut Window,
8384 cx: &mut App,
8385 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8386 let mut element = self
8387 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8388 .into_any();
8389
8390 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8391
8392 let cursor = newest_selection_head?;
8393 let cursor_row_layout =
8394 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8395 let cursor_column = cursor.column() as usize;
8396
8397 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8398
8399 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8400
8401 element.prepaint_at(origin, window, cx);
8402 Some((element, origin))
8403 }
8404
8405 fn render_edit_prediction_eager_jump_popover(
8406 &mut self,
8407 text_bounds: &Bounds<Pixels>,
8408 content_origin: gpui::Point<Pixels>,
8409 editor_snapshot: &EditorSnapshot,
8410 visible_row_range: Range<DisplayRow>,
8411 scroll_top: f32,
8412 scroll_bottom: f32,
8413 line_height: Pixels,
8414 scroll_pixel_position: gpui::Point<Pixels>,
8415 target_display_point: DisplayPoint,
8416 editor_width: Pixels,
8417 window: &mut Window,
8418 cx: &mut App,
8419 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8420 if target_display_point.row().as_f32() < scroll_top {
8421 let mut element = self
8422 .render_edit_prediction_line_popover(
8423 "Jump to Edit",
8424 Some(IconName::ArrowUp),
8425 window,
8426 cx,
8427 )?
8428 .into_any();
8429
8430 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8431 let offset = point(
8432 (text_bounds.size.width - size.width) / 2.,
8433 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8434 );
8435
8436 let origin = text_bounds.origin + offset;
8437 element.prepaint_at(origin, window, cx);
8438 Some((element, origin))
8439 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8440 let mut element = self
8441 .render_edit_prediction_line_popover(
8442 "Jump to Edit",
8443 Some(IconName::ArrowDown),
8444 window,
8445 cx,
8446 )?
8447 .into_any();
8448
8449 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8450 let offset = point(
8451 (text_bounds.size.width - size.width) / 2.,
8452 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8453 );
8454
8455 let origin = text_bounds.origin + offset;
8456 element.prepaint_at(origin, window, cx);
8457 Some((element, origin))
8458 } else {
8459 self.render_edit_prediction_end_of_line_popover(
8460 "Jump to Edit",
8461 editor_snapshot,
8462 visible_row_range,
8463 target_display_point,
8464 line_height,
8465 scroll_pixel_position,
8466 content_origin,
8467 editor_width,
8468 window,
8469 cx,
8470 )
8471 }
8472 }
8473
8474 fn render_edit_prediction_end_of_line_popover(
8475 self: &mut Editor,
8476 label: &'static str,
8477 editor_snapshot: &EditorSnapshot,
8478 visible_row_range: Range<DisplayRow>,
8479 target_display_point: DisplayPoint,
8480 line_height: Pixels,
8481 scroll_pixel_position: gpui::Point<Pixels>,
8482 content_origin: gpui::Point<Pixels>,
8483 editor_width: Pixels,
8484 window: &mut Window,
8485 cx: &mut App,
8486 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8487 let target_line_end = DisplayPoint::new(
8488 target_display_point.row(),
8489 editor_snapshot.line_len(target_display_point.row()),
8490 );
8491
8492 let mut element = self
8493 .render_edit_prediction_line_popover(label, None, window, cx)?
8494 .into_any();
8495
8496 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8497
8498 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8499
8500 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8501 let mut origin = start_point
8502 + line_origin
8503 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8504 origin.x = origin.x.max(content_origin.x);
8505
8506 let max_x = content_origin.x + editor_width - size.width;
8507
8508 if origin.x > max_x {
8509 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8510
8511 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8512 origin.y += offset;
8513 IconName::ArrowUp
8514 } else {
8515 origin.y -= offset;
8516 IconName::ArrowDown
8517 };
8518
8519 element = self
8520 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8521 .into_any();
8522
8523 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8524
8525 origin.x = content_origin.x + editor_width - size.width - px(2.);
8526 }
8527
8528 element.prepaint_at(origin, window, cx);
8529 Some((element, origin))
8530 }
8531
8532 fn render_edit_prediction_diff_popover(
8533 self: &Editor,
8534 text_bounds: &Bounds<Pixels>,
8535 content_origin: gpui::Point<Pixels>,
8536 right_margin: Pixels,
8537 editor_snapshot: &EditorSnapshot,
8538 visible_row_range: Range<DisplayRow>,
8539 line_layouts: &[LineWithInvisibles],
8540 line_height: Pixels,
8541 scroll_pixel_position: gpui::Point<Pixels>,
8542 newest_selection_head: Option<DisplayPoint>,
8543 editor_width: Pixels,
8544 style: &EditorStyle,
8545 edits: &Vec<(Range<Anchor>, String)>,
8546 edit_preview: &Option<language::EditPreview>,
8547 snapshot: &language::BufferSnapshot,
8548 window: &mut Window,
8549 cx: &mut App,
8550 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8551 let edit_start = edits
8552 .first()
8553 .unwrap()
8554 .0
8555 .start
8556 .to_display_point(editor_snapshot);
8557 let edit_end = edits
8558 .last()
8559 .unwrap()
8560 .0
8561 .end
8562 .to_display_point(editor_snapshot);
8563
8564 let is_visible = visible_row_range.contains(&edit_start.row())
8565 || visible_row_range.contains(&edit_end.row());
8566 if !is_visible {
8567 return None;
8568 }
8569
8570 let highlighted_edits =
8571 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8572
8573 let styled_text = highlighted_edits.to_styled_text(&style.text);
8574 let line_count = highlighted_edits.text.lines().count();
8575
8576 const BORDER_WIDTH: Pixels = px(1.);
8577
8578 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8579 let has_keybind = keybind.is_some();
8580
8581 let mut element = h_flex()
8582 .items_start()
8583 .child(
8584 h_flex()
8585 .bg(cx.theme().colors().editor_background)
8586 .border(BORDER_WIDTH)
8587 .shadow_sm()
8588 .border_color(cx.theme().colors().border)
8589 .rounded_l_lg()
8590 .when(line_count > 1, |el| el.rounded_br_lg())
8591 .pr_1()
8592 .child(styled_text),
8593 )
8594 .child(
8595 h_flex()
8596 .h(line_height + BORDER_WIDTH * 2.)
8597 .px_1p5()
8598 .gap_1()
8599 // Workaround: For some reason, there's a gap if we don't do this
8600 .ml(-BORDER_WIDTH)
8601 .shadow(vec![gpui::BoxShadow {
8602 color: gpui::black().opacity(0.05),
8603 offset: point(px(1.), px(1.)),
8604 blur_radius: px(2.),
8605 spread_radius: px(0.),
8606 }])
8607 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8608 .border(BORDER_WIDTH)
8609 .border_color(cx.theme().colors().border)
8610 .rounded_r_lg()
8611 .id("edit_prediction_diff_popover_keybind")
8612 .when(!has_keybind, |el| {
8613 let status_colors = cx.theme().status();
8614
8615 el.bg(status_colors.error_background)
8616 .border_color(status_colors.error.opacity(0.6))
8617 .child(Icon::new(IconName::Info).color(Color::Error))
8618 .cursor_default()
8619 .hoverable_tooltip(move |_window, cx| {
8620 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8621 })
8622 })
8623 .children(keybind),
8624 )
8625 .into_any();
8626
8627 let longest_row =
8628 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8629 let longest_line_width = if visible_row_range.contains(&longest_row) {
8630 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8631 } else {
8632 layout_line(
8633 longest_row,
8634 editor_snapshot,
8635 style,
8636 editor_width,
8637 |_| false,
8638 window,
8639 cx,
8640 )
8641 .width
8642 };
8643
8644 let viewport_bounds =
8645 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8646 right: -right_margin,
8647 ..Default::default()
8648 });
8649
8650 let x_after_longest =
8651 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8652 - scroll_pixel_position.x;
8653
8654 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8655
8656 // Fully visible if it can be displayed within the window (allow overlapping other
8657 // panes). However, this is only allowed if the popover starts within text_bounds.
8658 let can_position_to_the_right = x_after_longest < text_bounds.right()
8659 && x_after_longest + element_bounds.width < viewport_bounds.right();
8660
8661 let mut origin = if can_position_to_the_right {
8662 point(
8663 x_after_longest,
8664 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8665 - scroll_pixel_position.y,
8666 )
8667 } else {
8668 let cursor_row = newest_selection_head.map(|head| head.row());
8669 let above_edit = edit_start
8670 .row()
8671 .0
8672 .checked_sub(line_count as u32)
8673 .map(DisplayRow);
8674 let below_edit = Some(edit_end.row() + 1);
8675 let above_cursor =
8676 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8677 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8678
8679 // Place the edit popover adjacent to the edit if there is a location
8680 // available that is onscreen and does not obscure the cursor. Otherwise,
8681 // place it adjacent to the cursor.
8682 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8683 .into_iter()
8684 .flatten()
8685 .find(|&start_row| {
8686 let end_row = start_row + line_count as u32;
8687 visible_row_range.contains(&start_row)
8688 && visible_row_range.contains(&end_row)
8689 && cursor_row.map_or(true, |cursor_row| {
8690 !((start_row..end_row).contains(&cursor_row))
8691 })
8692 })?;
8693
8694 content_origin
8695 + point(
8696 -scroll_pixel_position.x,
8697 row_target.as_f32() * line_height - scroll_pixel_position.y,
8698 )
8699 };
8700
8701 origin.x -= BORDER_WIDTH;
8702
8703 window.defer_draw(element, origin, 1);
8704
8705 // Do not return an element, since it will already be drawn due to defer_draw.
8706 None
8707 }
8708
8709 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8710 px(30.)
8711 }
8712
8713 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8714 if self.read_only(cx) {
8715 cx.theme().players().read_only()
8716 } else {
8717 self.style.as_ref().unwrap().local_player
8718 }
8719 }
8720
8721 fn render_edit_prediction_accept_keybind(
8722 &self,
8723 window: &mut Window,
8724 cx: &App,
8725 ) -> Option<AnyElement> {
8726 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8727 let accept_keystroke = accept_binding.keystroke()?;
8728
8729 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8730
8731 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8732 Color::Accent
8733 } else {
8734 Color::Muted
8735 };
8736
8737 h_flex()
8738 .px_0p5()
8739 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8740 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8741 .text_size(TextSize::XSmall.rems(cx))
8742 .child(h_flex().children(ui::render_modifiers(
8743 &accept_keystroke.modifiers,
8744 PlatformStyle::platform(),
8745 Some(modifiers_color),
8746 Some(IconSize::XSmall.rems().into()),
8747 true,
8748 )))
8749 .when(is_platform_style_mac, |parent| {
8750 parent.child(accept_keystroke.key.clone())
8751 })
8752 .when(!is_platform_style_mac, |parent| {
8753 parent.child(
8754 Key::new(
8755 util::capitalize(&accept_keystroke.key),
8756 Some(Color::Default),
8757 )
8758 .size(Some(IconSize::XSmall.rems().into())),
8759 )
8760 })
8761 .into_any()
8762 .into()
8763 }
8764
8765 fn render_edit_prediction_line_popover(
8766 &self,
8767 label: impl Into<SharedString>,
8768 icon: Option<IconName>,
8769 window: &mut Window,
8770 cx: &App,
8771 ) -> Option<Stateful<Div>> {
8772 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8773
8774 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8775 let has_keybind = keybind.is_some();
8776
8777 let result = h_flex()
8778 .id("ep-line-popover")
8779 .py_0p5()
8780 .pl_1()
8781 .pr(padding_right)
8782 .gap_1()
8783 .rounded_md()
8784 .border_1()
8785 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8786 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8787 .shadow_sm()
8788 .when(!has_keybind, |el| {
8789 let status_colors = cx.theme().status();
8790
8791 el.bg(status_colors.error_background)
8792 .border_color(status_colors.error.opacity(0.6))
8793 .pl_2()
8794 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8795 .cursor_default()
8796 .hoverable_tooltip(move |_window, cx| {
8797 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8798 })
8799 })
8800 .children(keybind)
8801 .child(
8802 Label::new(label)
8803 .size(LabelSize::Small)
8804 .when(!has_keybind, |el| {
8805 el.color(cx.theme().status().error.into()).strikethrough()
8806 }),
8807 )
8808 .when(!has_keybind, |el| {
8809 el.child(
8810 h_flex().ml_1().child(
8811 Icon::new(IconName::Info)
8812 .size(IconSize::Small)
8813 .color(cx.theme().status().error.into()),
8814 ),
8815 )
8816 })
8817 .when_some(icon, |element, icon| {
8818 element.child(
8819 div()
8820 .mt(px(1.5))
8821 .child(Icon::new(icon).size(IconSize::Small)),
8822 )
8823 });
8824
8825 Some(result)
8826 }
8827
8828 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8829 let accent_color = cx.theme().colors().text_accent;
8830 let editor_bg_color = cx.theme().colors().editor_background;
8831 editor_bg_color.blend(accent_color.opacity(0.1))
8832 }
8833
8834 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8835 let accent_color = cx.theme().colors().text_accent;
8836 let editor_bg_color = cx.theme().colors().editor_background;
8837 editor_bg_color.blend(accent_color.opacity(0.6))
8838 }
8839
8840 fn render_edit_prediction_cursor_popover(
8841 &self,
8842 min_width: Pixels,
8843 max_width: Pixels,
8844 cursor_point: Point,
8845 style: &EditorStyle,
8846 accept_keystroke: Option<&gpui::Keystroke>,
8847 _window: &Window,
8848 cx: &mut Context<Editor>,
8849 ) -> Option<AnyElement> {
8850 let provider = self.edit_prediction_provider.as_ref()?;
8851
8852 if provider.provider.needs_terms_acceptance(cx) {
8853 return Some(
8854 h_flex()
8855 .min_w(min_width)
8856 .flex_1()
8857 .px_2()
8858 .py_1()
8859 .gap_3()
8860 .elevation_2(cx)
8861 .hover(|style| style.bg(cx.theme().colors().element_hover))
8862 .id("accept-terms")
8863 .cursor_pointer()
8864 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8865 .on_click(cx.listener(|this, _event, window, cx| {
8866 cx.stop_propagation();
8867 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8868 window.dispatch_action(
8869 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8870 cx,
8871 );
8872 }))
8873 .child(
8874 h_flex()
8875 .flex_1()
8876 .gap_2()
8877 .child(Icon::new(IconName::ZedPredict))
8878 .child(Label::new("Accept Terms of Service"))
8879 .child(div().w_full())
8880 .child(
8881 Icon::new(IconName::ArrowUpRight)
8882 .color(Color::Muted)
8883 .size(IconSize::Small),
8884 )
8885 .into_any_element(),
8886 )
8887 .into_any(),
8888 );
8889 }
8890
8891 let is_refreshing = provider.provider.is_refreshing(cx);
8892
8893 fn pending_completion_container() -> Div {
8894 h_flex()
8895 .h_full()
8896 .flex_1()
8897 .gap_2()
8898 .child(Icon::new(IconName::ZedPredict))
8899 }
8900
8901 let completion = match &self.active_inline_completion {
8902 Some(prediction) => {
8903 if !self.has_visible_completions_menu() {
8904 const RADIUS: Pixels = px(6.);
8905 const BORDER_WIDTH: Pixels = px(1.);
8906
8907 return Some(
8908 h_flex()
8909 .elevation_2(cx)
8910 .border(BORDER_WIDTH)
8911 .border_color(cx.theme().colors().border)
8912 .when(accept_keystroke.is_none(), |el| {
8913 el.border_color(cx.theme().status().error)
8914 })
8915 .rounded(RADIUS)
8916 .rounded_tl(px(0.))
8917 .overflow_hidden()
8918 .child(div().px_1p5().child(match &prediction.completion {
8919 InlineCompletion::Move { target, snapshot } => {
8920 use text::ToPoint as _;
8921 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8922 {
8923 Icon::new(IconName::ZedPredictDown)
8924 } else {
8925 Icon::new(IconName::ZedPredictUp)
8926 }
8927 }
8928 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8929 }))
8930 .child(
8931 h_flex()
8932 .gap_1()
8933 .py_1()
8934 .px_2()
8935 .rounded_r(RADIUS - BORDER_WIDTH)
8936 .border_l_1()
8937 .border_color(cx.theme().colors().border)
8938 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8939 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8940 el.child(
8941 Label::new("Hold")
8942 .size(LabelSize::Small)
8943 .when(accept_keystroke.is_none(), |el| {
8944 el.strikethrough()
8945 })
8946 .line_height_style(LineHeightStyle::UiLabel),
8947 )
8948 })
8949 .id("edit_prediction_cursor_popover_keybind")
8950 .when(accept_keystroke.is_none(), |el| {
8951 let status_colors = cx.theme().status();
8952
8953 el.bg(status_colors.error_background)
8954 .border_color(status_colors.error.opacity(0.6))
8955 .child(Icon::new(IconName::Info).color(Color::Error))
8956 .cursor_default()
8957 .hoverable_tooltip(move |_window, cx| {
8958 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8959 .into()
8960 })
8961 })
8962 .when_some(
8963 accept_keystroke.as_ref(),
8964 |el, accept_keystroke| {
8965 el.child(h_flex().children(ui::render_modifiers(
8966 &accept_keystroke.modifiers,
8967 PlatformStyle::platform(),
8968 Some(Color::Default),
8969 Some(IconSize::XSmall.rems().into()),
8970 false,
8971 )))
8972 },
8973 ),
8974 )
8975 .into_any(),
8976 );
8977 }
8978
8979 self.render_edit_prediction_cursor_popover_preview(
8980 prediction,
8981 cursor_point,
8982 style,
8983 cx,
8984 )?
8985 }
8986
8987 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8988 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8989 stale_completion,
8990 cursor_point,
8991 style,
8992 cx,
8993 )?,
8994
8995 None => {
8996 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8997 }
8998 },
8999
9000 None => pending_completion_container().child(Label::new("No Prediction")),
9001 };
9002
9003 let completion = if is_refreshing {
9004 completion
9005 .with_animation(
9006 "loading-completion",
9007 Animation::new(Duration::from_secs(2))
9008 .repeat()
9009 .with_easing(pulsating_between(0.4, 0.8)),
9010 |label, delta| label.opacity(delta),
9011 )
9012 .into_any_element()
9013 } else {
9014 completion.into_any_element()
9015 };
9016
9017 let has_completion = self.active_inline_completion.is_some();
9018
9019 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9020 Some(
9021 h_flex()
9022 .min_w(min_width)
9023 .max_w(max_width)
9024 .flex_1()
9025 .elevation_2(cx)
9026 .border_color(cx.theme().colors().border)
9027 .child(
9028 div()
9029 .flex_1()
9030 .py_1()
9031 .px_2()
9032 .overflow_hidden()
9033 .child(completion),
9034 )
9035 .when_some(accept_keystroke, |el, accept_keystroke| {
9036 if !accept_keystroke.modifiers.modified() {
9037 return el;
9038 }
9039
9040 el.child(
9041 h_flex()
9042 .h_full()
9043 .border_l_1()
9044 .rounded_r_lg()
9045 .border_color(cx.theme().colors().border)
9046 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9047 .gap_1()
9048 .py_1()
9049 .px_2()
9050 .child(
9051 h_flex()
9052 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9053 .when(is_platform_style_mac, |parent| parent.gap_1())
9054 .child(h_flex().children(ui::render_modifiers(
9055 &accept_keystroke.modifiers,
9056 PlatformStyle::platform(),
9057 Some(if !has_completion {
9058 Color::Muted
9059 } else {
9060 Color::Default
9061 }),
9062 None,
9063 false,
9064 ))),
9065 )
9066 .child(Label::new("Preview").into_any_element())
9067 .opacity(if has_completion { 1.0 } else { 0.4 }),
9068 )
9069 })
9070 .into_any(),
9071 )
9072 }
9073
9074 fn render_edit_prediction_cursor_popover_preview(
9075 &self,
9076 completion: &InlineCompletionState,
9077 cursor_point: Point,
9078 style: &EditorStyle,
9079 cx: &mut Context<Editor>,
9080 ) -> Option<Div> {
9081 use text::ToPoint as _;
9082
9083 fn render_relative_row_jump(
9084 prefix: impl Into<String>,
9085 current_row: u32,
9086 target_row: u32,
9087 ) -> Div {
9088 let (row_diff, arrow) = if target_row < current_row {
9089 (current_row - target_row, IconName::ArrowUp)
9090 } else {
9091 (target_row - current_row, IconName::ArrowDown)
9092 };
9093
9094 h_flex()
9095 .child(
9096 Label::new(format!("{}{}", prefix.into(), row_diff))
9097 .color(Color::Muted)
9098 .size(LabelSize::Small),
9099 )
9100 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9101 }
9102
9103 match &completion.completion {
9104 InlineCompletion::Move {
9105 target, snapshot, ..
9106 } => Some(
9107 h_flex()
9108 .px_2()
9109 .gap_2()
9110 .flex_1()
9111 .child(
9112 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9113 Icon::new(IconName::ZedPredictDown)
9114 } else {
9115 Icon::new(IconName::ZedPredictUp)
9116 },
9117 )
9118 .child(Label::new("Jump to Edit")),
9119 ),
9120
9121 InlineCompletion::Edit {
9122 edits,
9123 edit_preview,
9124 snapshot,
9125 display_mode: _,
9126 } => {
9127 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9128
9129 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
9130 &snapshot,
9131 &edits,
9132 edit_preview.as_ref()?,
9133 true,
9134 cx,
9135 )
9136 .first_line_preview();
9137
9138 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9139 .with_default_highlights(&style.text, highlighted_edits.highlights);
9140
9141 let preview = h_flex()
9142 .gap_1()
9143 .min_w_16()
9144 .child(styled_text)
9145 .when(has_more_lines, |parent| parent.child("…"));
9146
9147 let left = if first_edit_row != cursor_point.row {
9148 render_relative_row_jump("", cursor_point.row, first_edit_row)
9149 .into_any_element()
9150 } else {
9151 Icon::new(IconName::ZedPredict).into_any_element()
9152 };
9153
9154 Some(
9155 h_flex()
9156 .h_full()
9157 .flex_1()
9158 .gap_2()
9159 .pr_1()
9160 .overflow_x_hidden()
9161 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9162 .child(left)
9163 .child(preview),
9164 )
9165 }
9166 }
9167 }
9168
9169 pub fn render_context_menu(
9170 &self,
9171 style: &EditorStyle,
9172 max_height_in_lines: u32,
9173 window: &mut Window,
9174 cx: &mut Context<Editor>,
9175 ) -> Option<AnyElement> {
9176 let menu = self.context_menu.borrow();
9177 let menu = menu.as_ref()?;
9178 if !menu.visible() {
9179 return None;
9180 };
9181 Some(menu.render(style, max_height_in_lines, window, cx))
9182 }
9183
9184 fn render_context_menu_aside(
9185 &mut self,
9186 max_size: Size<Pixels>,
9187 window: &mut Window,
9188 cx: &mut Context<Editor>,
9189 ) -> Option<AnyElement> {
9190 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9191 if menu.visible() {
9192 menu.render_aside(max_size, window, cx)
9193 } else {
9194 None
9195 }
9196 })
9197 }
9198
9199 fn hide_context_menu(
9200 &mut self,
9201 window: &mut Window,
9202 cx: &mut Context<Self>,
9203 ) -> Option<CodeContextMenu> {
9204 cx.notify();
9205 self.completion_tasks.clear();
9206 let context_menu = self.context_menu.borrow_mut().take();
9207 self.stale_inline_completion_in_menu.take();
9208 self.update_visible_inline_completion(window, cx);
9209 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9210 if let Some(completion_provider) = &self.completion_provider {
9211 completion_provider.selection_changed(None, window, cx);
9212 }
9213 }
9214 context_menu
9215 }
9216
9217 fn show_snippet_choices(
9218 &mut self,
9219 choices: &Vec<String>,
9220 selection: Range<Anchor>,
9221 cx: &mut Context<Self>,
9222 ) {
9223 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9224 (Some(a), Some(b)) if a == b => a,
9225 _ => {
9226 log::error!("expected anchor range to have matching buffer IDs");
9227 return;
9228 }
9229 };
9230 let multi_buffer = self.buffer().read(cx);
9231 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9232 return;
9233 };
9234
9235 let id = post_inc(&mut self.next_completion_id);
9236 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9237 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9238 CompletionsMenu::new_snippet_choices(
9239 id,
9240 true,
9241 choices,
9242 selection,
9243 buffer,
9244 snippet_sort_order,
9245 ),
9246 ));
9247 }
9248
9249 pub fn insert_snippet(
9250 &mut self,
9251 insertion_ranges: &[Range<usize>],
9252 snippet: Snippet,
9253 window: &mut Window,
9254 cx: &mut Context<Self>,
9255 ) -> Result<()> {
9256 struct Tabstop<T> {
9257 is_end_tabstop: bool,
9258 ranges: Vec<Range<T>>,
9259 choices: Option<Vec<String>>,
9260 }
9261
9262 let tabstops = self.buffer.update(cx, |buffer, cx| {
9263 let snippet_text: Arc<str> = snippet.text.clone().into();
9264 let edits = insertion_ranges
9265 .iter()
9266 .cloned()
9267 .map(|range| (range, snippet_text.clone()));
9268 let autoindent_mode = AutoindentMode::Block {
9269 original_indent_columns: Vec::new(),
9270 };
9271 buffer.edit(edits, Some(autoindent_mode), cx);
9272
9273 let snapshot = &*buffer.read(cx);
9274 let snippet = &snippet;
9275 snippet
9276 .tabstops
9277 .iter()
9278 .map(|tabstop| {
9279 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9280 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9281 });
9282 let mut tabstop_ranges = tabstop
9283 .ranges
9284 .iter()
9285 .flat_map(|tabstop_range| {
9286 let mut delta = 0_isize;
9287 insertion_ranges.iter().map(move |insertion_range| {
9288 let insertion_start = insertion_range.start as isize + delta;
9289 delta +=
9290 snippet.text.len() as isize - insertion_range.len() as isize;
9291
9292 let start = ((insertion_start + tabstop_range.start) as usize)
9293 .min(snapshot.len());
9294 let end = ((insertion_start + tabstop_range.end) as usize)
9295 .min(snapshot.len());
9296 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9297 })
9298 })
9299 .collect::<Vec<_>>();
9300 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9301
9302 Tabstop {
9303 is_end_tabstop,
9304 ranges: tabstop_ranges,
9305 choices: tabstop.choices.clone(),
9306 }
9307 })
9308 .collect::<Vec<_>>()
9309 });
9310 if let Some(tabstop) = tabstops.first() {
9311 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9312 // Reverse order so that the first range is the newest created selection.
9313 // Completions will use it and autoscroll will prioritize it.
9314 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9315 });
9316
9317 if let Some(choices) = &tabstop.choices {
9318 if let Some(selection) = tabstop.ranges.first() {
9319 self.show_snippet_choices(choices, selection.clone(), cx)
9320 }
9321 }
9322
9323 // If we're already at the last tabstop and it's at the end of the snippet,
9324 // we're done, we don't need to keep the state around.
9325 if !tabstop.is_end_tabstop {
9326 let choices = tabstops
9327 .iter()
9328 .map(|tabstop| tabstop.choices.clone())
9329 .collect();
9330
9331 let ranges = tabstops
9332 .into_iter()
9333 .map(|tabstop| tabstop.ranges)
9334 .collect::<Vec<_>>();
9335
9336 self.snippet_stack.push(SnippetState {
9337 active_index: 0,
9338 ranges,
9339 choices,
9340 });
9341 }
9342
9343 // Check whether the just-entered snippet ends with an auto-closable bracket.
9344 if self.autoclose_regions.is_empty() {
9345 let snapshot = self.buffer.read(cx).snapshot(cx);
9346 for selection in &mut self.selections.all::<Point>(cx) {
9347 let selection_head = selection.head();
9348 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9349 continue;
9350 };
9351
9352 let mut bracket_pair = None;
9353 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9354 let prev_chars = snapshot
9355 .reversed_chars_at(selection_head)
9356 .collect::<String>();
9357 for (pair, enabled) in scope.brackets() {
9358 if enabled
9359 && pair.close
9360 && prev_chars.starts_with(pair.start.as_str())
9361 && next_chars.starts_with(pair.end.as_str())
9362 {
9363 bracket_pair = Some(pair.clone());
9364 break;
9365 }
9366 }
9367 if let Some(pair) = bracket_pair {
9368 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9369 let autoclose_enabled =
9370 self.use_autoclose && snapshot_settings.use_autoclose;
9371 if autoclose_enabled {
9372 let start = snapshot.anchor_after(selection_head);
9373 let end = snapshot.anchor_after(selection_head);
9374 self.autoclose_regions.push(AutocloseRegion {
9375 selection_id: selection.id,
9376 range: start..end,
9377 pair,
9378 });
9379 }
9380 }
9381 }
9382 }
9383 }
9384 Ok(())
9385 }
9386
9387 pub fn move_to_next_snippet_tabstop(
9388 &mut self,
9389 window: &mut Window,
9390 cx: &mut Context<Self>,
9391 ) -> bool {
9392 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9393 }
9394
9395 pub fn move_to_prev_snippet_tabstop(
9396 &mut self,
9397 window: &mut Window,
9398 cx: &mut Context<Self>,
9399 ) -> bool {
9400 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9401 }
9402
9403 pub fn move_to_snippet_tabstop(
9404 &mut self,
9405 bias: Bias,
9406 window: &mut Window,
9407 cx: &mut Context<Self>,
9408 ) -> bool {
9409 if let Some(mut snippet) = self.snippet_stack.pop() {
9410 match bias {
9411 Bias::Left => {
9412 if snippet.active_index > 0 {
9413 snippet.active_index -= 1;
9414 } else {
9415 self.snippet_stack.push(snippet);
9416 return false;
9417 }
9418 }
9419 Bias::Right => {
9420 if snippet.active_index + 1 < snippet.ranges.len() {
9421 snippet.active_index += 1;
9422 } else {
9423 self.snippet_stack.push(snippet);
9424 return false;
9425 }
9426 }
9427 }
9428 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9429 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9430 // Reverse order so that the first range is the newest created selection.
9431 // Completions will use it and autoscroll will prioritize it.
9432 s.select_ranges(current_ranges.iter().rev().cloned())
9433 });
9434
9435 if let Some(choices) = &snippet.choices[snippet.active_index] {
9436 if let Some(selection) = current_ranges.first() {
9437 self.show_snippet_choices(&choices, selection.clone(), cx);
9438 }
9439 }
9440
9441 // If snippet state is not at the last tabstop, push it back on the stack
9442 if snippet.active_index + 1 < snippet.ranges.len() {
9443 self.snippet_stack.push(snippet);
9444 }
9445 return true;
9446 }
9447 }
9448
9449 false
9450 }
9451
9452 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9453 self.transact(window, cx, |this, window, cx| {
9454 this.select_all(&SelectAll, window, cx);
9455 this.insert("", window, cx);
9456 });
9457 }
9458
9459 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9460 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9461 self.transact(window, cx, |this, window, cx| {
9462 this.select_autoclose_pair(window, cx);
9463 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9464 if !this.linked_edit_ranges.is_empty() {
9465 let selections = this.selections.all::<MultiBufferPoint>(cx);
9466 let snapshot = this.buffer.read(cx).snapshot(cx);
9467
9468 for selection in selections.iter() {
9469 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9470 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9471 if selection_start.buffer_id != selection_end.buffer_id {
9472 continue;
9473 }
9474 if let Some(ranges) =
9475 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9476 {
9477 for (buffer, entries) in ranges {
9478 linked_ranges.entry(buffer).or_default().extend(entries);
9479 }
9480 }
9481 }
9482 }
9483
9484 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9485 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9486 for selection in &mut selections {
9487 if selection.is_empty() {
9488 let old_head = selection.head();
9489 let mut new_head =
9490 movement::left(&display_map, old_head.to_display_point(&display_map))
9491 .to_point(&display_map);
9492 if let Some((buffer, line_buffer_range)) = display_map
9493 .buffer_snapshot
9494 .buffer_line_for_row(MultiBufferRow(old_head.row))
9495 {
9496 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9497 let indent_len = match indent_size.kind {
9498 IndentKind::Space => {
9499 buffer.settings_at(line_buffer_range.start, cx).tab_size
9500 }
9501 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9502 };
9503 if old_head.column <= indent_size.len && old_head.column > 0 {
9504 let indent_len = indent_len.get();
9505 new_head = cmp::min(
9506 new_head,
9507 MultiBufferPoint::new(
9508 old_head.row,
9509 ((old_head.column - 1) / indent_len) * indent_len,
9510 ),
9511 );
9512 }
9513 }
9514
9515 selection.set_head(new_head, SelectionGoal::None);
9516 }
9517 }
9518
9519 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9520 s.select(selections)
9521 });
9522 this.insert("", window, cx);
9523 let empty_str: Arc<str> = Arc::from("");
9524 for (buffer, edits) in linked_ranges {
9525 let snapshot = buffer.read(cx).snapshot();
9526 use text::ToPoint as TP;
9527
9528 let edits = edits
9529 .into_iter()
9530 .map(|range| {
9531 let end_point = TP::to_point(&range.end, &snapshot);
9532 let mut start_point = TP::to_point(&range.start, &snapshot);
9533
9534 if end_point == start_point {
9535 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9536 .saturating_sub(1);
9537 start_point =
9538 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9539 };
9540
9541 (start_point..end_point, empty_str.clone())
9542 })
9543 .sorted_by_key(|(range, _)| range.start)
9544 .collect::<Vec<_>>();
9545 buffer.update(cx, |this, cx| {
9546 this.edit(edits, None, cx);
9547 })
9548 }
9549 this.refresh_inline_completion(true, false, window, cx);
9550 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9551 });
9552 }
9553
9554 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9555 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9556 self.transact(window, cx, |this, window, cx| {
9557 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9558 s.move_with(|map, selection| {
9559 if selection.is_empty() {
9560 let cursor = movement::right(map, selection.head());
9561 selection.end = cursor;
9562 selection.reversed = true;
9563 selection.goal = SelectionGoal::None;
9564 }
9565 })
9566 });
9567 this.insert("", window, cx);
9568 this.refresh_inline_completion(true, false, window, cx);
9569 });
9570 }
9571
9572 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9573 if self.mode.is_single_line() {
9574 cx.propagate();
9575 return;
9576 }
9577
9578 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9579 if self.move_to_prev_snippet_tabstop(window, cx) {
9580 return;
9581 }
9582 self.outdent(&Outdent, window, cx);
9583 }
9584
9585 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9586 if self.mode.is_single_line() {
9587 cx.propagate();
9588 return;
9589 }
9590
9591 if self.move_to_next_snippet_tabstop(window, cx) {
9592 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9593 return;
9594 }
9595 if self.read_only(cx) {
9596 return;
9597 }
9598 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9599 let mut selections = self.selections.all_adjusted(cx);
9600 let buffer = self.buffer.read(cx);
9601 let snapshot = buffer.snapshot(cx);
9602 let rows_iter = selections.iter().map(|s| s.head().row);
9603 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9604
9605 let has_some_cursor_in_whitespace = selections
9606 .iter()
9607 .filter(|selection| selection.is_empty())
9608 .any(|selection| {
9609 let cursor = selection.head();
9610 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9611 cursor.column < current_indent.len
9612 });
9613
9614 let mut edits = Vec::new();
9615 let mut prev_edited_row = 0;
9616 let mut row_delta = 0;
9617 for selection in &mut selections {
9618 if selection.start.row != prev_edited_row {
9619 row_delta = 0;
9620 }
9621 prev_edited_row = selection.end.row;
9622
9623 // If the selection is non-empty, then increase the indentation of the selected lines.
9624 if !selection.is_empty() {
9625 row_delta =
9626 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9627 continue;
9628 }
9629
9630 let cursor = selection.head();
9631 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9632 if let Some(suggested_indent) =
9633 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9634 {
9635 // Don't do anything if already at suggested indent
9636 // and there is any other cursor which is not
9637 if has_some_cursor_in_whitespace
9638 && cursor.column == current_indent.len
9639 && current_indent.len == suggested_indent.len
9640 {
9641 continue;
9642 }
9643
9644 // Adjust line and move cursor to suggested indent
9645 // if cursor is not at suggested indent
9646 if cursor.column < suggested_indent.len
9647 && cursor.column <= current_indent.len
9648 && current_indent.len <= suggested_indent.len
9649 {
9650 selection.start = Point::new(cursor.row, suggested_indent.len);
9651 selection.end = selection.start;
9652 if row_delta == 0 {
9653 edits.extend(Buffer::edit_for_indent_size_adjustment(
9654 cursor.row,
9655 current_indent,
9656 suggested_indent,
9657 ));
9658 row_delta = suggested_indent.len - current_indent.len;
9659 }
9660 continue;
9661 }
9662
9663 // If current indent is more than suggested indent
9664 // only move cursor to current indent and skip indent
9665 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9666 selection.start = Point::new(cursor.row, current_indent.len);
9667 selection.end = selection.start;
9668 continue;
9669 }
9670 }
9671
9672 // Otherwise, insert a hard or soft tab.
9673 let settings = buffer.language_settings_at(cursor, cx);
9674 let tab_size = if settings.hard_tabs {
9675 IndentSize::tab()
9676 } else {
9677 let tab_size = settings.tab_size.get();
9678 let indent_remainder = snapshot
9679 .text_for_range(Point::new(cursor.row, 0)..cursor)
9680 .flat_map(str::chars)
9681 .fold(row_delta % tab_size, |counter: u32, c| {
9682 if c == '\t' {
9683 0
9684 } else {
9685 (counter + 1) % tab_size
9686 }
9687 });
9688
9689 let chars_to_next_tab_stop = tab_size - indent_remainder;
9690 IndentSize::spaces(chars_to_next_tab_stop)
9691 };
9692 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9693 selection.end = selection.start;
9694 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9695 row_delta += tab_size.len;
9696 }
9697
9698 self.transact(window, cx, |this, window, cx| {
9699 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9700 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9701 s.select(selections)
9702 });
9703 this.refresh_inline_completion(true, false, window, cx);
9704 });
9705 }
9706
9707 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9708 if self.read_only(cx) {
9709 return;
9710 }
9711 if self.mode.is_single_line() {
9712 cx.propagate();
9713 return;
9714 }
9715
9716 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9717 let mut selections = self.selections.all::<Point>(cx);
9718 let mut prev_edited_row = 0;
9719 let mut row_delta = 0;
9720 let mut edits = Vec::new();
9721 let buffer = self.buffer.read(cx);
9722 let snapshot = buffer.snapshot(cx);
9723 for selection in &mut selections {
9724 if selection.start.row != prev_edited_row {
9725 row_delta = 0;
9726 }
9727 prev_edited_row = selection.end.row;
9728
9729 row_delta =
9730 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9731 }
9732
9733 self.transact(window, cx, |this, window, cx| {
9734 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9735 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9736 s.select(selections)
9737 });
9738 });
9739 }
9740
9741 fn indent_selection(
9742 buffer: &MultiBuffer,
9743 snapshot: &MultiBufferSnapshot,
9744 selection: &mut Selection<Point>,
9745 edits: &mut Vec<(Range<Point>, String)>,
9746 delta_for_start_row: u32,
9747 cx: &App,
9748 ) -> u32 {
9749 let settings = buffer.language_settings_at(selection.start, cx);
9750 let tab_size = settings.tab_size.get();
9751 let indent_kind = if settings.hard_tabs {
9752 IndentKind::Tab
9753 } else {
9754 IndentKind::Space
9755 };
9756 let mut start_row = selection.start.row;
9757 let mut end_row = selection.end.row + 1;
9758
9759 // If a selection ends at the beginning of a line, don't indent
9760 // that last line.
9761 if selection.end.column == 0 && selection.end.row > selection.start.row {
9762 end_row -= 1;
9763 }
9764
9765 // Avoid re-indenting a row that has already been indented by a
9766 // previous selection, but still update this selection's column
9767 // to reflect that indentation.
9768 if delta_for_start_row > 0 {
9769 start_row += 1;
9770 selection.start.column += delta_for_start_row;
9771 if selection.end.row == selection.start.row {
9772 selection.end.column += delta_for_start_row;
9773 }
9774 }
9775
9776 let mut delta_for_end_row = 0;
9777 let has_multiple_rows = start_row + 1 != end_row;
9778 for row in start_row..end_row {
9779 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9780 let indent_delta = match (current_indent.kind, indent_kind) {
9781 (IndentKind::Space, IndentKind::Space) => {
9782 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9783 IndentSize::spaces(columns_to_next_tab_stop)
9784 }
9785 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9786 (_, IndentKind::Tab) => IndentSize::tab(),
9787 };
9788
9789 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9790 0
9791 } else {
9792 selection.start.column
9793 };
9794 let row_start = Point::new(row, start);
9795 edits.push((
9796 row_start..row_start,
9797 indent_delta.chars().collect::<String>(),
9798 ));
9799
9800 // Update this selection's endpoints to reflect the indentation.
9801 if row == selection.start.row {
9802 selection.start.column += indent_delta.len;
9803 }
9804 if row == selection.end.row {
9805 selection.end.column += indent_delta.len;
9806 delta_for_end_row = indent_delta.len;
9807 }
9808 }
9809
9810 if selection.start.row == selection.end.row {
9811 delta_for_start_row + delta_for_end_row
9812 } else {
9813 delta_for_end_row
9814 }
9815 }
9816
9817 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9818 if self.read_only(cx) {
9819 return;
9820 }
9821 if self.mode.is_single_line() {
9822 cx.propagate();
9823 return;
9824 }
9825
9826 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9827 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9828 let selections = self.selections.all::<Point>(cx);
9829 let mut deletion_ranges = Vec::new();
9830 let mut last_outdent = None;
9831 {
9832 let buffer = self.buffer.read(cx);
9833 let snapshot = buffer.snapshot(cx);
9834 for selection in &selections {
9835 let settings = buffer.language_settings_at(selection.start, cx);
9836 let tab_size = settings.tab_size.get();
9837 let mut rows = selection.spanned_rows(false, &display_map);
9838
9839 // Avoid re-outdenting a row that has already been outdented by a
9840 // previous selection.
9841 if let Some(last_row) = last_outdent {
9842 if last_row == rows.start {
9843 rows.start = rows.start.next_row();
9844 }
9845 }
9846 let has_multiple_rows = rows.len() > 1;
9847 for row in rows.iter_rows() {
9848 let indent_size = snapshot.indent_size_for_line(row);
9849 if indent_size.len > 0 {
9850 let deletion_len = match indent_size.kind {
9851 IndentKind::Space => {
9852 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9853 if columns_to_prev_tab_stop == 0 {
9854 tab_size
9855 } else {
9856 columns_to_prev_tab_stop
9857 }
9858 }
9859 IndentKind::Tab => 1,
9860 };
9861 let start = if has_multiple_rows
9862 || deletion_len > selection.start.column
9863 || indent_size.len < selection.start.column
9864 {
9865 0
9866 } else {
9867 selection.start.column - deletion_len
9868 };
9869 deletion_ranges.push(
9870 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9871 );
9872 last_outdent = Some(row);
9873 }
9874 }
9875 }
9876 }
9877
9878 self.transact(window, cx, |this, window, cx| {
9879 this.buffer.update(cx, |buffer, cx| {
9880 let empty_str: Arc<str> = Arc::default();
9881 buffer.edit(
9882 deletion_ranges
9883 .into_iter()
9884 .map(|range| (range, empty_str.clone())),
9885 None,
9886 cx,
9887 );
9888 });
9889 let selections = this.selections.all::<usize>(cx);
9890 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9891 s.select(selections)
9892 });
9893 });
9894 }
9895
9896 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9897 if self.read_only(cx) {
9898 return;
9899 }
9900 if self.mode.is_single_line() {
9901 cx.propagate();
9902 return;
9903 }
9904
9905 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9906 let selections = self
9907 .selections
9908 .all::<usize>(cx)
9909 .into_iter()
9910 .map(|s| s.range());
9911
9912 self.transact(window, cx, |this, window, cx| {
9913 this.buffer.update(cx, |buffer, cx| {
9914 buffer.autoindent_ranges(selections, cx);
9915 });
9916 let selections = this.selections.all::<usize>(cx);
9917 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9918 s.select(selections)
9919 });
9920 });
9921 }
9922
9923 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9924 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9925 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9926 let selections = self.selections.all::<Point>(cx);
9927
9928 let mut new_cursors = Vec::new();
9929 let mut edit_ranges = Vec::new();
9930 let mut selections = selections.iter().peekable();
9931 while let Some(selection) = selections.next() {
9932 let mut rows = selection.spanned_rows(false, &display_map);
9933 let goal_display_column = selection.head().to_display_point(&display_map).column();
9934
9935 // Accumulate contiguous regions of rows that we want to delete.
9936 while let Some(next_selection) = selections.peek() {
9937 let next_rows = next_selection.spanned_rows(false, &display_map);
9938 if next_rows.start <= rows.end {
9939 rows.end = next_rows.end;
9940 selections.next().unwrap();
9941 } else {
9942 break;
9943 }
9944 }
9945
9946 let buffer = &display_map.buffer_snapshot;
9947 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9948 let edit_end;
9949 let cursor_buffer_row;
9950 if buffer.max_point().row >= rows.end.0 {
9951 // If there's a line after the range, delete the \n from the end of the row range
9952 // and position the cursor on the next line.
9953 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9954 cursor_buffer_row = rows.end;
9955 } else {
9956 // If there isn't a line after the range, delete the \n from the line before the
9957 // start of the row range and position the cursor there.
9958 edit_start = edit_start.saturating_sub(1);
9959 edit_end = buffer.len();
9960 cursor_buffer_row = rows.start.previous_row();
9961 }
9962
9963 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9964 *cursor.column_mut() =
9965 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9966
9967 new_cursors.push((
9968 selection.id,
9969 buffer.anchor_after(cursor.to_point(&display_map)),
9970 ));
9971 edit_ranges.push(edit_start..edit_end);
9972 }
9973
9974 self.transact(window, cx, |this, window, cx| {
9975 let buffer = this.buffer.update(cx, |buffer, cx| {
9976 let empty_str: Arc<str> = Arc::default();
9977 buffer.edit(
9978 edit_ranges
9979 .into_iter()
9980 .map(|range| (range, empty_str.clone())),
9981 None,
9982 cx,
9983 );
9984 buffer.snapshot(cx)
9985 });
9986 let new_selections = new_cursors
9987 .into_iter()
9988 .map(|(id, cursor)| {
9989 let cursor = cursor.to_point(&buffer);
9990 Selection {
9991 id,
9992 start: cursor,
9993 end: cursor,
9994 reversed: false,
9995 goal: SelectionGoal::None,
9996 }
9997 })
9998 .collect();
9999
10000 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10001 s.select(new_selections);
10002 });
10003 });
10004 }
10005
10006 pub fn join_lines_impl(
10007 &mut self,
10008 insert_whitespace: bool,
10009 window: &mut Window,
10010 cx: &mut Context<Self>,
10011 ) {
10012 if self.read_only(cx) {
10013 return;
10014 }
10015 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10016 for selection in self.selections.all::<Point>(cx) {
10017 let start = MultiBufferRow(selection.start.row);
10018 // Treat single line selections as if they include the next line. Otherwise this action
10019 // would do nothing for single line selections individual cursors.
10020 let end = if selection.start.row == selection.end.row {
10021 MultiBufferRow(selection.start.row + 1)
10022 } else {
10023 MultiBufferRow(selection.end.row)
10024 };
10025
10026 if let Some(last_row_range) = row_ranges.last_mut() {
10027 if start <= last_row_range.end {
10028 last_row_range.end = end;
10029 continue;
10030 }
10031 }
10032 row_ranges.push(start..end);
10033 }
10034
10035 let snapshot = self.buffer.read(cx).snapshot(cx);
10036 let mut cursor_positions = Vec::new();
10037 for row_range in &row_ranges {
10038 let anchor = snapshot.anchor_before(Point::new(
10039 row_range.end.previous_row().0,
10040 snapshot.line_len(row_range.end.previous_row()),
10041 ));
10042 cursor_positions.push(anchor..anchor);
10043 }
10044
10045 self.transact(window, cx, |this, window, cx| {
10046 for row_range in row_ranges.into_iter().rev() {
10047 for row in row_range.iter_rows().rev() {
10048 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10049 let next_line_row = row.next_row();
10050 let indent = snapshot.indent_size_for_line(next_line_row);
10051 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10052
10053 let replace =
10054 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10055 " "
10056 } else {
10057 ""
10058 };
10059
10060 this.buffer.update(cx, |buffer, cx| {
10061 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10062 });
10063 }
10064 }
10065
10066 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10067 s.select_anchor_ranges(cursor_positions)
10068 });
10069 });
10070 }
10071
10072 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10073 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10074 self.join_lines_impl(true, window, cx);
10075 }
10076
10077 pub fn sort_lines_case_sensitive(
10078 &mut self,
10079 _: &SortLinesCaseSensitive,
10080 window: &mut Window,
10081 cx: &mut Context<Self>,
10082 ) {
10083 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10084 }
10085
10086 pub fn sort_lines_case_insensitive(
10087 &mut self,
10088 _: &SortLinesCaseInsensitive,
10089 window: &mut Window,
10090 cx: &mut Context<Self>,
10091 ) {
10092 self.manipulate_immutable_lines(window, cx, |lines| {
10093 lines.sort_by_key(|line| line.to_lowercase())
10094 })
10095 }
10096
10097 pub fn unique_lines_case_insensitive(
10098 &mut self,
10099 _: &UniqueLinesCaseInsensitive,
10100 window: &mut Window,
10101 cx: &mut Context<Self>,
10102 ) {
10103 self.manipulate_immutable_lines(window, cx, |lines| {
10104 let mut seen = HashSet::default();
10105 lines.retain(|line| seen.insert(line.to_lowercase()));
10106 })
10107 }
10108
10109 pub fn unique_lines_case_sensitive(
10110 &mut self,
10111 _: &UniqueLinesCaseSensitive,
10112 window: &mut Window,
10113 cx: &mut Context<Self>,
10114 ) {
10115 self.manipulate_immutable_lines(window, cx, |lines| {
10116 let mut seen = HashSet::default();
10117 lines.retain(|line| seen.insert(*line));
10118 })
10119 }
10120
10121 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10122 let Some(project) = self.project.clone() else {
10123 return;
10124 };
10125 self.reload(project, window, cx)
10126 .detach_and_notify_err(window, cx);
10127 }
10128
10129 pub fn restore_file(
10130 &mut self,
10131 _: &::git::RestoreFile,
10132 window: &mut Window,
10133 cx: &mut Context<Self>,
10134 ) {
10135 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10136 let mut buffer_ids = HashSet::default();
10137 let snapshot = self.buffer().read(cx).snapshot(cx);
10138 for selection in self.selections.all::<usize>(cx) {
10139 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10140 }
10141
10142 let buffer = self.buffer().read(cx);
10143 let ranges = buffer_ids
10144 .into_iter()
10145 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10146 .collect::<Vec<_>>();
10147
10148 self.restore_hunks_in_ranges(ranges, window, cx);
10149 }
10150
10151 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10152 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10153 let selections = self
10154 .selections
10155 .all(cx)
10156 .into_iter()
10157 .map(|s| s.range())
10158 .collect();
10159 self.restore_hunks_in_ranges(selections, window, cx);
10160 }
10161
10162 pub fn restore_hunks_in_ranges(
10163 &mut self,
10164 ranges: Vec<Range<Point>>,
10165 window: &mut Window,
10166 cx: &mut Context<Editor>,
10167 ) {
10168 let mut revert_changes = HashMap::default();
10169 let chunk_by = self
10170 .snapshot(window, cx)
10171 .hunks_for_ranges(ranges)
10172 .into_iter()
10173 .chunk_by(|hunk| hunk.buffer_id);
10174 for (buffer_id, hunks) in &chunk_by {
10175 let hunks = hunks.collect::<Vec<_>>();
10176 for hunk in &hunks {
10177 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10178 }
10179 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10180 }
10181 drop(chunk_by);
10182 if !revert_changes.is_empty() {
10183 self.transact(window, cx, |editor, window, cx| {
10184 editor.restore(revert_changes, window, cx);
10185 });
10186 }
10187 }
10188
10189 pub fn open_active_item_in_terminal(
10190 &mut self,
10191 _: &OpenInTerminal,
10192 window: &mut Window,
10193 cx: &mut Context<Self>,
10194 ) {
10195 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10196 let project_path = buffer.read(cx).project_path(cx)?;
10197 let project = self.project.as_ref()?.read(cx);
10198 let entry = project.entry_for_path(&project_path, cx)?;
10199 let parent = match &entry.canonical_path {
10200 Some(canonical_path) => canonical_path.to_path_buf(),
10201 None => project.absolute_path(&project_path, cx)?,
10202 }
10203 .parent()?
10204 .to_path_buf();
10205 Some(parent)
10206 }) {
10207 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10208 }
10209 }
10210
10211 fn set_breakpoint_context_menu(
10212 &mut self,
10213 display_row: DisplayRow,
10214 position: Option<Anchor>,
10215 clicked_point: gpui::Point<Pixels>,
10216 window: &mut Window,
10217 cx: &mut Context<Self>,
10218 ) {
10219 let source = self
10220 .buffer
10221 .read(cx)
10222 .snapshot(cx)
10223 .anchor_before(Point::new(display_row.0, 0u32));
10224
10225 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10226
10227 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10228 self,
10229 source,
10230 clicked_point,
10231 context_menu,
10232 window,
10233 cx,
10234 );
10235 }
10236
10237 fn add_edit_breakpoint_block(
10238 &mut self,
10239 anchor: Anchor,
10240 breakpoint: &Breakpoint,
10241 edit_action: BreakpointPromptEditAction,
10242 window: &mut Window,
10243 cx: &mut Context<Self>,
10244 ) {
10245 let weak_editor = cx.weak_entity();
10246 let bp_prompt = cx.new(|cx| {
10247 BreakpointPromptEditor::new(
10248 weak_editor,
10249 anchor,
10250 breakpoint.clone(),
10251 edit_action,
10252 window,
10253 cx,
10254 )
10255 });
10256
10257 let height = bp_prompt.update(cx, |this, cx| {
10258 this.prompt
10259 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10260 });
10261 let cloned_prompt = bp_prompt.clone();
10262 let blocks = vec![BlockProperties {
10263 style: BlockStyle::Sticky,
10264 placement: BlockPlacement::Above(anchor),
10265 height: Some(height),
10266 render: Arc::new(move |cx| {
10267 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10268 cloned_prompt.clone().into_any_element()
10269 }),
10270 priority: 0,
10271 render_in_minimap: true,
10272 }];
10273
10274 let focus_handle = bp_prompt.focus_handle(cx);
10275 window.focus(&focus_handle);
10276
10277 let block_ids = self.insert_blocks(blocks, None, cx);
10278 bp_prompt.update(cx, |prompt, _| {
10279 prompt.add_block_ids(block_ids);
10280 });
10281 }
10282
10283 pub(crate) fn breakpoint_at_row(
10284 &self,
10285 row: u32,
10286 window: &mut Window,
10287 cx: &mut Context<Self>,
10288 ) -> Option<(Anchor, Breakpoint)> {
10289 let snapshot = self.snapshot(window, cx);
10290 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10291
10292 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10293 }
10294
10295 pub(crate) fn breakpoint_at_anchor(
10296 &self,
10297 breakpoint_position: Anchor,
10298 snapshot: &EditorSnapshot,
10299 cx: &mut Context<Self>,
10300 ) -> Option<(Anchor, Breakpoint)> {
10301 let project = self.project.clone()?;
10302
10303 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10304 snapshot
10305 .buffer_snapshot
10306 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10307 })?;
10308
10309 let enclosing_excerpt = breakpoint_position.excerpt_id;
10310 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10311 let buffer_snapshot = buffer.read(cx).snapshot();
10312
10313 let row = buffer_snapshot
10314 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10315 .row;
10316
10317 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10318 let anchor_end = snapshot
10319 .buffer_snapshot
10320 .anchor_after(Point::new(row, line_len));
10321
10322 let bp = self
10323 .breakpoint_store
10324 .as_ref()?
10325 .read_with(cx, |breakpoint_store, cx| {
10326 breakpoint_store
10327 .breakpoints(
10328 &buffer,
10329 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10330 &buffer_snapshot,
10331 cx,
10332 )
10333 .next()
10334 .and_then(|(bp, _)| {
10335 let breakpoint_row = buffer_snapshot
10336 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10337 .row;
10338
10339 if breakpoint_row == row {
10340 snapshot
10341 .buffer_snapshot
10342 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10343 .map(|position| (position, bp.bp.clone()))
10344 } else {
10345 None
10346 }
10347 })
10348 });
10349 bp
10350 }
10351
10352 pub fn edit_log_breakpoint(
10353 &mut self,
10354 _: &EditLogBreakpoint,
10355 window: &mut Window,
10356 cx: &mut Context<Self>,
10357 ) {
10358 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10359 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10360 message: None,
10361 state: BreakpointState::Enabled,
10362 condition: None,
10363 hit_condition: None,
10364 });
10365
10366 self.add_edit_breakpoint_block(
10367 anchor,
10368 &breakpoint,
10369 BreakpointPromptEditAction::Log,
10370 window,
10371 cx,
10372 );
10373 }
10374 }
10375
10376 fn breakpoints_at_cursors(
10377 &self,
10378 window: &mut Window,
10379 cx: &mut Context<Self>,
10380 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10381 let snapshot = self.snapshot(window, cx);
10382 let cursors = self
10383 .selections
10384 .disjoint_anchors()
10385 .into_iter()
10386 .map(|selection| {
10387 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10388
10389 let breakpoint_position = self
10390 .breakpoint_at_row(cursor_position.row, window, cx)
10391 .map(|bp| bp.0)
10392 .unwrap_or_else(|| {
10393 snapshot
10394 .display_snapshot
10395 .buffer_snapshot
10396 .anchor_after(Point::new(cursor_position.row, 0))
10397 });
10398
10399 let breakpoint = self
10400 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10401 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10402
10403 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10404 })
10405 // 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.
10406 .collect::<HashMap<Anchor, _>>();
10407
10408 cursors.into_iter().collect()
10409 }
10410
10411 pub fn enable_breakpoint(
10412 &mut self,
10413 _: &crate::actions::EnableBreakpoint,
10414 window: &mut Window,
10415 cx: &mut Context<Self>,
10416 ) {
10417 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10418 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10419 continue;
10420 };
10421 self.edit_breakpoint_at_anchor(
10422 anchor,
10423 breakpoint,
10424 BreakpointEditAction::InvertState,
10425 cx,
10426 );
10427 }
10428 }
10429
10430 pub fn disable_breakpoint(
10431 &mut self,
10432 _: &crate::actions::DisableBreakpoint,
10433 window: &mut Window,
10434 cx: &mut Context<Self>,
10435 ) {
10436 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10437 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10438 continue;
10439 };
10440 self.edit_breakpoint_at_anchor(
10441 anchor,
10442 breakpoint,
10443 BreakpointEditAction::InvertState,
10444 cx,
10445 );
10446 }
10447 }
10448
10449 pub fn toggle_breakpoint(
10450 &mut self,
10451 _: &crate::actions::ToggleBreakpoint,
10452 window: &mut Window,
10453 cx: &mut Context<Self>,
10454 ) {
10455 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10456 if let Some(breakpoint) = breakpoint {
10457 self.edit_breakpoint_at_anchor(
10458 anchor,
10459 breakpoint,
10460 BreakpointEditAction::Toggle,
10461 cx,
10462 );
10463 } else {
10464 self.edit_breakpoint_at_anchor(
10465 anchor,
10466 Breakpoint::new_standard(),
10467 BreakpointEditAction::Toggle,
10468 cx,
10469 );
10470 }
10471 }
10472 }
10473
10474 pub fn edit_breakpoint_at_anchor(
10475 &mut self,
10476 breakpoint_position: Anchor,
10477 breakpoint: Breakpoint,
10478 edit_action: BreakpointEditAction,
10479 cx: &mut Context<Self>,
10480 ) {
10481 let Some(breakpoint_store) = &self.breakpoint_store else {
10482 return;
10483 };
10484
10485 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10486 if breakpoint_position == Anchor::min() {
10487 self.buffer()
10488 .read(cx)
10489 .excerpt_buffer_ids()
10490 .into_iter()
10491 .next()
10492 } else {
10493 None
10494 }
10495 }) else {
10496 return;
10497 };
10498
10499 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10500 return;
10501 };
10502
10503 breakpoint_store.update(cx, |breakpoint_store, cx| {
10504 breakpoint_store.toggle_breakpoint(
10505 buffer,
10506 BreakpointWithPosition {
10507 position: breakpoint_position.text_anchor,
10508 bp: breakpoint,
10509 },
10510 edit_action,
10511 cx,
10512 );
10513 });
10514
10515 cx.notify();
10516 }
10517
10518 #[cfg(any(test, feature = "test-support"))]
10519 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10520 self.breakpoint_store.clone()
10521 }
10522
10523 pub fn prepare_restore_change(
10524 &self,
10525 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10526 hunk: &MultiBufferDiffHunk,
10527 cx: &mut App,
10528 ) -> Option<()> {
10529 if hunk.is_created_file() {
10530 return None;
10531 }
10532 let buffer = self.buffer.read(cx);
10533 let diff = buffer.diff_for(hunk.buffer_id)?;
10534 let buffer = buffer.buffer(hunk.buffer_id)?;
10535 let buffer = buffer.read(cx);
10536 let original_text = diff
10537 .read(cx)
10538 .base_text()
10539 .as_rope()
10540 .slice(hunk.diff_base_byte_range.clone());
10541 let buffer_snapshot = buffer.snapshot();
10542 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10543 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10544 probe
10545 .0
10546 .start
10547 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10548 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10549 }) {
10550 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10551 Some(())
10552 } else {
10553 None
10554 }
10555 }
10556
10557 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10558 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10559 }
10560
10561 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10562 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10563 }
10564
10565 fn manipulate_lines<M>(
10566 &mut self,
10567 window: &mut Window,
10568 cx: &mut Context<Self>,
10569 mut manipulate: M,
10570 ) where
10571 M: FnMut(&str) -> LineManipulationResult,
10572 {
10573 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10574
10575 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10576 let buffer = self.buffer.read(cx).snapshot(cx);
10577
10578 let mut edits = Vec::new();
10579
10580 let selections = self.selections.all::<Point>(cx);
10581 let mut selections = selections.iter().peekable();
10582 let mut contiguous_row_selections = Vec::new();
10583 let mut new_selections = Vec::new();
10584 let mut added_lines = 0;
10585 let mut removed_lines = 0;
10586
10587 while let Some(selection) = selections.next() {
10588 let (start_row, end_row) = consume_contiguous_rows(
10589 &mut contiguous_row_selections,
10590 selection,
10591 &display_map,
10592 &mut selections,
10593 );
10594
10595 let start_point = Point::new(start_row.0, 0);
10596 let end_point = Point::new(
10597 end_row.previous_row().0,
10598 buffer.line_len(end_row.previous_row()),
10599 );
10600 let text = buffer
10601 .text_for_range(start_point..end_point)
10602 .collect::<String>();
10603
10604 let LineManipulationResult {
10605 new_text,
10606 line_count_before,
10607 line_count_after,
10608 } = manipulate(&text);
10609
10610 edits.push((start_point..end_point, new_text));
10611
10612 // Selections must change based on added and removed line count
10613 let start_row =
10614 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10615 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10616 new_selections.push(Selection {
10617 id: selection.id,
10618 start: start_row,
10619 end: end_row,
10620 goal: SelectionGoal::None,
10621 reversed: selection.reversed,
10622 });
10623
10624 if line_count_after > line_count_before {
10625 added_lines += line_count_after - line_count_before;
10626 } else if line_count_before > line_count_after {
10627 removed_lines += line_count_before - line_count_after;
10628 }
10629 }
10630
10631 self.transact(window, cx, |this, window, cx| {
10632 let buffer = this.buffer.update(cx, |buffer, cx| {
10633 buffer.edit(edits, None, cx);
10634 buffer.snapshot(cx)
10635 });
10636
10637 // Recalculate offsets on newly edited buffer
10638 let new_selections = new_selections
10639 .iter()
10640 .map(|s| {
10641 let start_point = Point::new(s.start.0, 0);
10642 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10643 Selection {
10644 id: s.id,
10645 start: buffer.point_to_offset(start_point),
10646 end: buffer.point_to_offset(end_point),
10647 goal: s.goal,
10648 reversed: s.reversed,
10649 }
10650 })
10651 .collect();
10652
10653 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10654 s.select(new_selections);
10655 });
10656
10657 this.request_autoscroll(Autoscroll::fit(), cx);
10658 });
10659 }
10660
10661 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10662 self.manipulate_text(window, cx, |text| {
10663 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10664 if has_upper_case_characters {
10665 text.to_lowercase()
10666 } else {
10667 text.to_uppercase()
10668 }
10669 })
10670 }
10671
10672 fn manipulate_immutable_lines<Fn>(
10673 &mut self,
10674 window: &mut Window,
10675 cx: &mut Context<Self>,
10676 mut callback: Fn,
10677 ) where
10678 Fn: FnMut(&mut Vec<&str>),
10679 {
10680 self.manipulate_lines(window, cx, |text| {
10681 let mut lines: Vec<&str> = text.split('\n').collect();
10682 let line_count_before = lines.len();
10683
10684 callback(&mut lines);
10685
10686 LineManipulationResult {
10687 new_text: lines.join("\n"),
10688 line_count_before,
10689 line_count_after: lines.len(),
10690 }
10691 });
10692 }
10693
10694 fn manipulate_mutable_lines<Fn>(
10695 &mut self,
10696 window: &mut Window,
10697 cx: &mut Context<Self>,
10698 mut callback: Fn,
10699 ) where
10700 Fn: FnMut(&mut Vec<Cow<'_, str>>),
10701 {
10702 self.manipulate_lines(window, cx, |text| {
10703 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
10704 let line_count_before = lines.len();
10705
10706 callback(&mut lines);
10707
10708 LineManipulationResult {
10709 new_text: lines.join("\n"),
10710 line_count_before,
10711 line_count_after: lines.len(),
10712 }
10713 });
10714 }
10715
10716 pub fn convert_indentation_to_spaces(
10717 &mut self,
10718 _: &ConvertIndentationToSpaces,
10719 window: &mut Window,
10720 cx: &mut Context<Self>,
10721 ) {
10722 let settings = self.buffer.read(cx).language_settings(cx);
10723 let tab_size = settings.tab_size.get() as usize;
10724
10725 self.manipulate_mutable_lines(window, cx, |lines| {
10726 // Allocates a reasonably sized scratch buffer once for the whole loop
10727 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10728 // Avoids recomputing spaces that could be inserted many times
10729 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10730 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10731 .collect();
10732
10733 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10734 let mut chars = line.as_ref().chars();
10735 let mut col = 0;
10736 let mut changed = false;
10737
10738 while let Some(ch) = chars.next() {
10739 match ch {
10740 ' ' => {
10741 reindented_line.push(' ');
10742 col += 1;
10743 }
10744 '\t' => {
10745 // \t are converted to spaces depending on the current column
10746 let spaces_len = tab_size - (col % tab_size);
10747 reindented_line.extend(&space_cache[spaces_len - 1]);
10748 col += spaces_len;
10749 changed = true;
10750 }
10751 _ => {
10752 // If we dont append before break, the character is consumed
10753 reindented_line.push(ch);
10754 break;
10755 }
10756 }
10757 }
10758
10759 if !changed {
10760 reindented_line.clear();
10761 continue;
10762 }
10763 // Append the rest of the line and replace old reference with new one
10764 reindented_line.extend(chars);
10765 *line = Cow::Owned(reindented_line.clone());
10766 reindented_line.clear();
10767 }
10768 });
10769 }
10770
10771 pub fn convert_indentation_to_tabs(
10772 &mut self,
10773 _: &ConvertIndentationToTabs,
10774 window: &mut Window,
10775 cx: &mut Context<Self>,
10776 ) {
10777 let settings = self.buffer.read(cx).language_settings(cx);
10778 let tab_size = settings.tab_size.get() as usize;
10779
10780 self.manipulate_mutable_lines(window, cx, |lines| {
10781 // Allocates a reasonably sized buffer once for the whole loop
10782 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10783 // Avoids recomputing spaces that could be inserted many times
10784 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10785 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10786 .collect();
10787
10788 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10789 let mut chars = line.chars();
10790 let mut spaces_count = 0;
10791 let mut first_non_indent_char = None;
10792 let mut changed = false;
10793
10794 while let Some(ch) = chars.next() {
10795 match ch {
10796 ' ' => {
10797 // Keep track of spaces. Append \t when we reach tab_size
10798 spaces_count += 1;
10799 changed = true;
10800 if spaces_count == tab_size {
10801 reindented_line.push('\t');
10802 spaces_count = 0;
10803 }
10804 }
10805 '\t' => {
10806 reindented_line.push('\t');
10807 spaces_count = 0;
10808 }
10809 _ => {
10810 // Dont append it yet, we might have remaining spaces
10811 first_non_indent_char = Some(ch);
10812 break;
10813 }
10814 }
10815 }
10816
10817 if !changed {
10818 reindented_line.clear();
10819 continue;
10820 }
10821 // Remaining spaces that didn't make a full tab stop
10822 if spaces_count > 0 {
10823 reindented_line.extend(&space_cache[spaces_count - 1]);
10824 }
10825 // If we consume an extra character that was not indentation, add it back
10826 if let Some(extra_char) = first_non_indent_char {
10827 reindented_line.push(extra_char);
10828 }
10829 // Append the rest of the line and replace old reference with new one
10830 reindented_line.extend(chars);
10831 *line = Cow::Owned(reindented_line.clone());
10832 reindented_line.clear();
10833 }
10834 });
10835 }
10836
10837 pub fn convert_to_upper_case(
10838 &mut self,
10839 _: &ConvertToUpperCase,
10840 window: &mut Window,
10841 cx: &mut Context<Self>,
10842 ) {
10843 self.manipulate_text(window, cx, |text| text.to_uppercase())
10844 }
10845
10846 pub fn convert_to_lower_case(
10847 &mut self,
10848 _: &ConvertToLowerCase,
10849 window: &mut Window,
10850 cx: &mut Context<Self>,
10851 ) {
10852 self.manipulate_text(window, cx, |text| text.to_lowercase())
10853 }
10854
10855 pub fn convert_to_title_case(
10856 &mut self,
10857 _: &ConvertToTitleCase,
10858 window: &mut Window,
10859 cx: &mut Context<Self>,
10860 ) {
10861 self.manipulate_text(window, cx, |text| {
10862 text.split('\n')
10863 .map(|line| line.to_case(Case::Title))
10864 .join("\n")
10865 })
10866 }
10867
10868 pub fn convert_to_snake_case(
10869 &mut self,
10870 _: &ConvertToSnakeCase,
10871 window: &mut Window,
10872 cx: &mut Context<Self>,
10873 ) {
10874 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10875 }
10876
10877 pub fn convert_to_kebab_case(
10878 &mut self,
10879 _: &ConvertToKebabCase,
10880 window: &mut Window,
10881 cx: &mut Context<Self>,
10882 ) {
10883 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10884 }
10885
10886 pub fn convert_to_upper_camel_case(
10887 &mut self,
10888 _: &ConvertToUpperCamelCase,
10889 window: &mut Window,
10890 cx: &mut Context<Self>,
10891 ) {
10892 self.manipulate_text(window, cx, |text| {
10893 text.split('\n')
10894 .map(|line| line.to_case(Case::UpperCamel))
10895 .join("\n")
10896 })
10897 }
10898
10899 pub fn convert_to_lower_camel_case(
10900 &mut self,
10901 _: &ConvertToLowerCamelCase,
10902 window: &mut Window,
10903 cx: &mut Context<Self>,
10904 ) {
10905 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10906 }
10907
10908 pub fn convert_to_opposite_case(
10909 &mut self,
10910 _: &ConvertToOppositeCase,
10911 window: &mut Window,
10912 cx: &mut Context<Self>,
10913 ) {
10914 self.manipulate_text(window, cx, |text| {
10915 text.chars()
10916 .fold(String::with_capacity(text.len()), |mut t, c| {
10917 if c.is_uppercase() {
10918 t.extend(c.to_lowercase());
10919 } else {
10920 t.extend(c.to_uppercase());
10921 }
10922 t
10923 })
10924 })
10925 }
10926
10927 pub fn convert_to_rot13(
10928 &mut self,
10929 _: &ConvertToRot13,
10930 window: &mut Window,
10931 cx: &mut Context<Self>,
10932 ) {
10933 self.manipulate_text(window, cx, |text| {
10934 text.chars()
10935 .map(|c| match c {
10936 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10937 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10938 _ => c,
10939 })
10940 .collect()
10941 })
10942 }
10943
10944 pub fn convert_to_rot47(
10945 &mut self,
10946 _: &ConvertToRot47,
10947 window: &mut Window,
10948 cx: &mut Context<Self>,
10949 ) {
10950 self.manipulate_text(window, cx, |text| {
10951 text.chars()
10952 .map(|c| {
10953 let code_point = c as u32;
10954 if code_point >= 33 && code_point <= 126 {
10955 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10956 }
10957 c
10958 })
10959 .collect()
10960 })
10961 }
10962
10963 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10964 where
10965 Fn: FnMut(&str) -> String,
10966 {
10967 let buffer = self.buffer.read(cx).snapshot(cx);
10968
10969 let mut new_selections = Vec::new();
10970 let mut edits = Vec::new();
10971 let mut selection_adjustment = 0i32;
10972
10973 for selection in self.selections.all::<usize>(cx) {
10974 let selection_is_empty = selection.is_empty();
10975
10976 let (start, end) = if selection_is_empty {
10977 let (word_range, _) = buffer.surrounding_word(selection.start, false);
10978 (word_range.start, word_range.end)
10979 } else {
10980 (selection.start, selection.end)
10981 };
10982
10983 let text = buffer.text_for_range(start..end).collect::<String>();
10984 let old_length = text.len() as i32;
10985 let text = callback(&text);
10986
10987 new_selections.push(Selection {
10988 start: (start as i32 - selection_adjustment) as usize,
10989 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10990 goal: SelectionGoal::None,
10991 ..selection
10992 });
10993
10994 selection_adjustment += old_length - text.len() as i32;
10995
10996 edits.push((start..end, text));
10997 }
10998
10999 self.transact(window, cx, |this, window, cx| {
11000 this.buffer.update(cx, |buffer, cx| {
11001 buffer.edit(edits, None, cx);
11002 });
11003
11004 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11005 s.select(new_selections);
11006 });
11007
11008 this.request_autoscroll(Autoscroll::fit(), cx);
11009 });
11010 }
11011
11012 pub fn move_selection_on_drop(
11013 &mut self,
11014 selection: &Selection<Anchor>,
11015 target: DisplayPoint,
11016 is_cut: bool,
11017 window: &mut Window,
11018 cx: &mut Context<Self>,
11019 ) {
11020 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11021 let buffer = &display_map.buffer_snapshot;
11022 let mut edits = Vec::new();
11023 let insert_point = display_map
11024 .clip_point(target, Bias::Left)
11025 .to_point(&display_map);
11026 let text = buffer
11027 .text_for_range(selection.start..selection.end)
11028 .collect::<String>();
11029 if is_cut {
11030 edits.push(((selection.start..selection.end), String::new()));
11031 }
11032 let insert_anchor = buffer.anchor_before(insert_point);
11033 edits.push(((insert_anchor..insert_anchor), text));
11034 let last_edit_start = insert_anchor.bias_left(buffer);
11035 let last_edit_end = insert_anchor.bias_right(buffer);
11036 self.transact(window, cx, |this, window, cx| {
11037 this.buffer.update(cx, |buffer, cx| {
11038 buffer.edit(edits, None, cx);
11039 });
11040 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11041 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11042 });
11043 });
11044 }
11045
11046 pub fn clear_selection_drag_state(&mut self) {
11047 self.selection_drag_state = SelectionDragState::None;
11048 }
11049
11050 pub fn duplicate(
11051 &mut self,
11052 upwards: bool,
11053 whole_lines: bool,
11054 window: &mut Window,
11055 cx: &mut Context<Self>,
11056 ) {
11057 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11058
11059 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11060 let buffer = &display_map.buffer_snapshot;
11061 let selections = self.selections.all::<Point>(cx);
11062
11063 let mut edits = Vec::new();
11064 let mut selections_iter = selections.iter().peekable();
11065 while let Some(selection) = selections_iter.next() {
11066 let mut rows = selection.spanned_rows(false, &display_map);
11067 // duplicate line-wise
11068 if whole_lines || selection.start == selection.end {
11069 // Avoid duplicating the same lines twice.
11070 while let Some(next_selection) = selections_iter.peek() {
11071 let next_rows = next_selection.spanned_rows(false, &display_map);
11072 if next_rows.start < rows.end {
11073 rows.end = next_rows.end;
11074 selections_iter.next().unwrap();
11075 } else {
11076 break;
11077 }
11078 }
11079
11080 // Copy the text from the selected row region and splice it either at the start
11081 // or end of the region.
11082 let start = Point::new(rows.start.0, 0);
11083 let end = Point::new(
11084 rows.end.previous_row().0,
11085 buffer.line_len(rows.end.previous_row()),
11086 );
11087 let text = buffer
11088 .text_for_range(start..end)
11089 .chain(Some("\n"))
11090 .collect::<String>();
11091 let insert_location = if upwards {
11092 Point::new(rows.end.0, 0)
11093 } else {
11094 start
11095 };
11096 edits.push((insert_location..insert_location, text));
11097 } else {
11098 // duplicate character-wise
11099 let start = selection.start;
11100 let end = selection.end;
11101 let text = buffer.text_for_range(start..end).collect::<String>();
11102 edits.push((selection.end..selection.end, text));
11103 }
11104 }
11105
11106 self.transact(window, cx, |this, _, cx| {
11107 this.buffer.update(cx, |buffer, cx| {
11108 buffer.edit(edits, None, cx);
11109 });
11110
11111 this.request_autoscroll(Autoscroll::fit(), cx);
11112 });
11113 }
11114
11115 pub fn duplicate_line_up(
11116 &mut self,
11117 _: &DuplicateLineUp,
11118 window: &mut Window,
11119 cx: &mut Context<Self>,
11120 ) {
11121 self.duplicate(true, true, window, cx);
11122 }
11123
11124 pub fn duplicate_line_down(
11125 &mut self,
11126 _: &DuplicateLineDown,
11127 window: &mut Window,
11128 cx: &mut Context<Self>,
11129 ) {
11130 self.duplicate(false, true, window, cx);
11131 }
11132
11133 pub fn duplicate_selection(
11134 &mut self,
11135 _: &DuplicateSelection,
11136 window: &mut Window,
11137 cx: &mut Context<Self>,
11138 ) {
11139 self.duplicate(false, false, window, cx);
11140 }
11141
11142 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11143 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11144 if self.mode.is_single_line() {
11145 cx.propagate();
11146 return;
11147 }
11148
11149 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11150 let buffer = self.buffer.read(cx).snapshot(cx);
11151
11152 let mut edits = Vec::new();
11153 let mut unfold_ranges = Vec::new();
11154 let mut refold_creases = Vec::new();
11155
11156 let selections = self.selections.all::<Point>(cx);
11157 let mut selections = selections.iter().peekable();
11158 let mut contiguous_row_selections = Vec::new();
11159 let mut new_selections = Vec::new();
11160
11161 while let Some(selection) = selections.next() {
11162 // Find all the selections that span a contiguous row range
11163 let (start_row, end_row) = consume_contiguous_rows(
11164 &mut contiguous_row_selections,
11165 selection,
11166 &display_map,
11167 &mut selections,
11168 );
11169
11170 // Move the text spanned by the row range to be before the line preceding the row range
11171 if start_row.0 > 0 {
11172 let range_to_move = Point::new(
11173 start_row.previous_row().0,
11174 buffer.line_len(start_row.previous_row()),
11175 )
11176 ..Point::new(
11177 end_row.previous_row().0,
11178 buffer.line_len(end_row.previous_row()),
11179 );
11180 let insertion_point = display_map
11181 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11182 .0;
11183
11184 // Don't move lines across excerpts
11185 if buffer
11186 .excerpt_containing(insertion_point..range_to_move.end)
11187 .is_some()
11188 {
11189 let text = buffer
11190 .text_for_range(range_to_move.clone())
11191 .flat_map(|s| s.chars())
11192 .skip(1)
11193 .chain(['\n'])
11194 .collect::<String>();
11195
11196 edits.push((
11197 buffer.anchor_after(range_to_move.start)
11198 ..buffer.anchor_before(range_to_move.end),
11199 String::new(),
11200 ));
11201 let insertion_anchor = buffer.anchor_after(insertion_point);
11202 edits.push((insertion_anchor..insertion_anchor, text));
11203
11204 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11205
11206 // Move selections up
11207 new_selections.extend(contiguous_row_selections.drain(..).map(
11208 |mut selection| {
11209 selection.start.row -= row_delta;
11210 selection.end.row -= row_delta;
11211 selection
11212 },
11213 ));
11214
11215 // Move folds up
11216 unfold_ranges.push(range_to_move.clone());
11217 for fold in display_map.folds_in_range(
11218 buffer.anchor_before(range_to_move.start)
11219 ..buffer.anchor_after(range_to_move.end),
11220 ) {
11221 let mut start = fold.range.start.to_point(&buffer);
11222 let mut end = fold.range.end.to_point(&buffer);
11223 start.row -= row_delta;
11224 end.row -= row_delta;
11225 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11226 }
11227 }
11228 }
11229
11230 // If we didn't move line(s), preserve the existing selections
11231 new_selections.append(&mut contiguous_row_selections);
11232 }
11233
11234 self.transact(window, cx, |this, window, cx| {
11235 this.unfold_ranges(&unfold_ranges, true, true, cx);
11236 this.buffer.update(cx, |buffer, cx| {
11237 for (range, text) in edits {
11238 buffer.edit([(range, text)], None, cx);
11239 }
11240 });
11241 this.fold_creases(refold_creases, true, window, cx);
11242 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11243 s.select(new_selections);
11244 })
11245 });
11246 }
11247
11248 pub fn move_line_down(
11249 &mut self,
11250 _: &MoveLineDown,
11251 window: &mut Window,
11252 cx: &mut Context<Self>,
11253 ) {
11254 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11255 if self.mode.is_single_line() {
11256 cx.propagate();
11257 return;
11258 }
11259
11260 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11261 let buffer = self.buffer.read(cx).snapshot(cx);
11262
11263 let mut edits = Vec::new();
11264 let mut unfold_ranges = Vec::new();
11265 let mut refold_creases = Vec::new();
11266
11267 let selections = self.selections.all::<Point>(cx);
11268 let mut selections = selections.iter().peekable();
11269 let mut contiguous_row_selections = Vec::new();
11270 let mut new_selections = Vec::new();
11271
11272 while let Some(selection) = selections.next() {
11273 // Find all the selections that span a contiguous row range
11274 let (start_row, end_row) = consume_contiguous_rows(
11275 &mut contiguous_row_selections,
11276 selection,
11277 &display_map,
11278 &mut selections,
11279 );
11280
11281 // Move the text spanned by the row range to be after the last line of the row range
11282 if end_row.0 <= buffer.max_point().row {
11283 let range_to_move =
11284 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11285 let insertion_point = display_map
11286 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11287 .0;
11288
11289 // Don't move lines across excerpt boundaries
11290 if buffer
11291 .excerpt_containing(range_to_move.start..insertion_point)
11292 .is_some()
11293 {
11294 let mut text = String::from("\n");
11295 text.extend(buffer.text_for_range(range_to_move.clone()));
11296 text.pop(); // Drop trailing newline
11297 edits.push((
11298 buffer.anchor_after(range_to_move.start)
11299 ..buffer.anchor_before(range_to_move.end),
11300 String::new(),
11301 ));
11302 let insertion_anchor = buffer.anchor_after(insertion_point);
11303 edits.push((insertion_anchor..insertion_anchor, text));
11304
11305 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11306
11307 // Move selections down
11308 new_selections.extend(contiguous_row_selections.drain(..).map(
11309 |mut selection| {
11310 selection.start.row += row_delta;
11311 selection.end.row += row_delta;
11312 selection
11313 },
11314 ));
11315
11316 // Move folds down
11317 unfold_ranges.push(range_to_move.clone());
11318 for fold in display_map.folds_in_range(
11319 buffer.anchor_before(range_to_move.start)
11320 ..buffer.anchor_after(range_to_move.end),
11321 ) {
11322 let mut start = fold.range.start.to_point(&buffer);
11323 let mut end = fold.range.end.to_point(&buffer);
11324 start.row += row_delta;
11325 end.row += row_delta;
11326 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11327 }
11328 }
11329 }
11330
11331 // If we didn't move line(s), preserve the existing selections
11332 new_selections.append(&mut contiguous_row_selections);
11333 }
11334
11335 self.transact(window, cx, |this, window, cx| {
11336 this.unfold_ranges(&unfold_ranges, true, true, cx);
11337 this.buffer.update(cx, |buffer, cx| {
11338 for (range, text) in edits {
11339 buffer.edit([(range, text)], None, cx);
11340 }
11341 });
11342 this.fold_creases(refold_creases, true, window, cx);
11343 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11344 s.select(new_selections)
11345 });
11346 });
11347 }
11348
11349 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11350 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11351 let text_layout_details = &self.text_layout_details(window);
11352 self.transact(window, cx, |this, window, cx| {
11353 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11354 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11355 s.move_with(|display_map, selection| {
11356 if !selection.is_empty() {
11357 return;
11358 }
11359
11360 let mut head = selection.head();
11361 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11362 if head.column() == display_map.line_len(head.row()) {
11363 transpose_offset = display_map
11364 .buffer_snapshot
11365 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11366 }
11367
11368 if transpose_offset == 0 {
11369 return;
11370 }
11371
11372 *head.column_mut() += 1;
11373 head = display_map.clip_point(head, Bias::Right);
11374 let goal = SelectionGoal::HorizontalPosition(
11375 display_map
11376 .x_for_display_point(head, text_layout_details)
11377 .into(),
11378 );
11379 selection.collapse_to(head, goal);
11380
11381 let transpose_start = display_map
11382 .buffer_snapshot
11383 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11384 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11385 let transpose_end = display_map
11386 .buffer_snapshot
11387 .clip_offset(transpose_offset + 1, Bias::Right);
11388 if let Some(ch) =
11389 display_map.buffer_snapshot.chars_at(transpose_start).next()
11390 {
11391 edits.push((transpose_start..transpose_offset, String::new()));
11392 edits.push((transpose_end..transpose_end, ch.to_string()));
11393 }
11394 }
11395 });
11396 edits
11397 });
11398 this.buffer
11399 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11400 let selections = this.selections.all::<usize>(cx);
11401 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11402 s.select(selections);
11403 });
11404 });
11405 }
11406
11407 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11408 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11409 if self.mode.is_single_line() {
11410 cx.propagate();
11411 return;
11412 }
11413
11414 self.rewrap_impl(RewrapOptions::default(), cx)
11415 }
11416
11417 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11418 let buffer = self.buffer.read(cx).snapshot(cx);
11419 let selections = self.selections.all::<Point>(cx);
11420
11421 // Shrink and split selections to respect paragraph boundaries.
11422 let ranges = selections.into_iter().flat_map(|selection| {
11423 let language_settings = buffer.language_settings_at(selection.head(), cx);
11424 let language_scope = buffer.language_scope_at(selection.head());
11425
11426 let Some(start_row) = (selection.start.row..=selection.end.row)
11427 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11428 else {
11429 return vec![];
11430 };
11431 let Some(end_row) = (selection.start.row..=selection.end.row)
11432 .rev()
11433 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11434 else {
11435 return vec![];
11436 };
11437
11438 let mut row = start_row;
11439 let mut ranges = Vec::new();
11440 while let Some(blank_row) =
11441 (row..end_row).find(|row| buffer.is_line_blank(MultiBufferRow(*row)))
11442 {
11443 let next_paragraph_start = (blank_row + 1..=end_row)
11444 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11445 .unwrap();
11446 ranges.push((
11447 language_settings.clone(),
11448 language_scope.clone(),
11449 Point::new(row, 0)..Point::new(blank_row - 1, 0),
11450 ));
11451 row = next_paragraph_start;
11452 }
11453 ranges.push((
11454 language_settings.clone(),
11455 language_scope.clone(),
11456 Point::new(row, 0)..Point::new(end_row, 0),
11457 ));
11458
11459 ranges
11460 });
11461
11462 let mut edits = Vec::new();
11463 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11464
11465 for (language_settings, language_scope, range) in ranges {
11466 let mut start_row = range.start.row;
11467 let mut end_row = range.end.row;
11468
11469 // Skip selections that overlap with a range that has already been rewrapped.
11470 let selection_range = start_row..end_row;
11471 if rewrapped_row_ranges
11472 .iter()
11473 .any(|range| range.overlaps(&selection_range))
11474 {
11475 continue;
11476 }
11477
11478 let tab_size = language_settings.tab_size;
11479
11480 // Since not all lines in the selection may be at the same indent
11481 // level, choose the indent size that is the most common between all
11482 // of the lines.
11483 //
11484 // If there is a tie, we use the deepest indent.
11485 let (indent_size, indent_end) = {
11486 let mut indent_size_occurrences = HashMap::default();
11487 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
11488
11489 for row in start_row..=end_row {
11490 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11491 rows_by_indent_size.entry(indent).or_default().push(row);
11492 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
11493 }
11494
11495 let indent_size = indent_size_occurrences
11496 .into_iter()
11497 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
11498 .map(|(indent, _)| indent)
11499 .unwrap_or_default();
11500 let row = rows_by_indent_size[&indent_size][0];
11501 let indent_end = Point::new(row, indent_size.len);
11502
11503 (indent_size, indent_end)
11504 };
11505
11506 let mut line_prefix = indent_size.chars().collect::<String>();
11507
11508 let mut inside_comment = false;
11509 if let Some(comment_prefix) = language_scope.and_then(|language| {
11510 language
11511 .line_comment_prefixes()
11512 .iter()
11513 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11514 .cloned()
11515 }) {
11516 line_prefix.push_str(&comment_prefix);
11517 inside_comment = true;
11518 }
11519
11520 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11521 RewrapBehavior::InComments => inside_comment,
11522 RewrapBehavior::InSelections => !range.is_empty(),
11523 RewrapBehavior::Anywhere => true,
11524 };
11525
11526 let should_rewrap = options.override_language_settings
11527 || allow_rewrap_based_on_language
11528 || self.hard_wrap.is_some();
11529 if !should_rewrap {
11530 continue;
11531 }
11532
11533 if range.is_empty() {
11534 'expand_upwards: while start_row > 0 {
11535 let prev_row = start_row - 1;
11536 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11537 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11538 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11539 {
11540 start_row = prev_row;
11541 } else {
11542 break 'expand_upwards;
11543 }
11544 }
11545
11546 'expand_downwards: while end_row < buffer.max_point().row {
11547 let next_row = end_row + 1;
11548 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11549 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11550 && !buffer.is_line_blank(MultiBufferRow(next_row))
11551 {
11552 end_row = next_row;
11553 } else {
11554 break 'expand_downwards;
11555 }
11556 }
11557 }
11558
11559 let start = Point::new(start_row, 0);
11560 let start_offset = start.to_offset(&buffer);
11561 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11562 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11563 let Some(lines_without_prefixes) = selection_text
11564 .lines()
11565 .map(|line| {
11566 line.strip_prefix(&line_prefix)
11567 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
11568 .with_context(|| {
11569 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11570 })
11571 })
11572 .collect::<Result<Vec<_>, _>>()
11573 .log_err()
11574 else {
11575 continue;
11576 };
11577
11578 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11579 buffer
11580 .language_settings_at(Point::new(start_row, 0), cx)
11581 .preferred_line_length as usize
11582 });
11583 let wrapped_text = wrap_with_prefix(
11584 line_prefix,
11585 lines_without_prefixes.join("\n"),
11586 wrap_column,
11587 tab_size,
11588 options.preserve_existing_whitespace,
11589 );
11590
11591 // TODO: should always use char-based diff while still supporting cursor behavior that
11592 // matches vim.
11593 let mut diff_options = DiffOptions::default();
11594 if options.override_language_settings {
11595 diff_options.max_word_diff_len = 0;
11596 diff_options.max_word_diff_line_count = 0;
11597 } else {
11598 diff_options.max_word_diff_len = usize::MAX;
11599 diff_options.max_word_diff_line_count = usize::MAX;
11600 }
11601
11602 for (old_range, new_text) in
11603 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11604 {
11605 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11606 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11607 edits.push((edit_start..edit_end, new_text));
11608 }
11609
11610 rewrapped_row_ranges.push(start_row..=end_row);
11611 }
11612
11613 self.buffer
11614 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11615 }
11616
11617 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11618 let mut text = String::new();
11619 let buffer = self.buffer.read(cx).snapshot(cx);
11620 let mut selections = self.selections.all::<Point>(cx);
11621 let mut clipboard_selections = Vec::with_capacity(selections.len());
11622 {
11623 let max_point = buffer.max_point();
11624 let mut is_first = true;
11625 for selection in &mut selections {
11626 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11627 if is_entire_line {
11628 selection.start = Point::new(selection.start.row, 0);
11629 if !selection.is_empty() && selection.end.column == 0 {
11630 selection.end = cmp::min(max_point, selection.end);
11631 } else {
11632 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11633 }
11634 selection.goal = SelectionGoal::None;
11635 }
11636 if is_first {
11637 is_first = false;
11638 } else {
11639 text += "\n";
11640 }
11641 let mut len = 0;
11642 for chunk in buffer.text_for_range(selection.start..selection.end) {
11643 text.push_str(chunk);
11644 len += chunk.len();
11645 }
11646 clipboard_selections.push(ClipboardSelection {
11647 len,
11648 is_entire_line,
11649 first_line_indent: buffer
11650 .indent_size_for_line(MultiBufferRow(selection.start.row))
11651 .len,
11652 });
11653 }
11654 }
11655
11656 self.transact(window, cx, |this, window, cx| {
11657 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11658 s.select(selections);
11659 });
11660 this.insert("", window, cx);
11661 });
11662 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11663 }
11664
11665 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11666 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11667 let item = self.cut_common(window, cx);
11668 cx.write_to_clipboard(item);
11669 }
11670
11671 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11672 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11673 self.change_selections(None, window, cx, |s| {
11674 s.move_with(|snapshot, sel| {
11675 if sel.is_empty() {
11676 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11677 }
11678 });
11679 });
11680 let item = self.cut_common(window, cx);
11681 cx.set_global(KillRing(item))
11682 }
11683
11684 pub fn kill_ring_yank(
11685 &mut self,
11686 _: &KillRingYank,
11687 window: &mut Window,
11688 cx: &mut Context<Self>,
11689 ) {
11690 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11691 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11692 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11693 (kill_ring.text().to_string(), kill_ring.metadata_json())
11694 } else {
11695 return;
11696 }
11697 } else {
11698 return;
11699 };
11700 self.do_paste(&text, metadata, false, window, cx);
11701 }
11702
11703 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11704 self.do_copy(true, cx);
11705 }
11706
11707 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11708 self.do_copy(false, cx);
11709 }
11710
11711 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11712 let selections = self.selections.all::<Point>(cx);
11713 let buffer = self.buffer.read(cx).read(cx);
11714 let mut text = String::new();
11715
11716 let mut clipboard_selections = Vec::with_capacity(selections.len());
11717 {
11718 let max_point = buffer.max_point();
11719 let mut is_first = true;
11720 for selection in &selections {
11721 let mut start = selection.start;
11722 let mut end = selection.end;
11723 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11724 if is_entire_line {
11725 start = Point::new(start.row, 0);
11726 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11727 }
11728
11729 let mut trimmed_selections = Vec::new();
11730 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11731 let row = MultiBufferRow(start.row);
11732 let first_indent = buffer.indent_size_for_line(row);
11733 if first_indent.len == 0 || start.column > first_indent.len {
11734 trimmed_selections.push(start..end);
11735 } else {
11736 trimmed_selections.push(
11737 Point::new(row.0, first_indent.len)
11738 ..Point::new(row.0, buffer.line_len(row)),
11739 );
11740 for row in start.row + 1..=end.row {
11741 let mut line_len = buffer.line_len(MultiBufferRow(row));
11742 if row == end.row {
11743 line_len = end.column;
11744 }
11745 if line_len == 0 {
11746 trimmed_selections
11747 .push(Point::new(row, 0)..Point::new(row, line_len));
11748 continue;
11749 }
11750 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11751 if row_indent_size.len >= first_indent.len {
11752 trimmed_selections.push(
11753 Point::new(row, first_indent.len)..Point::new(row, line_len),
11754 );
11755 } else {
11756 trimmed_selections.clear();
11757 trimmed_selections.push(start..end);
11758 break;
11759 }
11760 }
11761 }
11762 } else {
11763 trimmed_selections.push(start..end);
11764 }
11765
11766 for trimmed_range in trimmed_selections {
11767 if is_first {
11768 is_first = false;
11769 } else {
11770 text += "\n";
11771 }
11772 let mut len = 0;
11773 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11774 text.push_str(chunk);
11775 len += chunk.len();
11776 }
11777 clipboard_selections.push(ClipboardSelection {
11778 len,
11779 is_entire_line,
11780 first_line_indent: buffer
11781 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11782 .len,
11783 });
11784 }
11785 }
11786 }
11787
11788 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11789 text,
11790 clipboard_selections,
11791 ));
11792 }
11793
11794 pub fn do_paste(
11795 &mut self,
11796 text: &String,
11797 clipboard_selections: Option<Vec<ClipboardSelection>>,
11798 handle_entire_lines: bool,
11799 window: &mut Window,
11800 cx: &mut Context<Self>,
11801 ) {
11802 if self.read_only(cx) {
11803 return;
11804 }
11805
11806 let clipboard_text = Cow::Borrowed(text);
11807
11808 self.transact(window, cx, |this, window, cx| {
11809 if let Some(mut clipboard_selections) = clipboard_selections {
11810 let old_selections = this.selections.all::<usize>(cx);
11811 let all_selections_were_entire_line =
11812 clipboard_selections.iter().all(|s| s.is_entire_line);
11813 let first_selection_indent_column =
11814 clipboard_selections.first().map(|s| s.first_line_indent);
11815 if clipboard_selections.len() != old_selections.len() {
11816 clipboard_selections.drain(..);
11817 }
11818 let cursor_offset = this.selections.last::<usize>(cx).head();
11819 let mut auto_indent_on_paste = true;
11820
11821 this.buffer.update(cx, |buffer, cx| {
11822 let snapshot = buffer.read(cx);
11823 auto_indent_on_paste = snapshot
11824 .language_settings_at(cursor_offset, cx)
11825 .auto_indent_on_paste;
11826
11827 let mut start_offset = 0;
11828 let mut edits = Vec::new();
11829 let mut original_indent_columns = Vec::new();
11830 for (ix, selection) in old_selections.iter().enumerate() {
11831 let to_insert;
11832 let entire_line;
11833 let original_indent_column;
11834 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11835 let end_offset = start_offset + clipboard_selection.len;
11836 to_insert = &clipboard_text[start_offset..end_offset];
11837 entire_line = clipboard_selection.is_entire_line;
11838 start_offset = end_offset + 1;
11839 original_indent_column = Some(clipboard_selection.first_line_indent);
11840 } else {
11841 to_insert = clipboard_text.as_str();
11842 entire_line = all_selections_were_entire_line;
11843 original_indent_column = first_selection_indent_column
11844 }
11845
11846 // If the corresponding selection was empty when this slice of the
11847 // clipboard text was written, then the entire line containing the
11848 // selection was copied. If this selection is also currently empty,
11849 // then paste the line before the current line of the buffer.
11850 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11851 let column = selection.start.to_point(&snapshot).column as usize;
11852 let line_start = selection.start - column;
11853 line_start..line_start
11854 } else {
11855 selection.range()
11856 };
11857
11858 edits.push((range, to_insert));
11859 original_indent_columns.push(original_indent_column);
11860 }
11861 drop(snapshot);
11862
11863 buffer.edit(
11864 edits,
11865 if auto_indent_on_paste {
11866 Some(AutoindentMode::Block {
11867 original_indent_columns,
11868 })
11869 } else {
11870 None
11871 },
11872 cx,
11873 );
11874 });
11875
11876 let selections = this.selections.all::<usize>(cx);
11877 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11878 s.select(selections)
11879 });
11880 } else {
11881 this.insert(&clipboard_text, window, cx);
11882 }
11883 });
11884 }
11885
11886 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11887 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11888 if let Some(item) = cx.read_from_clipboard() {
11889 let entries = item.entries();
11890
11891 match entries.first() {
11892 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11893 // of all the pasted entries.
11894 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11895 .do_paste(
11896 clipboard_string.text(),
11897 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11898 true,
11899 window,
11900 cx,
11901 ),
11902 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11903 }
11904 }
11905 }
11906
11907 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11908 if self.read_only(cx) {
11909 return;
11910 }
11911
11912 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11913
11914 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11915 if let Some((selections, _)) =
11916 self.selection_history.transaction(transaction_id).cloned()
11917 {
11918 self.change_selections(None, window, cx, |s| {
11919 s.select_anchors(selections.to_vec());
11920 });
11921 } else {
11922 log::error!(
11923 "No entry in selection_history found for undo. \
11924 This may correspond to a bug where undo does not update the selection. \
11925 If this is occurring, please add details to \
11926 https://github.com/zed-industries/zed/issues/22692"
11927 );
11928 }
11929 self.request_autoscroll(Autoscroll::fit(), cx);
11930 self.unmark_text(window, cx);
11931 self.refresh_inline_completion(true, false, window, cx);
11932 cx.emit(EditorEvent::Edited { transaction_id });
11933 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11934 }
11935 }
11936
11937 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11938 if self.read_only(cx) {
11939 return;
11940 }
11941
11942 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11943
11944 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11945 if let Some((_, Some(selections))) =
11946 self.selection_history.transaction(transaction_id).cloned()
11947 {
11948 self.change_selections(None, window, cx, |s| {
11949 s.select_anchors(selections.to_vec());
11950 });
11951 } else {
11952 log::error!(
11953 "No entry in selection_history found for redo. \
11954 This may correspond to a bug where undo does not update the selection. \
11955 If this is occurring, please add details to \
11956 https://github.com/zed-industries/zed/issues/22692"
11957 );
11958 }
11959 self.request_autoscroll(Autoscroll::fit(), cx);
11960 self.unmark_text(window, cx);
11961 self.refresh_inline_completion(true, false, window, cx);
11962 cx.emit(EditorEvent::Edited { transaction_id });
11963 }
11964 }
11965
11966 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11967 self.buffer
11968 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11969 }
11970
11971 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11972 self.buffer
11973 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11974 }
11975
11976 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11977 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11978 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11979 s.move_with(|map, selection| {
11980 let cursor = if selection.is_empty() {
11981 movement::left(map, selection.start)
11982 } else {
11983 selection.start
11984 };
11985 selection.collapse_to(cursor, SelectionGoal::None);
11986 });
11987 })
11988 }
11989
11990 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11991 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11992 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11993 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11994 })
11995 }
11996
11997 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11998 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11999 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12000 s.move_with(|map, selection| {
12001 let cursor = if selection.is_empty() {
12002 movement::right(map, selection.end)
12003 } else {
12004 selection.end
12005 };
12006 selection.collapse_to(cursor, SelectionGoal::None)
12007 });
12008 })
12009 }
12010
12011 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12012 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12013 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12014 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12015 })
12016 }
12017
12018 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12019 if self.take_rename(true, window, cx).is_some() {
12020 return;
12021 }
12022
12023 if self.mode.is_single_line() {
12024 cx.propagate();
12025 return;
12026 }
12027
12028 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12029
12030 let text_layout_details = &self.text_layout_details(window);
12031 let selection_count = self.selections.count();
12032 let first_selection = self.selections.first_anchor();
12033
12034 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12035 s.move_with(|map, selection| {
12036 if !selection.is_empty() {
12037 selection.goal = SelectionGoal::None;
12038 }
12039 let (cursor, goal) = movement::up(
12040 map,
12041 selection.start,
12042 selection.goal,
12043 false,
12044 text_layout_details,
12045 );
12046 selection.collapse_to(cursor, goal);
12047 });
12048 });
12049
12050 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12051 {
12052 cx.propagate();
12053 }
12054 }
12055
12056 pub fn move_up_by_lines(
12057 &mut self,
12058 action: &MoveUpByLines,
12059 window: &mut Window,
12060 cx: &mut Context<Self>,
12061 ) {
12062 if self.take_rename(true, window, cx).is_some() {
12063 return;
12064 }
12065
12066 if self.mode.is_single_line() {
12067 cx.propagate();
12068 return;
12069 }
12070
12071 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12072
12073 let text_layout_details = &self.text_layout_details(window);
12074
12075 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12076 s.move_with(|map, selection| {
12077 if !selection.is_empty() {
12078 selection.goal = SelectionGoal::None;
12079 }
12080 let (cursor, goal) = movement::up_by_rows(
12081 map,
12082 selection.start,
12083 action.lines,
12084 selection.goal,
12085 false,
12086 text_layout_details,
12087 );
12088 selection.collapse_to(cursor, goal);
12089 });
12090 })
12091 }
12092
12093 pub fn move_down_by_lines(
12094 &mut self,
12095 action: &MoveDownByLines,
12096 window: &mut Window,
12097 cx: &mut Context<Self>,
12098 ) {
12099 if self.take_rename(true, window, cx).is_some() {
12100 return;
12101 }
12102
12103 if self.mode.is_single_line() {
12104 cx.propagate();
12105 return;
12106 }
12107
12108 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12109
12110 let text_layout_details = &self.text_layout_details(window);
12111
12112 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12113 s.move_with(|map, selection| {
12114 if !selection.is_empty() {
12115 selection.goal = SelectionGoal::None;
12116 }
12117 let (cursor, goal) = movement::down_by_rows(
12118 map,
12119 selection.start,
12120 action.lines,
12121 selection.goal,
12122 false,
12123 text_layout_details,
12124 );
12125 selection.collapse_to(cursor, goal);
12126 });
12127 })
12128 }
12129
12130 pub fn select_down_by_lines(
12131 &mut self,
12132 action: &SelectDownByLines,
12133 window: &mut Window,
12134 cx: &mut Context<Self>,
12135 ) {
12136 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12137 let text_layout_details = &self.text_layout_details(window);
12138 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12139 s.move_heads_with(|map, head, goal| {
12140 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12141 })
12142 })
12143 }
12144
12145 pub fn select_up_by_lines(
12146 &mut self,
12147 action: &SelectUpByLines,
12148 window: &mut Window,
12149 cx: &mut Context<Self>,
12150 ) {
12151 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12152 let text_layout_details = &self.text_layout_details(window);
12153 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12154 s.move_heads_with(|map, head, goal| {
12155 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12156 })
12157 })
12158 }
12159
12160 pub fn select_page_up(
12161 &mut self,
12162 _: &SelectPageUp,
12163 window: &mut Window,
12164 cx: &mut Context<Self>,
12165 ) {
12166 let Some(row_count) = self.visible_row_count() else {
12167 return;
12168 };
12169
12170 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12171
12172 let text_layout_details = &self.text_layout_details(window);
12173
12174 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12175 s.move_heads_with(|map, head, goal| {
12176 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12177 })
12178 })
12179 }
12180
12181 pub fn move_page_up(
12182 &mut self,
12183 action: &MovePageUp,
12184 window: &mut Window,
12185 cx: &mut Context<Self>,
12186 ) {
12187 if self.take_rename(true, window, cx).is_some() {
12188 return;
12189 }
12190
12191 if self
12192 .context_menu
12193 .borrow_mut()
12194 .as_mut()
12195 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12196 .unwrap_or(false)
12197 {
12198 return;
12199 }
12200
12201 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12202 cx.propagate();
12203 return;
12204 }
12205
12206 let Some(row_count) = self.visible_row_count() else {
12207 return;
12208 };
12209
12210 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12211
12212 let autoscroll = if action.center_cursor {
12213 Autoscroll::center()
12214 } else {
12215 Autoscroll::fit()
12216 };
12217
12218 let text_layout_details = &self.text_layout_details(window);
12219
12220 self.change_selections(Some(autoscroll), window, cx, |s| {
12221 s.move_with(|map, selection| {
12222 if !selection.is_empty() {
12223 selection.goal = SelectionGoal::None;
12224 }
12225 let (cursor, goal) = movement::up_by_rows(
12226 map,
12227 selection.end,
12228 row_count,
12229 selection.goal,
12230 false,
12231 text_layout_details,
12232 );
12233 selection.collapse_to(cursor, goal);
12234 });
12235 });
12236 }
12237
12238 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12239 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12240 let text_layout_details = &self.text_layout_details(window);
12241 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12242 s.move_heads_with(|map, head, goal| {
12243 movement::up(map, head, goal, false, text_layout_details)
12244 })
12245 })
12246 }
12247
12248 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12249 self.take_rename(true, window, cx);
12250
12251 if self.mode.is_single_line() {
12252 cx.propagate();
12253 return;
12254 }
12255
12256 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12257
12258 let text_layout_details = &self.text_layout_details(window);
12259 let selection_count = self.selections.count();
12260 let first_selection = self.selections.first_anchor();
12261
12262 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12263 s.move_with(|map, selection| {
12264 if !selection.is_empty() {
12265 selection.goal = SelectionGoal::None;
12266 }
12267 let (cursor, goal) = movement::down(
12268 map,
12269 selection.end,
12270 selection.goal,
12271 false,
12272 text_layout_details,
12273 );
12274 selection.collapse_to(cursor, goal);
12275 });
12276 });
12277
12278 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12279 {
12280 cx.propagate();
12281 }
12282 }
12283
12284 pub fn select_page_down(
12285 &mut self,
12286 _: &SelectPageDown,
12287 window: &mut Window,
12288 cx: &mut Context<Self>,
12289 ) {
12290 let Some(row_count) = self.visible_row_count() else {
12291 return;
12292 };
12293
12294 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12295
12296 let text_layout_details = &self.text_layout_details(window);
12297
12298 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12299 s.move_heads_with(|map, head, goal| {
12300 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12301 })
12302 })
12303 }
12304
12305 pub fn move_page_down(
12306 &mut self,
12307 action: &MovePageDown,
12308 window: &mut Window,
12309 cx: &mut Context<Self>,
12310 ) {
12311 if self.take_rename(true, window, cx).is_some() {
12312 return;
12313 }
12314
12315 if self
12316 .context_menu
12317 .borrow_mut()
12318 .as_mut()
12319 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12320 .unwrap_or(false)
12321 {
12322 return;
12323 }
12324
12325 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12326 cx.propagate();
12327 return;
12328 }
12329
12330 let Some(row_count) = self.visible_row_count() else {
12331 return;
12332 };
12333
12334 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12335
12336 let autoscroll = if action.center_cursor {
12337 Autoscroll::center()
12338 } else {
12339 Autoscroll::fit()
12340 };
12341
12342 let text_layout_details = &self.text_layout_details(window);
12343 self.change_selections(Some(autoscroll), window, cx, |s| {
12344 s.move_with(|map, selection| {
12345 if !selection.is_empty() {
12346 selection.goal = SelectionGoal::None;
12347 }
12348 let (cursor, goal) = movement::down_by_rows(
12349 map,
12350 selection.end,
12351 row_count,
12352 selection.goal,
12353 false,
12354 text_layout_details,
12355 );
12356 selection.collapse_to(cursor, goal);
12357 });
12358 });
12359 }
12360
12361 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12362 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12363 let text_layout_details = &self.text_layout_details(window);
12364 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12365 s.move_heads_with(|map, head, goal| {
12366 movement::down(map, head, goal, false, text_layout_details)
12367 })
12368 });
12369 }
12370
12371 pub fn context_menu_first(
12372 &mut self,
12373 _: &ContextMenuFirst,
12374 window: &mut Window,
12375 cx: &mut Context<Self>,
12376 ) {
12377 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12378 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12379 }
12380 }
12381
12382 pub fn context_menu_prev(
12383 &mut self,
12384 _: &ContextMenuPrevious,
12385 window: &mut Window,
12386 cx: &mut Context<Self>,
12387 ) {
12388 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12389 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12390 }
12391 }
12392
12393 pub fn context_menu_next(
12394 &mut self,
12395 _: &ContextMenuNext,
12396 window: &mut Window,
12397 cx: &mut Context<Self>,
12398 ) {
12399 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12400 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12401 }
12402 }
12403
12404 pub fn context_menu_last(
12405 &mut self,
12406 _: &ContextMenuLast,
12407 window: &mut Window,
12408 cx: &mut Context<Self>,
12409 ) {
12410 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12411 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12412 }
12413 }
12414
12415 pub fn move_to_previous_word_start(
12416 &mut self,
12417 _: &MoveToPreviousWordStart,
12418 window: &mut Window,
12419 cx: &mut Context<Self>,
12420 ) {
12421 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12422 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12423 s.move_cursors_with(|map, head, _| {
12424 (
12425 movement::previous_word_start(map, head),
12426 SelectionGoal::None,
12427 )
12428 });
12429 })
12430 }
12431
12432 pub fn move_to_previous_subword_start(
12433 &mut self,
12434 _: &MoveToPreviousSubwordStart,
12435 window: &mut Window,
12436 cx: &mut Context<Self>,
12437 ) {
12438 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12439 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12440 s.move_cursors_with(|map, head, _| {
12441 (
12442 movement::previous_subword_start(map, head),
12443 SelectionGoal::None,
12444 )
12445 });
12446 })
12447 }
12448
12449 pub fn select_to_previous_word_start(
12450 &mut self,
12451 _: &SelectToPreviousWordStart,
12452 window: &mut Window,
12453 cx: &mut Context<Self>,
12454 ) {
12455 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12456 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12457 s.move_heads_with(|map, head, _| {
12458 (
12459 movement::previous_word_start(map, head),
12460 SelectionGoal::None,
12461 )
12462 });
12463 })
12464 }
12465
12466 pub fn select_to_previous_subword_start(
12467 &mut self,
12468 _: &SelectToPreviousSubwordStart,
12469 window: &mut Window,
12470 cx: &mut Context<Self>,
12471 ) {
12472 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12473 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12474 s.move_heads_with(|map, head, _| {
12475 (
12476 movement::previous_subword_start(map, head),
12477 SelectionGoal::None,
12478 )
12479 });
12480 })
12481 }
12482
12483 pub fn delete_to_previous_word_start(
12484 &mut self,
12485 action: &DeleteToPreviousWordStart,
12486 window: &mut Window,
12487 cx: &mut Context<Self>,
12488 ) {
12489 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12490 self.transact(window, cx, |this, window, cx| {
12491 this.select_autoclose_pair(window, cx);
12492 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12493 s.move_with(|map, selection| {
12494 if selection.is_empty() {
12495 let cursor = if action.ignore_newlines {
12496 movement::previous_word_start(map, selection.head())
12497 } else {
12498 movement::previous_word_start_or_newline(map, selection.head())
12499 };
12500 selection.set_head(cursor, SelectionGoal::None);
12501 }
12502 });
12503 });
12504 this.insert("", window, cx);
12505 });
12506 }
12507
12508 pub fn delete_to_previous_subword_start(
12509 &mut self,
12510 _: &DeleteToPreviousSubwordStart,
12511 window: &mut Window,
12512 cx: &mut Context<Self>,
12513 ) {
12514 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12515 self.transact(window, cx, |this, window, cx| {
12516 this.select_autoclose_pair(window, cx);
12517 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12518 s.move_with(|map, selection| {
12519 if selection.is_empty() {
12520 let cursor = movement::previous_subword_start(map, selection.head());
12521 selection.set_head(cursor, SelectionGoal::None);
12522 }
12523 });
12524 });
12525 this.insert("", window, cx);
12526 });
12527 }
12528
12529 pub fn move_to_next_word_end(
12530 &mut self,
12531 _: &MoveToNextWordEnd,
12532 window: &mut Window,
12533 cx: &mut Context<Self>,
12534 ) {
12535 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12536 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12537 s.move_cursors_with(|map, head, _| {
12538 (movement::next_word_end(map, head), SelectionGoal::None)
12539 });
12540 })
12541 }
12542
12543 pub fn move_to_next_subword_end(
12544 &mut self,
12545 _: &MoveToNextSubwordEnd,
12546 window: &mut Window,
12547 cx: &mut Context<Self>,
12548 ) {
12549 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12550 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12551 s.move_cursors_with(|map, head, _| {
12552 (movement::next_subword_end(map, head), SelectionGoal::None)
12553 });
12554 })
12555 }
12556
12557 pub fn select_to_next_word_end(
12558 &mut self,
12559 _: &SelectToNextWordEnd,
12560 window: &mut Window,
12561 cx: &mut Context<Self>,
12562 ) {
12563 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12564 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12565 s.move_heads_with(|map, head, _| {
12566 (movement::next_word_end(map, head), SelectionGoal::None)
12567 });
12568 })
12569 }
12570
12571 pub fn select_to_next_subword_end(
12572 &mut self,
12573 _: &SelectToNextSubwordEnd,
12574 window: &mut Window,
12575 cx: &mut Context<Self>,
12576 ) {
12577 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12578 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12579 s.move_heads_with(|map, head, _| {
12580 (movement::next_subword_end(map, head), SelectionGoal::None)
12581 });
12582 })
12583 }
12584
12585 pub fn delete_to_next_word_end(
12586 &mut self,
12587 action: &DeleteToNextWordEnd,
12588 window: &mut Window,
12589 cx: &mut Context<Self>,
12590 ) {
12591 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12592 self.transact(window, cx, |this, window, cx| {
12593 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12594 s.move_with(|map, selection| {
12595 if selection.is_empty() {
12596 let cursor = if action.ignore_newlines {
12597 movement::next_word_end(map, selection.head())
12598 } else {
12599 movement::next_word_end_or_newline(map, selection.head())
12600 };
12601 selection.set_head(cursor, SelectionGoal::None);
12602 }
12603 });
12604 });
12605 this.insert("", window, cx);
12606 });
12607 }
12608
12609 pub fn delete_to_next_subword_end(
12610 &mut self,
12611 _: &DeleteToNextSubwordEnd,
12612 window: &mut Window,
12613 cx: &mut Context<Self>,
12614 ) {
12615 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12616 self.transact(window, cx, |this, window, cx| {
12617 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12618 s.move_with(|map, selection| {
12619 if selection.is_empty() {
12620 let cursor = movement::next_subword_end(map, selection.head());
12621 selection.set_head(cursor, SelectionGoal::None);
12622 }
12623 });
12624 });
12625 this.insert("", window, cx);
12626 });
12627 }
12628
12629 pub fn move_to_beginning_of_line(
12630 &mut self,
12631 action: &MoveToBeginningOfLine,
12632 window: &mut Window,
12633 cx: &mut Context<Self>,
12634 ) {
12635 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12636 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12637 s.move_cursors_with(|map, head, _| {
12638 (
12639 movement::indented_line_beginning(
12640 map,
12641 head,
12642 action.stop_at_soft_wraps,
12643 action.stop_at_indent,
12644 ),
12645 SelectionGoal::None,
12646 )
12647 });
12648 })
12649 }
12650
12651 pub fn select_to_beginning_of_line(
12652 &mut self,
12653 action: &SelectToBeginningOfLine,
12654 window: &mut Window,
12655 cx: &mut Context<Self>,
12656 ) {
12657 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12658 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12659 s.move_heads_with(|map, head, _| {
12660 (
12661 movement::indented_line_beginning(
12662 map,
12663 head,
12664 action.stop_at_soft_wraps,
12665 action.stop_at_indent,
12666 ),
12667 SelectionGoal::None,
12668 )
12669 });
12670 });
12671 }
12672
12673 pub fn delete_to_beginning_of_line(
12674 &mut self,
12675 action: &DeleteToBeginningOfLine,
12676 window: &mut Window,
12677 cx: &mut Context<Self>,
12678 ) {
12679 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12680 self.transact(window, cx, |this, window, cx| {
12681 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12682 s.move_with(|_, selection| {
12683 selection.reversed = true;
12684 });
12685 });
12686
12687 this.select_to_beginning_of_line(
12688 &SelectToBeginningOfLine {
12689 stop_at_soft_wraps: false,
12690 stop_at_indent: action.stop_at_indent,
12691 },
12692 window,
12693 cx,
12694 );
12695 this.backspace(&Backspace, window, cx);
12696 });
12697 }
12698
12699 pub fn move_to_end_of_line(
12700 &mut self,
12701 action: &MoveToEndOfLine,
12702 window: &mut Window,
12703 cx: &mut Context<Self>,
12704 ) {
12705 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12706 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12707 s.move_cursors_with(|map, head, _| {
12708 (
12709 movement::line_end(map, head, action.stop_at_soft_wraps),
12710 SelectionGoal::None,
12711 )
12712 });
12713 })
12714 }
12715
12716 pub fn select_to_end_of_line(
12717 &mut self,
12718 action: &SelectToEndOfLine,
12719 window: &mut Window,
12720 cx: &mut Context<Self>,
12721 ) {
12722 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12723 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12724 s.move_heads_with(|map, head, _| {
12725 (
12726 movement::line_end(map, head, action.stop_at_soft_wraps),
12727 SelectionGoal::None,
12728 )
12729 });
12730 })
12731 }
12732
12733 pub fn delete_to_end_of_line(
12734 &mut self,
12735 _: &DeleteToEndOfLine,
12736 window: &mut Window,
12737 cx: &mut Context<Self>,
12738 ) {
12739 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12740 self.transact(window, cx, |this, window, cx| {
12741 this.select_to_end_of_line(
12742 &SelectToEndOfLine {
12743 stop_at_soft_wraps: false,
12744 },
12745 window,
12746 cx,
12747 );
12748 this.delete(&Delete, window, cx);
12749 });
12750 }
12751
12752 pub fn cut_to_end_of_line(
12753 &mut self,
12754 _: &CutToEndOfLine,
12755 window: &mut Window,
12756 cx: &mut Context<Self>,
12757 ) {
12758 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12759 self.transact(window, cx, |this, window, cx| {
12760 this.select_to_end_of_line(
12761 &SelectToEndOfLine {
12762 stop_at_soft_wraps: false,
12763 },
12764 window,
12765 cx,
12766 );
12767 this.cut(&Cut, window, cx);
12768 });
12769 }
12770
12771 pub fn move_to_start_of_paragraph(
12772 &mut self,
12773 _: &MoveToStartOfParagraph,
12774 window: &mut Window,
12775 cx: &mut Context<Self>,
12776 ) {
12777 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12778 cx.propagate();
12779 return;
12780 }
12781 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12782 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12783 s.move_with(|map, selection| {
12784 selection.collapse_to(
12785 movement::start_of_paragraph(map, selection.head(), 1),
12786 SelectionGoal::None,
12787 )
12788 });
12789 })
12790 }
12791
12792 pub fn move_to_end_of_paragraph(
12793 &mut self,
12794 _: &MoveToEndOfParagraph,
12795 window: &mut Window,
12796 cx: &mut Context<Self>,
12797 ) {
12798 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12799 cx.propagate();
12800 return;
12801 }
12802 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12803 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12804 s.move_with(|map, selection| {
12805 selection.collapse_to(
12806 movement::end_of_paragraph(map, selection.head(), 1),
12807 SelectionGoal::None,
12808 )
12809 });
12810 })
12811 }
12812
12813 pub fn select_to_start_of_paragraph(
12814 &mut self,
12815 _: &SelectToStartOfParagraph,
12816 window: &mut Window,
12817 cx: &mut Context<Self>,
12818 ) {
12819 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12820 cx.propagate();
12821 return;
12822 }
12823 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12824 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12825 s.move_heads_with(|map, head, _| {
12826 (
12827 movement::start_of_paragraph(map, head, 1),
12828 SelectionGoal::None,
12829 )
12830 });
12831 })
12832 }
12833
12834 pub fn select_to_end_of_paragraph(
12835 &mut self,
12836 _: &SelectToEndOfParagraph,
12837 window: &mut Window,
12838 cx: &mut Context<Self>,
12839 ) {
12840 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12841 cx.propagate();
12842 return;
12843 }
12844 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12845 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12846 s.move_heads_with(|map, head, _| {
12847 (
12848 movement::end_of_paragraph(map, head, 1),
12849 SelectionGoal::None,
12850 )
12851 });
12852 })
12853 }
12854
12855 pub fn move_to_start_of_excerpt(
12856 &mut self,
12857 _: &MoveToStartOfExcerpt,
12858 window: &mut Window,
12859 cx: &mut Context<Self>,
12860 ) {
12861 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12862 cx.propagate();
12863 return;
12864 }
12865 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12866 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12867 s.move_with(|map, selection| {
12868 selection.collapse_to(
12869 movement::start_of_excerpt(
12870 map,
12871 selection.head(),
12872 workspace::searchable::Direction::Prev,
12873 ),
12874 SelectionGoal::None,
12875 )
12876 });
12877 })
12878 }
12879
12880 pub fn move_to_start_of_next_excerpt(
12881 &mut self,
12882 _: &MoveToStartOfNextExcerpt,
12883 window: &mut Window,
12884 cx: &mut Context<Self>,
12885 ) {
12886 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12887 cx.propagate();
12888 return;
12889 }
12890
12891 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12892 s.move_with(|map, selection| {
12893 selection.collapse_to(
12894 movement::start_of_excerpt(
12895 map,
12896 selection.head(),
12897 workspace::searchable::Direction::Next,
12898 ),
12899 SelectionGoal::None,
12900 )
12901 });
12902 })
12903 }
12904
12905 pub fn move_to_end_of_excerpt(
12906 &mut self,
12907 _: &MoveToEndOfExcerpt,
12908 window: &mut Window,
12909 cx: &mut Context<Self>,
12910 ) {
12911 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12912 cx.propagate();
12913 return;
12914 }
12915 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12916 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12917 s.move_with(|map, selection| {
12918 selection.collapse_to(
12919 movement::end_of_excerpt(
12920 map,
12921 selection.head(),
12922 workspace::searchable::Direction::Next,
12923 ),
12924 SelectionGoal::None,
12925 )
12926 });
12927 })
12928 }
12929
12930 pub fn move_to_end_of_previous_excerpt(
12931 &mut self,
12932 _: &MoveToEndOfPreviousExcerpt,
12933 window: &mut Window,
12934 cx: &mut Context<Self>,
12935 ) {
12936 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12937 cx.propagate();
12938 return;
12939 }
12940 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12941 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12942 s.move_with(|map, selection| {
12943 selection.collapse_to(
12944 movement::end_of_excerpt(
12945 map,
12946 selection.head(),
12947 workspace::searchable::Direction::Prev,
12948 ),
12949 SelectionGoal::None,
12950 )
12951 });
12952 })
12953 }
12954
12955 pub fn select_to_start_of_excerpt(
12956 &mut self,
12957 _: &SelectToStartOfExcerpt,
12958 window: &mut Window,
12959 cx: &mut Context<Self>,
12960 ) {
12961 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12962 cx.propagate();
12963 return;
12964 }
12965 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12966 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12967 s.move_heads_with(|map, head, _| {
12968 (
12969 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12970 SelectionGoal::None,
12971 )
12972 });
12973 })
12974 }
12975
12976 pub fn select_to_start_of_next_excerpt(
12977 &mut self,
12978 _: &SelectToStartOfNextExcerpt,
12979 window: &mut Window,
12980 cx: &mut Context<Self>,
12981 ) {
12982 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12983 cx.propagate();
12984 return;
12985 }
12986 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12987 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12988 s.move_heads_with(|map, head, _| {
12989 (
12990 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12991 SelectionGoal::None,
12992 )
12993 });
12994 })
12995 }
12996
12997 pub fn select_to_end_of_excerpt(
12998 &mut self,
12999 _: &SelectToEndOfExcerpt,
13000 window: &mut Window,
13001 cx: &mut Context<Self>,
13002 ) {
13003 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13004 cx.propagate();
13005 return;
13006 }
13007 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13008 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13009 s.move_heads_with(|map, head, _| {
13010 (
13011 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13012 SelectionGoal::None,
13013 )
13014 });
13015 })
13016 }
13017
13018 pub fn select_to_end_of_previous_excerpt(
13019 &mut self,
13020 _: &SelectToEndOfPreviousExcerpt,
13021 window: &mut Window,
13022 cx: &mut Context<Self>,
13023 ) {
13024 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13025 cx.propagate();
13026 return;
13027 }
13028 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13029 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13030 s.move_heads_with(|map, head, _| {
13031 (
13032 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13033 SelectionGoal::None,
13034 )
13035 });
13036 })
13037 }
13038
13039 pub fn move_to_beginning(
13040 &mut self,
13041 _: &MoveToBeginning,
13042 window: &mut Window,
13043 cx: &mut Context<Self>,
13044 ) {
13045 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13046 cx.propagate();
13047 return;
13048 }
13049 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13050 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13051 s.select_ranges(vec![0..0]);
13052 });
13053 }
13054
13055 pub fn select_to_beginning(
13056 &mut self,
13057 _: &SelectToBeginning,
13058 window: &mut Window,
13059 cx: &mut Context<Self>,
13060 ) {
13061 let mut selection = self.selections.last::<Point>(cx);
13062 selection.set_head(Point::zero(), SelectionGoal::None);
13063 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13064 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13065 s.select(vec![selection]);
13066 });
13067 }
13068
13069 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13070 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13071 cx.propagate();
13072 return;
13073 }
13074 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13075 let cursor = self.buffer.read(cx).read(cx).len();
13076 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13077 s.select_ranges(vec![cursor..cursor])
13078 });
13079 }
13080
13081 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13082 self.nav_history = nav_history;
13083 }
13084
13085 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13086 self.nav_history.as_ref()
13087 }
13088
13089 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13090 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
13091 }
13092
13093 fn push_to_nav_history(
13094 &mut self,
13095 cursor_anchor: Anchor,
13096 new_position: Option<Point>,
13097 is_deactivate: bool,
13098 cx: &mut Context<Self>,
13099 ) {
13100 if let Some(nav_history) = self.nav_history.as_mut() {
13101 let buffer = self.buffer.read(cx).read(cx);
13102 let cursor_position = cursor_anchor.to_point(&buffer);
13103 let scroll_state = self.scroll_manager.anchor();
13104 let scroll_top_row = scroll_state.top_row(&buffer);
13105 drop(buffer);
13106
13107 if let Some(new_position) = new_position {
13108 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13109 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
13110 return;
13111 }
13112 }
13113
13114 nav_history.push(
13115 Some(NavigationData {
13116 cursor_anchor,
13117 cursor_position,
13118 scroll_anchor: scroll_state,
13119 scroll_top_row,
13120 }),
13121 cx,
13122 );
13123 cx.emit(EditorEvent::PushedToNavHistory {
13124 anchor: cursor_anchor,
13125 is_deactivate,
13126 })
13127 }
13128 }
13129
13130 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13131 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13132 let buffer = self.buffer.read(cx).snapshot(cx);
13133 let mut selection = self.selections.first::<usize>(cx);
13134 selection.set_head(buffer.len(), SelectionGoal::None);
13135 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13136 s.select(vec![selection]);
13137 });
13138 }
13139
13140 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13141 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13142 let end = self.buffer.read(cx).read(cx).len();
13143 self.change_selections(None, window, cx, |s| {
13144 s.select_ranges(vec![0..end]);
13145 });
13146 }
13147
13148 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13149 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13150 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13151 let mut selections = self.selections.all::<Point>(cx);
13152 let max_point = display_map.buffer_snapshot.max_point();
13153 for selection in &mut selections {
13154 let rows = selection.spanned_rows(true, &display_map);
13155 selection.start = Point::new(rows.start.0, 0);
13156 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13157 selection.reversed = false;
13158 }
13159 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13160 s.select(selections);
13161 });
13162 }
13163
13164 pub fn split_selection_into_lines(
13165 &mut self,
13166 _: &SplitSelectionIntoLines,
13167 window: &mut Window,
13168 cx: &mut Context<Self>,
13169 ) {
13170 let selections = self
13171 .selections
13172 .all::<Point>(cx)
13173 .into_iter()
13174 .map(|selection| selection.start..selection.end)
13175 .collect::<Vec<_>>();
13176 self.unfold_ranges(&selections, true, true, cx);
13177
13178 let mut new_selection_ranges = Vec::new();
13179 {
13180 let buffer = self.buffer.read(cx).read(cx);
13181 for selection in selections {
13182 for row in selection.start.row..selection.end.row {
13183 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13184 new_selection_ranges.push(cursor..cursor);
13185 }
13186
13187 let is_multiline_selection = selection.start.row != selection.end.row;
13188 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13189 // so this action feels more ergonomic when paired with other selection operations
13190 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13191 if !should_skip_last {
13192 new_selection_ranges.push(selection.end..selection.end);
13193 }
13194 }
13195 }
13196 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13197 s.select_ranges(new_selection_ranges);
13198 });
13199 }
13200
13201 pub fn add_selection_above(
13202 &mut self,
13203 _: &AddSelectionAbove,
13204 window: &mut Window,
13205 cx: &mut Context<Self>,
13206 ) {
13207 self.add_selection(true, window, cx);
13208 }
13209
13210 pub fn add_selection_below(
13211 &mut self,
13212 _: &AddSelectionBelow,
13213 window: &mut Window,
13214 cx: &mut Context<Self>,
13215 ) {
13216 self.add_selection(false, window, cx);
13217 }
13218
13219 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13220 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13221
13222 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13223 let all_selections = self.selections.all::<Point>(cx);
13224 let text_layout_details = self.text_layout_details(window);
13225
13226 let (mut columnar_selections, new_selections_to_columnarize) = {
13227 if let Some(state) = self.add_selections_state.as_ref() {
13228 let columnar_selection_ids: HashSet<_> = state
13229 .groups
13230 .iter()
13231 .flat_map(|group| group.stack.iter())
13232 .copied()
13233 .collect();
13234
13235 all_selections
13236 .into_iter()
13237 .partition(|s| columnar_selection_ids.contains(&s.id))
13238 } else {
13239 (Vec::new(), all_selections)
13240 }
13241 };
13242
13243 let mut state = self
13244 .add_selections_state
13245 .take()
13246 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13247
13248 for selection in new_selections_to_columnarize {
13249 let range = selection.display_range(&display_map).sorted();
13250 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13251 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13252 let positions = start_x.min(end_x)..start_x.max(end_x);
13253 let mut stack = Vec::new();
13254 for row in range.start.row().0..=range.end.row().0 {
13255 if let Some(selection) = self.selections.build_columnar_selection(
13256 &display_map,
13257 DisplayRow(row),
13258 &positions,
13259 selection.reversed,
13260 &text_layout_details,
13261 ) {
13262 stack.push(selection.id);
13263 columnar_selections.push(selection);
13264 }
13265 }
13266 if !stack.is_empty() {
13267 if above {
13268 stack.reverse();
13269 }
13270 state.groups.push(AddSelectionsGroup { above, stack });
13271 }
13272 }
13273
13274 let mut final_selections = Vec::new();
13275 let end_row = if above {
13276 DisplayRow(0)
13277 } else {
13278 display_map.max_point().row()
13279 };
13280
13281 let mut last_added_item_per_group = HashMap::default();
13282 for group in state.groups.iter_mut() {
13283 if let Some(last_id) = group.stack.last() {
13284 last_added_item_per_group.insert(*last_id, group);
13285 }
13286 }
13287
13288 for selection in columnar_selections {
13289 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13290 if above == group.above {
13291 let range = selection.display_range(&display_map).sorted();
13292 debug_assert_eq!(range.start.row(), range.end.row());
13293 let mut row = range.start.row();
13294 let positions =
13295 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13296 px(start)..px(end)
13297 } else {
13298 let start_x =
13299 display_map.x_for_display_point(range.start, &text_layout_details);
13300 let end_x =
13301 display_map.x_for_display_point(range.end, &text_layout_details);
13302 start_x.min(end_x)..start_x.max(end_x)
13303 };
13304
13305 let mut maybe_new_selection = None;
13306 while row != end_row {
13307 if above {
13308 row.0 -= 1;
13309 } else {
13310 row.0 += 1;
13311 }
13312 if let Some(new_selection) = self.selections.build_columnar_selection(
13313 &display_map,
13314 row,
13315 &positions,
13316 selection.reversed,
13317 &text_layout_details,
13318 ) {
13319 maybe_new_selection = Some(new_selection);
13320 break;
13321 }
13322 }
13323
13324 if let Some(new_selection) = maybe_new_selection {
13325 group.stack.push(new_selection.id);
13326 if above {
13327 final_selections.push(new_selection);
13328 final_selections.push(selection);
13329 } else {
13330 final_selections.push(selection);
13331 final_selections.push(new_selection);
13332 }
13333 } else {
13334 final_selections.push(selection);
13335 }
13336 } else {
13337 group.stack.pop();
13338 }
13339 } else {
13340 final_selections.push(selection);
13341 }
13342 }
13343
13344 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13345 s.select(final_selections);
13346 });
13347
13348 let final_selection_ids: HashSet<_> = self
13349 .selections
13350 .all::<Point>(cx)
13351 .iter()
13352 .map(|s| s.id)
13353 .collect();
13354 state.groups.retain_mut(|group| {
13355 // selections might get merged above so we remove invalid items from stacks
13356 group.stack.retain(|id| final_selection_ids.contains(id));
13357
13358 // single selection in stack can be treated as initial state
13359 group.stack.len() > 1
13360 });
13361
13362 if !state.groups.is_empty() {
13363 self.add_selections_state = Some(state);
13364 }
13365 }
13366
13367 fn select_match_ranges(
13368 &mut self,
13369 range: Range<usize>,
13370 reversed: bool,
13371 replace_newest: bool,
13372 auto_scroll: Option<Autoscroll>,
13373 window: &mut Window,
13374 cx: &mut Context<Editor>,
13375 ) {
13376 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
13377 self.change_selections(auto_scroll, window, cx, |s| {
13378 if replace_newest {
13379 s.delete(s.newest_anchor().id);
13380 }
13381 if reversed {
13382 s.insert_range(range.end..range.start);
13383 } else {
13384 s.insert_range(range);
13385 }
13386 });
13387 }
13388
13389 pub fn select_next_match_internal(
13390 &mut self,
13391 display_map: &DisplaySnapshot,
13392 replace_newest: bool,
13393 autoscroll: Option<Autoscroll>,
13394 window: &mut Window,
13395 cx: &mut Context<Self>,
13396 ) -> Result<()> {
13397 let buffer = &display_map.buffer_snapshot;
13398 let mut selections = self.selections.all::<usize>(cx);
13399 if let Some(mut select_next_state) = self.select_next_state.take() {
13400 let query = &select_next_state.query;
13401 if !select_next_state.done {
13402 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13403 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13404 let mut next_selected_range = None;
13405
13406 let bytes_after_last_selection =
13407 buffer.bytes_in_range(last_selection.end..buffer.len());
13408 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13409 let query_matches = query
13410 .stream_find_iter(bytes_after_last_selection)
13411 .map(|result| (last_selection.end, result))
13412 .chain(
13413 query
13414 .stream_find_iter(bytes_before_first_selection)
13415 .map(|result| (0, result)),
13416 );
13417
13418 for (start_offset, query_match) in query_matches {
13419 let query_match = query_match.unwrap(); // can only fail due to I/O
13420 let offset_range =
13421 start_offset + query_match.start()..start_offset + query_match.end();
13422
13423 if !select_next_state.wordwise
13424 || (!buffer.is_inside_word(offset_range.start, false)
13425 && !buffer.is_inside_word(offset_range.end, false))
13426 {
13427 // TODO: This is n^2, because we might check all the selections
13428 if !selections
13429 .iter()
13430 .any(|selection| selection.range().overlaps(&offset_range))
13431 {
13432 next_selected_range = Some(offset_range);
13433 break;
13434 }
13435 }
13436 }
13437
13438 if let Some(next_selected_range) = next_selected_range {
13439 self.select_match_ranges(
13440 next_selected_range,
13441 last_selection.reversed,
13442 replace_newest,
13443 autoscroll,
13444 window,
13445 cx,
13446 );
13447 } else {
13448 select_next_state.done = true;
13449 }
13450 }
13451
13452 self.select_next_state = Some(select_next_state);
13453 } else {
13454 let mut only_carets = true;
13455 let mut same_text_selected = true;
13456 let mut selected_text = None;
13457
13458 let mut selections_iter = selections.iter().peekable();
13459 while let Some(selection) = selections_iter.next() {
13460 if selection.start != selection.end {
13461 only_carets = false;
13462 }
13463
13464 if same_text_selected {
13465 if selected_text.is_none() {
13466 selected_text =
13467 Some(buffer.text_for_range(selection.range()).collect::<String>());
13468 }
13469
13470 if let Some(next_selection) = selections_iter.peek() {
13471 if next_selection.range().len() == selection.range().len() {
13472 let next_selected_text = buffer
13473 .text_for_range(next_selection.range())
13474 .collect::<String>();
13475 if Some(next_selected_text) != selected_text {
13476 same_text_selected = false;
13477 selected_text = None;
13478 }
13479 } else {
13480 same_text_selected = false;
13481 selected_text = None;
13482 }
13483 }
13484 }
13485 }
13486
13487 if only_carets {
13488 for selection in &mut selections {
13489 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13490 selection.start = word_range.start;
13491 selection.end = word_range.end;
13492 selection.goal = SelectionGoal::None;
13493 selection.reversed = false;
13494 self.select_match_ranges(
13495 selection.start..selection.end,
13496 selection.reversed,
13497 replace_newest,
13498 autoscroll,
13499 window,
13500 cx,
13501 );
13502 }
13503
13504 if selections.len() == 1 {
13505 let selection = selections
13506 .last()
13507 .expect("ensured that there's only one selection");
13508 let query = buffer
13509 .text_for_range(selection.start..selection.end)
13510 .collect::<String>();
13511 let is_empty = query.is_empty();
13512 let select_state = SelectNextState {
13513 query: AhoCorasick::new(&[query])?,
13514 wordwise: true,
13515 done: is_empty,
13516 };
13517 self.select_next_state = Some(select_state);
13518 } else {
13519 self.select_next_state = None;
13520 }
13521 } else if let Some(selected_text) = selected_text {
13522 self.select_next_state = Some(SelectNextState {
13523 query: AhoCorasick::new(&[selected_text])?,
13524 wordwise: false,
13525 done: false,
13526 });
13527 self.select_next_match_internal(
13528 display_map,
13529 replace_newest,
13530 autoscroll,
13531 window,
13532 cx,
13533 )?;
13534 }
13535 }
13536 Ok(())
13537 }
13538
13539 pub fn select_all_matches(
13540 &mut self,
13541 _action: &SelectAllMatches,
13542 window: &mut Window,
13543 cx: &mut Context<Self>,
13544 ) -> Result<()> {
13545 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13546
13547 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13548
13549 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13550 let Some(select_next_state) = self.select_next_state.as_mut() else {
13551 return Ok(());
13552 };
13553 if select_next_state.done {
13554 return Ok(());
13555 }
13556
13557 let mut new_selections = Vec::new();
13558
13559 let reversed = self.selections.oldest::<usize>(cx).reversed;
13560 let buffer = &display_map.buffer_snapshot;
13561 let query_matches = select_next_state
13562 .query
13563 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13564
13565 for query_match in query_matches.into_iter() {
13566 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13567 let offset_range = if reversed {
13568 query_match.end()..query_match.start()
13569 } else {
13570 query_match.start()..query_match.end()
13571 };
13572
13573 if !select_next_state.wordwise
13574 || (!buffer.is_inside_word(offset_range.start, false)
13575 && !buffer.is_inside_word(offset_range.end, false))
13576 {
13577 new_selections.push(offset_range.start..offset_range.end);
13578 }
13579 }
13580
13581 select_next_state.done = true;
13582
13583 if new_selections.is_empty() {
13584 log::error!("bug: new_selections is empty in select_all_matches");
13585 return Ok(());
13586 }
13587
13588 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13589 self.change_selections(None, window, cx, |selections| {
13590 selections.select_ranges(new_selections)
13591 });
13592
13593 Ok(())
13594 }
13595
13596 pub fn select_next(
13597 &mut self,
13598 action: &SelectNext,
13599 window: &mut Window,
13600 cx: &mut Context<Self>,
13601 ) -> Result<()> {
13602 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13603 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13604 self.select_next_match_internal(
13605 &display_map,
13606 action.replace_newest,
13607 Some(Autoscroll::newest()),
13608 window,
13609 cx,
13610 )?;
13611 Ok(())
13612 }
13613
13614 pub fn select_previous(
13615 &mut self,
13616 action: &SelectPrevious,
13617 window: &mut Window,
13618 cx: &mut Context<Self>,
13619 ) -> Result<()> {
13620 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13621 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13622 let buffer = &display_map.buffer_snapshot;
13623 let mut selections = self.selections.all::<usize>(cx);
13624 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13625 let query = &select_prev_state.query;
13626 if !select_prev_state.done {
13627 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13628 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13629 let mut next_selected_range = None;
13630 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13631 let bytes_before_last_selection =
13632 buffer.reversed_bytes_in_range(0..last_selection.start);
13633 let bytes_after_first_selection =
13634 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13635 let query_matches = query
13636 .stream_find_iter(bytes_before_last_selection)
13637 .map(|result| (last_selection.start, result))
13638 .chain(
13639 query
13640 .stream_find_iter(bytes_after_first_selection)
13641 .map(|result| (buffer.len(), result)),
13642 );
13643 for (end_offset, query_match) in query_matches {
13644 let query_match = query_match.unwrap(); // can only fail due to I/O
13645 let offset_range =
13646 end_offset - query_match.end()..end_offset - query_match.start();
13647
13648 if !select_prev_state.wordwise
13649 || (!buffer.is_inside_word(offset_range.start, false)
13650 && !buffer.is_inside_word(offset_range.end, false))
13651 {
13652 next_selected_range = Some(offset_range);
13653 break;
13654 }
13655 }
13656
13657 if let Some(next_selected_range) = next_selected_range {
13658 self.select_match_ranges(
13659 next_selected_range,
13660 last_selection.reversed,
13661 action.replace_newest,
13662 Some(Autoscroll::newest()),
13663 window,
13664 cx,
13665 );
13666 } else {
13667 select_prev_state.done = true;
13668 }
13669 }
13670
13671 self.select_prev_state = Some(select_prev_state);
13672 } else {
13673 let mut only_carets = true;
13674 let mut same_text_selected = true;
13675 let mut selected_text = None;
13676
13677 let mut selections_iter = selections.iter().peekable();
13678 while let Some(selection) = selections_iter.next() {
13679 if selection.start != selection.end {
13680 only_carets = false;
13681 }
13682
13683 if same_text_selected {
13684 if selected_text.is_none() {
13685 selected_text =
13686 Some(buffer.text_for_range(selection.range()).collect::<String>());
13687 }
13688
13689 if let Some(next_selection) = selections_iter.peek() {
13690 if next_selection.range().len() == selection.range().len() {
13691 let next_selected_text = buffer
13692 .text_for_range(next_selection.range())
13693 .collect::<String>();
13694 if Some(next_selected_text) != selected_text {
13695 same_text_selected = false;
13696 selected_text = None;
13697 }
13698 } else {
13699 same_text_selected = false;
13700 selected_text = None;
13701 }
13702 }
13703 }
13704 }
13705
13706 if only_carets {
13707 for selection in &mut selections {
13708 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13709 selection.start = word_range.start;
13710 selection.end = word_range.end;
13711 selection.goal = SelectionGoal::None;
13712 selection.reversed = false;
13713 self.select_match_ranges(
13714 selection.start..selection.end,
13715 selection.reversed,
13716 action.replace_newest,
13717 Some(Autoscroll::newest()),
13718 window,
13719 cx,
13720 );
13721 }
13722 if selections.len() == 1 {
13723 let selection = selections
13724 .last()
13725 .expect("ensured that there's only one selection");
13726 let query = buffer
13727 .text_for_range(selection.start..selection.end)
13728 .collect::<String>();
13729 let is_empty = query.is_empty();
13730 let select_state = SelectNextState {
13731 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13732 wordwise: true,
13733 done: is_empty,
13734 };
13735 self.select_prev_state = Some(select_state);
13736 } else {
13737 self.select_prev_state = None;
13738 }
13739 } else if let Some(selected_text) = selected_text {
13740 self.select_prev_state = Some(SelectNextState {
13741 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13742 wordwise: false,
13743 done: false,
13744 });
13745 self.select_previous(action, window, cx)?;
13746 }
13747 }
13748 Ok(())
13749 }
13750
13751 pub fn find_next_match(
13752 &mut self,
13753 _: &FindNextMatch,
13754 window: &mut Window,
13755 cx: &mut Context<Self>,
13756 ) -> Result<()> {
13757 let selections = self.selections.disjoint_anchors();
13758 match selections.first() {
13759 Some(first) if selections.len() >= 2 => {
13760 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13761 s.select_ranges([first.range()]);
13762 });
13763 }
13764 _ => self.select_next(
13765 &SelectNext {
13766 replace_newest: true,
13767 },
13768 window,
13769 cx,
13770 )?,
13771 }
13772 Ok(())
13773 }
13774
13775 pub fn find_previous_match(
13776 &mut self,
13777 _: &FindPreviousMatch,
13778 window: &mut Window,
13779 cx: &mut Context<Self>,
13780 ) -> Result<()> {
13781 let selections = self.selections.disjoint_anchors();
13782 match selections.last() {
13783 Some(last) if selections.len() >= 2 => {
13784 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13785 s.select_ranges([last.range()]);
13786 });
13787 }
13788 _ => self.select_previous(
13789 &SelectPrevious {
13790 replace_newest: true,
13791 },
13792 window,
13793 cx,
13794 )?,
13795 }
13796 Ok(())
13797 }
13798
13799 pub fn toggle_comments(
13800 &mut self,
13801 action: &ToggleComments,
13802 window: &mut Window,
13803 cx: &mut Context<Self>,
13804 ) {
13805 if self.read_only(cx) {
13806 return;
13807 }
13808 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13809 let text_layout_details = &self.text_layout_details(window);
13810 self.transact(window, cx, |this, window, cx| {
13811 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13812 let mut edits = Vec::new();
13813 let mut selection_edit_ranges = Vec::new();
13814 let mut last_toggled_row = None;
13815 let snapshot = this.buffer.read(cx).read(cx);
13816 let empty_str: Arc<str> = Arc::default();
13817 let mut suffixes_inserted = Vec::new();
13818 let ignore_indent = action.ignore_indent;
13819
13820 fn comment_prefix_range(
13821 snapshot: &MultiBufferSnapshot,
13822 row: MultiBufferRow,
13823 comment_prefix: &str,
13824 comment_prefix_whitespace: &str,
13825 ignore_indent: bool,
13826 ) -> Range<Point> {
13827 let indent_size = if ignore_indent {
13828 0
13829 } else {
13830 snapshot.indent_size_for_line(row).len
13831 };
13832
13833 let start = Point::new(row.0, indent_size);
13834
13835 let mut line_bytes = snapshot
13836 .bytes_in_range(start..snapshot.max_point())
13837 .flatten()
13838 .copied();
13839
13840 // If this line currently begins with the line comment prefix, then record
13841 // the range containing the prefix.
13842 if line_bytes
13843 .by_ref()
13844 .take(comment_prefix.len())
13845 .eq(comment_prefix.bytes())
13846 {
13847 // Include any whitespace that matches the comment prefix.
13848 let matching_whitespace_len = line_bytes
13849 .zip(comment_prefix_whitespace.bytes())
13850 .take_while(|(a, b)| a == b)
13851 .count() as u32;
13852 let end = Point::new(
13853 start.row,
13854 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13855 );
13856 start..end
13857 } else {
13858 start..start
13859 }
13860 }
13861
13862 fn comment_suffix_range(
13863 snapshot: &MultiBufferSnapshot,
13864 row: MultiBufferRow,
13865 comment_suffix: &str,
13866 comment_suffix_has_leading_space: bool,
13867 ) -> Range<Point> {
13868 let end = Point::new(row.0, snapshot.line_len(row));
13869 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13870
13871 let mut line_end_bytes = snapshot
13872 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13873 .flatten()
13874 .copied();
13875
13876 let leading_space_len = if suffix_start_column > 0
13877 && line_end_bytes.next() == Some(b' ')
13878 && comment_suffix_has_leading_space
13879 {
13880 1
13881 } else {
13882 0
13883 };
13884
13885 // If this line currently begins with the line comment prefix, then record
13886 // the range containing the prefix.
13887 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13888 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13889 start..end
13890 } else {
13891 end..end
13892 }
13893 }
13894
13895 // TODO: Handle selections that cross excerpts
13896 for selection in &mut selections {
13897 let start_column = snapshot
13898 .indent_size_for_line(MultiBufferRow(selection.start.row))
13899 .len;
13900 let language = if let Some(language) =
13901 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13902 {
13903 language
13904 } else {
13905 continue;
13906 };
13907
13908 selection_edit_ranges.clear();
13909
13910 // If multiple selections contain a given row, avoid processing that
13911 // row more than once.
13912 let mut start_row = MultiBufferRow(selection.start.row);
13913 if last_toggled_row == Some(start_row) {
13914 start_row = start_row.next_row();
13915 }
13916 let end_row =
13917 if selection.end.row > selection.start.row && selection.end.column == 0 {
13918 MultiBufferRow(selection.end.row - 1)
13919 } else {
13920 MultiBufferRow(selection.end.row)
13921 };
13922 last_toggled_row = Some(end_row);
13923
13924 if start_row > end_row {
13925 continue;
13926 }
13927
13928 // If the language has line comments, toggle those.
13929 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13930
13931 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13932 if ignore_indent {
13933 full_comment_prefixes = full_comment_prefixes
13934 .into_iter()
13935 .map(|s| Arc::from(s.trim_end()))
13936 .collect();
13937 }
13938
13939 if !full_comment_prefixes.is_empty() {
13940 let first_prefix = full_comment_prefixes
13941 .first()
13942 .expect("prefixes is non-empty");
13943 let prefix_trimmed_lengths = full_comment_prefixes
13944 .iter()
13945 .map(|p| p.trim_end_matches(' ').len())
13946 .collect::<SmallVec<[usize; 4]>>();
13947
13948 let mut all_selection_lines_are_comments = true;
13949
13950 for row in start_row.0..=end_row.0 {
13951 let row = MultiBufferRow(row);
13952 if start_row < end_row && snapshot.is_line_blank(row) {
13953 continue;
13954 }
13955
13956 let prefix_range = full_comment_prefixes
13957 .iter()
13958 .zip(prefix_trimmed_lengths.iter().copied())
13959 .map(|(prefix, trimmed_prefix_len)| {
13960 comment_prefix_range(
13961 snapshot.deref(),
13962 row,
13963 &prefix[..trimmed_prefix_len],
13964 &prefix[trimmed_prefix_len..],
13965 ignore_indent,
13966 )
13967 })
13968 .max_by_key(|range| range.end.column - range.start.column)
13969 .expect("prefixes is non-empty");
13970
13971 if prefix_range.is_empty() {
13972 all_selection_lines_are_comments = false;
13973 }
13974
13975 selection_edit_ranges.push(prefix_range);
13976 }
13977
13978 if all_selection_lines_are_comments {
13979 edits.extend(
13980 selection_edit_ranges
13981 .iter()
13982 .cloned()
13983 .map(|range| (range, empty_str.clone())),
13984 );
13985 } else {
13986 let min_column = selection_edit_ranges
13987 .iter()
13988 .map(|range| range.start.column)
13989 .min()
13990 .unwrap_or(0);
13991 edits.extend(selection_edit_ranges.iter().map(|range| {
13992 let position = Point::new(range.start.row, min_column);
13993 (position..position, first_prefix.clone())
13994 }));
13995 }
13996 } else if let Some((full_comment_prefix, comment_suffix)) =
13997 language.block_comment_delimiters()
13998 {
13999 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14000 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14001 let prefix_range = comment_prefix_range(
14002 snapshot.deref(),
14003 start_row,
14004 comment_prefix,
14005 comment_prefix_whitespace,
14006 ignore_indent,
14007 );
14008 let suffix_range = comment_suffix_range(
14009 snapshot.deref(),
14010 end_row,
14011 comment_suffix.trim_start_matches(' '),
14012 comment_suffix.starts_with(' '),
14013 );
14014
14015 if prefix_range.is_empty() || suffix_range.is_empty() {
14016 edits.push((
14017 prefix_range.start..prefix_range.start,
14018 full_comment_prefix.clone(),
14019 ));
14020 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14021 suffixes_inserted.push((end_row, comment_suffix.len()));
14022 } else {
14023 edits.push((prefix_range, empty_str.clone()));
14024 edits.push((suffix_range, empty_str.clone()));
14025 }
14026 } else {
14027 continue;
14028 }
14029 }
14030
14031 drop(snapshot);
14032 this.buffer.update(cx, |buffer, cx| {
14033 buffer.edit(edits, None, cx);
14034 });
14035
14036 // Adjust selections so that they end before any comment suffixes that
14037 // were inserted.
14038 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14039 let mut selections = this.selections.all::<Point>(cx);
14040 let snapshot = this.buffer.read(cx).read(cx);
14041 for selection in &mut selections {
14042 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14043 match row.cmp(&MultiBufferRow(selection.end.row)) {
14044 Ordering::Less => {
14045 suffixes_inserted.next();
14046 continue;
14047 }
14048 Ordering::Greater => break,
14049 Ordering::Equal => {
14050 if selection.end.column == snapshot.line_len(row) {
14051 if selection.is_empty() {
14052 selection.start.column -= suffix_len as u32;
14053 }
14054 selection.end.column -= suffix_len as u32;
14055 }
14056 break;
14057 }
14058 }
14059 }
14060 }
14061
14062 drop(snapshot);
14063 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14064 s.select(selections)
14065 });
14066
14067 let selections = this.selections.all::<Point>(cx);
14068 let selections_on_single_row = selections.windows(2).all(|selections| {
14069 selections[0].start.row == selections[1].start.row
14070 && selections[0].end.row == selections[1].end.row
14071 && selections[0].start.row == selections[0].end.row
14072 });
14073 let selections_selecting = selections
14074 .iter()
14075 .any(|selection| selection.start != selection.end);
14076 let advance_downwards = action.advance_downwards
14077 && selections_on_single_row
14078 && !selections_selecting
14079 && !matches!(this.mode, EditorMode::SingleLine { .. });
14080
14081 if advance_downwards {
14082 let snapshot = this.buffer.read(cx).snapshot(cx);
14083
14084 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14085 s.move_cursors_with(|display_snapshot, display_point, _| {
14086 let mut point = display_point.to_point(display_snapshot);
14087 point.row += 1;
14088 point = snapshot.clip_point(point, Bias::Left);
14089 let display_point = point.to_display_point(display_snapshot);
14090 let goal = SelectionGoal::HorizontalPosition(
14091 display_snapshot
14092 .x_for_display_point(display_point, text_layout_details)
14093 .into(),
14094 );
14095 (display_point, goal)
14096 })
14097 });
14098 }
14099 });
14100 }
14101
14102 pub fn select_enclosing_symbol(
14103 &mut self,
14104 _: &SelectEnclosingSymbol,
14105 window: &mut Window,
14106 cx: &mut Context<Self>,
14107 ) {
14108 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14109
14110 let buffer = self.buffer.read(cx).snapshot(cx);
14111 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14112
14113 fn update_selection(
14114 selection: &Selection<usize>,
14115 buffer_snap: &MultiBufferSnapshot,
14116 ) -> Option<Selection<usize>> {
14117 let cursor = selection.head();
14118 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14119 for symbol in symbols.iter().rev() {
14120 let start = symbol.range.start.to_offset(buffer_snap);
14121 let end = symbol.range.end.to_offset(buffer_snap);
14122 let new_range = start..end;
14123 if start < selection.start || end > selection.end {
14124 return Some(Selection {
14125 id: selection.id,
14126 start: new_range.start,
14127 end: new_range.end,
14128 goal: SelectionGoal::None,
14129 reversed: selection.reversed,
14130 });
14131 }
14132 }
14133 None
14134 }
14135
14136 let mut selected_larger_symbol = false;
14137 let new_selections = old_selections
14138 .iter()
14139 .map(|selection| match update_selection(selection, &buffer) {
14140 Some(new_selection) => {
14141 if new_selection.range() != selection.range() {
14142 selected_larger_symbol = true;
14143 }
14144 new_selection
14145 }
14146 None => selection.clone(),
14147 })
14148 .collect::<Vec<_>>();
14149
14150 if selected_larger_symbol {
14151 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14152 s.select(new_selections);
14153 });
14154 }
14155 }
14156
14157 pub fn select_larger_syntax_node(
14158 &mut self,
14159 _: &SelectLargerSyntaxNode,
14160 window: &mut Window,
14161 cx: &mut Context<Self>,
14162 ) {
14163 let Some(visible_row_count) = self.visible_row_count() else {
14164 return;
14165 };
14166 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14167 if old_selections.is_empty() {
14168 return;
14169 }
14170
14171 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14172
14173 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14174 let buffer = self.buffer.read(cx).snapshot(cx);
14175
14176 let mut selected_larger_node = false;
14177 let mut new_selections = old_selections
14178 .iter()
14179 .map(|selection| {
14180 let old_range = selection.start..selection.end;
14181
14182 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14183 // manually select word at selection
14184 if ["string_content", "inline"].contains(&node.kind()) {
14185 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14186 // ignore if word is already selected
14187 if !word_range.is_empty() && old_range != word_range {
14188 let (last_word_range, _) =
14189 buffer.surrounding_word(old_range.end, false);
14190 // only select word if start and end point belongs to same word
14191 if word_range == last_word_range {
14192 selected_larger_node = true;
14193 return Selection {
14194 id: selection.id,
14195 start: word_range.start,
14196 end: word_range.end,
14197 goal: SelectionGoal::None,
14198 reversed: selection.reversed,
14199 };
14200 }
14201 }
14202 }
14203 }
14204
14205 let mut new_range = old_range.clone();
14206 while let Some((_node, containing_range)) =
14207 buffer.syntax_ancestor(new_range.clone())
14208 {
14209 new_range = match containing_range {
14210 MultiOrSingleBufferOffsetRange::Single(_) => break,
14211 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14212 };
14213 if !display_map.intersects_fold(new_range.start)
14214 && !display_map.intersects_fold(new_range.end)
14215 {
14216 break;
14217 }
14218 }
14219
14220 selected_larger_node |= new_range != old_range;
14221 Selection {
14222 id: selection.id,
14223 start: new_range.start,
14224 end: new_range.end,
14225 goal: SelectionGoal::None,
14226 reversed: selection.reversed,
14227 }
14228 })
14229 .collect::<Vec<_>>();
14230
14231 if !selected_larger_node {
14232 return; // don't put this call in the history
14233 }
14234
14235 // scroll based on transformation done to the last selection created by the user
14236 let (last_old, last_new) = old_selections
14237 .last()
14238 .zip(new_selections.last().cloned())
14239 .expect("old_selections isn't empty");
14240
14241 // revert selection
14242 let is_selection_reversed = {
14243 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14244 new_selections.last_mut().expect("checked above").reversed =
14245 should_newest_selection_be_reversed;
14246 should_newest_selection_be_reversed
14247 };
14248
14249 if selected_larger_node {
14250 self.select_syntax_node_history.disable_clearing = true;
14251 self.change_selections(None, window, cx, |s| {
14252 s.select(new_selections.clone());
14253 });
14254 self.select_syntax_node_history.disable_clearing = false;
14255 }
14256
14257 let start_row = last_new.start.to_display_point(&display_map).row().0;
14258 let end_row = last_new.end.to_display_point(&display_map).row().0;
14259 let selection_height = end_row - start_row + 1;
14260 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14261
14262 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14263 let scroll_behavior = if fits_on_the_screen {
14264 self.request_autoscroll(Autoscroll::fit(), cx);
14265 SelectSyntaxNodeScrollBehavior::FitSelection
14266 } else if is_selection_reversed {
14267 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14268 SelectSyntaxNodeScrollBehavior::CursorTop
14269 } else {
14270 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14271 SelectSyntaxNodeScrollBehavior::CursorBottom
14272 };
14273
14274 self.select_syntax_node_history.push((
14275 old_selections,
14276 scroll_behavior,
14277 is_selection_reversed,
14278 ));
14279 }
14280
14281 pub fn select_smaller_syntax_node(
14282 &mut self,
14283 _: &SelectSmallerSyntaxNode,
14284 window: &mut Window,
14285 cx: &mut Context<Self>,
14286 ) {
14287 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14288
14289 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14290 self.select_syntax_node_history.pop()
14291 {
14292 if let Some(selection) = selections.last_mut() {
14293 selection.reversed = is_selection_reversed;
14294 }
14295
14296 self.select_syntax_node_history.disable_clearing = true;
14297 self.change_selections(None, window, cx, |s| {
14298 s.select(selections.to_vec());
14299 });
14300 self.select_syntax_node_history.disable_clearing = false;
14301
14302 match scroll_behavior {
14303 SelectSyntaxNodeScrollBehavior::CursorTop => {
14304 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14305 }
14306 SelectSyntaxNodeScrollBehavior::FitSelection => {
14307 self.request_autoscroll(Autoscroll::fit(), cx);
14308 }
14309 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14310 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14311 }
14312 }
14313 }
14314 }
14315
14316 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14317 if !EditorSettings::get_global(cx).gutter.runnables {
14318 self.clear_tasks();
14319 return Task::ready(());
14320 }
14321 let project = self.project.as_ref().map(Entity::downgrade);
14322 let task_sources = self.lsp_task_sources(cx);
14323 let multi_buffer = self.buffer.downgrade();
14324 cx.spawn_in(window, async move |editor, cx| {
14325 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14326 let Some(project) = project.and_then(|p| p.upgrade()) else {
14327 return;
14328 };
14329 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14330 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14331 }) else {
14332 return;
14333 };
14334
14335 let hide_runnables = project
14336 .update(cx, |project, cx| {
14337 // Do not display any test indicators in non-dev server remote projects.
14338 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14339 })
14340 .unwrap_or(true);
14341 if hide_runnables {
14342 return;
14343 }
14344 let new_rows =
14345 cx.background_spawn({
14346 let snapshot = display_snapshot.clone();
14347 async move {
14348 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14349 }
14350 })
14351 .await;
14352 let Ok(lsp_tasks) =
14353 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14354 else {
14355 return;
14356 };
14357 let lsp_tasks = lsp_tasks.await;
14358
14359 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14360 lsp_tasks
14361 .into_iter()
14362 .flat_map(|(kind, tasks)| {
14363 tasks.into_iter().filter_map(move |(location, task)| {
14364 Some((kind.clone(), location?, task))
14365 })
14366 })
14367 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14368 let buffer = location.target.buffer;
14369 let buffer_snapshot = buffer.read(cx).snapshot();
14370 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14371 |(excerpt_id, snapshot, _)| {
14372 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14373 display_snapshot
14374 .buffer_snapshot
14375 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14376 } else {
14377 None
14378 }
14379 },
14380 );
14381 if let Some(offset) = offset {
14382 let task_buffer_range =
14383 location.target.range.to_point(&buffer_snapshot);
14384 let context_buffer_range =
14385 task_buffer_range.to_offset(&buffer_snapshot);
14386 let context_range = BufferOffset(context_buffer_range.start)
14387 ..BufferOffset(context_buffer_range.end);
14388
14389 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14390 .or_insert_with(|| RunnableTasks {
14391 templates: Vec::new(),
14392 offset,
14393 column: task_buffer_range.start.column,
14394 extra_variables: HashMap::default(),
14395 context_range,
14396 })
14397 .templates
14398 .push((kind, task.original_task().clone()));
14399 }
14400
14401 acc
14402 })
14403 }) else {
14404 return;
14405 };
14406
14407 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14408 buffer.language_settings(cx).tasks.prefer_lsp
14409 }) else {
14410 return;
14411 };
14412
14413 let rows = Self::runnable_rows(
14414 project,
14415 display_snapshot,
14416 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14417 new_rows,
14418 cx.clone(),
14419 )
14420 .await;
14421 editor
14422 .update(cx, |editor, _| {
14423 editor.clear_tasks();
14424 for (key, mut value) in rows {
14425 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14426 value.templates.extend(lsp_tasks.templates);
14427 }
14428
14429 editor.insert_tasks(key, value);
14430 }
14431 for (key, value) in lsp_tasks_by_rows {
14432 editor.insert_tasks(key, value);
14433 }
14434 })
14435 .ok();
14436 })
14437 }
14438 fn fetch_runnable_ranges(
14439 snapshot: &DisplaySnapshot,
14440 range: Range<Anchor>,
14441 ) -> Vec<language::RunnableRange> {
14442 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14443 }
14444
14445 fn runnable_rows(
14446 project: Entity<Project>,
14447 snapshot: DisplaySnapshot,
14448 prefer_lsp: bool,
14449 runnable_ranges: Vec<RunnableRange>,
14450 cx: AsyncWindowContext,
14451 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14452 cx.spawn(async move |cx| {
14453 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14454 for mut runnable in runnable_ranges {
14455 let Some(tasks) = cx
14456 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14457 .ok()
14458 else {
14459 continue;
14460 };
14461 let mut tasks = tasks.await;
14462
14463 if prefer_lsp {
14464 tasks.retain(|(task_kind, _)| {
14465 !matches!(task_kind, TaskSourceKind::Language { .. })
14466 });
14467 }
14468 if tasks.is_empty() {
14469 continue;
14470 }
14471
14472 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14473 let Some(row) = snapshot
14474 .buffer_snapshot
14475 .buffer_line_for_row(MultiBufferRow(point.row))
14476 .map(|(_, range)| range.start.row)
14477 else {
14478 continue;
14479 };
14480
14481 let context_range =
14482 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14483 runnable_rows.push((
14484 (runnable.buffer_id, row),
14485 RunnableTasks {
14486 templates: tasks,
14487 offset: snapshot
14488 .buffer_snapshot
14489 .anchor_before(runnable.run_range.start),
14490 context_range,
14491 column: point.column,
14492 extra_variables: runnable.extra_captures,
14493 },
14494 ));
14495 }
14496 runnable_rows
14497 })
14498 }
14499
14500 fn templates_with_tags(
14501 project: &Entity<Project>,
14502 runnable: &mut Runnable,
14503 cx: &mut App,
14504 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14505 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14506 let (worktree_id, file) = project
14507 .buffer_for_id(runnable.buffer, cx)
14508 .and_then(|buffer| buffer.read(cx).file())
14509 .map(|file| (file.worktree_id(cx), file.clone()))
14510 .unzip();
14511
14512 (
14513 project.task_store().read(cx).task_inventory().cloned(),
14514 worktree_id,
14515 file,
14516 )
14517 });
14518
14519 let tags = mem::take(&mut runnable.tags);
14520 let language = runnable.language.clone();
14521 cx.spawn(async move |cx| {
14522 let mut templates_with_tags = Vec::new();
14523 if let Some(inventory) = inventory {
14524 for RunnableTag(tag) in tags {
14525 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14526 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14527 }) else {
14528 return templates_with_tags;
14529 };
14530 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14531 move |(_, template)| {
14532 template.tags.iter().any(|source_tag| source_tag == &tag)
14533 },
14534 ));
14535 }
14536 }
14537 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14538
14539 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14540 // Strongest source wins; if we have worktree tag binding, prefer that to
14541 // global and language bindings;
14542 // if we have a global binding, prefer that to language binding.
14543 let first_mismatch = templates_with_tags
14544 .iter()
14545 .position(|(tag_source, _)| tag_source != leading_tag_source);
14546 if let Some(index) = first_mismatch {
14547 templates_with_tags.truncate(index);
14548 }
14549 }
14550
14551 templates_with_tags
14552 })
14553 }
14554
14555 pub fn move_to_enclosing_bracket(
14556 &mut self,
14557 _: &MoveToEnclosingBracket,
14558 window: &mut Window,
14559 cx: &mut Context<Self>,
14560 ) {
14561 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14562 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14563 s.move_offsets_with(|snapshot, selection| {
14564 let Some(enclosing_bracket_ranges) =
14565 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14566 else {
14567 return;
14568 };
14569
14570 let mut best_length = usize::MAX;
14571 let mut best_inside = false;
14572 let mut best_in_bracket_range = false;
14573 let mut best_destination = None;
14574 for (open, close) in enclosing_bracket_ranges {
14575 let close = close.to_inclusive();
14576 let length = close.end() - open.start;
14577 let inside = selection.start >= open.end && selection.end <= *close.start();
14578 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14579 || close.contains(&selection.head());
14580
14581 // If best is next to a bracket and current isn't, skip
14582 if !in_bracket_range && best_in_bracket_range {
14583 continue;
14584 }
14585
14586 // Prefer smaller lengths unless best is inside and current isn't
14587 if length > best_length && (best_inside || !inside) {
14588 continue;
14589 }
14590
14591 best_length = length;
14592 best_inside = inside;
14593 best_in_bracket_range = in_bracket_range;
14594 best_destination = Some(
14595 if close.contains(&selection.start) && close.contains(&selection.end) {
14596 if inside { open.end } else { open.start }
14597 } else if inside {
14598 *close.start()
14599 } else {
14600 *close.end()
14601 },
14602 );
14603 }
14604
14605 if let Some(destination) = best_destination {
14606 selection.collapse_to(destination, SelectionGoal::None);
14607 }
14608 })
14609 });
14610 }
14611
14612 pub fn undo_selection(
14613 &mut self,
14614 _: &UndoSelection,
14615 window: &mut Window,
14616 cx: &mut Context<Self>,
14617 ) {
14618 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14619 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14620 self.selection_history.mode = SelectionHistoryMode::Undoing;
14621 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14622 this.end_selection(window, cx);
14623 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14624 s.select_anchors(entry.selections.to_vec())
14625 });
14626 });
14627 self.selection_history.mode = SelectionHistoryMode::Normal;
14628
14629 self.select_next_state = entry.select_next_state;
14630 self.select_prev_state = entry.select_prev_state;
14631 self.add_selections_state = entry.add_selections_state;
14632 }
14633 }
14634
14635 pub fn redo_selection(
14636 &mut self,
14637 _: &RedoSelection,
14638 window: &mut Window,
14639 cx: &mut Context<Self>,
14640 ) {
14641 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14642 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14643 self.selection_history.mode = SelectionHistoryMode::Redoing;
14644 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14645 this.end_selection(window, cx);
14646 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14647 s.select_anchors(entry.selections.to_vec())
14648 });
14649 });
14650 self.selection_history.mode = SelectionHistoryMode::Normal;
14651
14652 self.select_next_state = entry.select_next_state;
14653 self.select_prev_state = entry.select_prev_state;
14654 self.add_selections_state = entry.add_selections_state;
14655 }
14656 }
14657
14658 pub fn expand_excerpts(
14659 &mut self,
14660 action: &ExpandExcerpts,
14661 _: &mut Window,
14662 cx: &mut Context<Self>,
14663 ) {
14664 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14665 }
14666
14667 pub fn expand_excerpts_down(
14668 &mut self,
14669 action: &ExpandExcerptsDown,
14670 _: &mut Window,
14671 cx: &mut Context<Self>,
14672 ) {
14673 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14674 }
14675
14676 pub fn expand_excerpts_up(
14677 &mut self,
14678 action: &ExpandExcerptsUp,
14679 _: &mut Window,
14680 cx: &mut Context<Self>,
14681 ) {
14682 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14683 }
14684
14685 pub fn expand_excerpts_for_direction(
14686 &mut self,
14687 lines: u32,
14688 direction: ExpandExcerptDirection,
14689
14690 cx: &mut Context<Self>,
14691 ) {
14692 let selections = self.selections.disjoint_anchors();
14693
14694 let lines = if lines == 0 {
14695 EditorSettings::get_global(cx).expand_excerpt_lines
14696 } else {
14697 lines
14698 };
14699
14700 self.buffer.update(cx, |buffer, cx| {
14701 let snapshot = buffer.snapshot(cx);
14702 let mut excerpt_ids = selections
14703 .iter()
14704 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14705 .collect::<Vec<_>>();
14706 excerpt_ids.sort();
14707 excerpt_ids.dedup();
14708 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14709 })
14710 }
14711
14712 pub fn expand_excerpt(
14713 &mut self,
14714 excerpt: ExcerptId,
14715 direction: ExpandExcerptDirection,
14716 window: &mut Window,
14717 cx: &mut Context<Self>,
14718 ) {
14719 let current_scroll_position = self.scroll_position(cx);
14720 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14721 let mut should_scroll_up = false;
14722
14723 if direction == ExpandExcerptDirection::Down {
14724 let multi_buffer = self.buffer.read(cx);
14725 let snapshot = multi_buffer.snapshot(cx);
14726 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14727 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14728 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14729 let buffer_snapshot = buffer.read(cx).snapshot();
14730 let excerpt_end_row =
14731 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14732 let last_row = buffer_snapshot.max_point().row;
14733 let lines_below = last_row.saturating_sub(excerpt_end_row);
14734 should_scroll_up = lines_below >= lines_to_expand;
14735 }
14736 }
14737 }
14738 }
14739
14740 self.buffer.update(cx, |buffer, cx| {
14741 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14742 });
14743
14744 if should_scroll_up {
14745 let new_scroll_position =
14746 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14747 self.set_scroll_position(new_scroll_position, window, cx);
14748 }
14749 }
14750
14751 pub fn go_to_singleton_buffer_point(
14752 &mut self,
14753 point: Point,
14754 window: &mut Window,
14755 cx: &mut Context<Self>,
14756 ) {
14757 self.go_to_singleton_buffer_range(point..point, window, cx);
14758 }
14759
14760 pub fn go_to_singleton_buffer_range(
14761 &mut self,
14762 range: Range<Point>,
14763 window: &mut Window,
14764 cx: &mut Context<Self>,
14765 ) {
14766 let multibuffer = self.buffer().read(cx);
14767 let Some(buffer) = multibuffer.as_singleton() else {
14768 return;
14769 };
14770 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14771 return;
14772 };
14773 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14774 return;
14775 };
14776 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
14777 s.select_anchor_ranges([start..end])
14778 });
14779 }
14780
14781 pub fn go_to_diagnostic(
14782 &mut self,
14783 _: &GoToDiagnostic,
14784 window: &mut Window,
14785 cx: &mut Context<Self>,
14786 ) {
14787 if !self.diagnostics_enabled() {
14788 return;
14789 }
14790 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14791 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14792 }
14793
14794 pub fn go_to_prev_diagnostic(
14795 &mut self,
14796 _: &GoToPreviousDiagnostic,
14797 window: &mut Window,
14798 cx: &mut Context<Self>,
14799 ) {
14800 if !self.diagnostics_enabled() {
14801 return;
14802 }
14803 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14804 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14805 }
14806
14807 pub fn go_to_diagnostic_impl(
14808 &mut self,
14809 direction: Direction,
14810 window: &mut Window,
14811 cx: &mut Context<Self>,
14812 ) {
14813 let buffer = self.buffer.read(cx).snapshot(cx);
14814 let selection = self.selections.newest::<usize>(cx);
14815
14816 let mut active_group_id = None;
14817 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14818 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14819 active_group_id = Some(active_group.group_id);
14820 }
14821 }
14822
14823 fn filtered(
14824 snapshot: EditorSnapshot,
14825 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14826 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14827 diagnostics
14828 .filter(|entry| entry.range.start != entry.range.end)
14829 .filter(|entry| !entry.diagnostic.is_unnecessary)
14830 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14831 }
14832
14833 let snapshot = self.snapshot(window, cx);
14834 let before = filtered(
14835 snapshot.clone(),
14836 buffer
14837 .diagnostics_in_range(0..selection.start)
14838 .filter(|entry| entry.range.start <= selection.start),
14839 );
14840 let after = filtered(
14841 snapshot,
14842 buffer
14843 .diagnostics_in_range(selection.start..buffer.len())
14844 .filter(|entry| entry.range.start >= selection.start),
14845 );
14846
14847 let mut found: Option<DiagnosticEntry<usize>> = None;
14848 if direction == Direction::Prev {
14849 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14850 {
14851 for diagnostic in prev_diagnostics.into_iter().rev() {
14852 if diagnostic.range.start != selection.start
14853 || active_group_id
14854 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14855 {
14856 found = Some(diagnostic);
14857 break 'outer;
14858 }
14859 }
14860 }
14861 } else {
14862 for diagnostic in after.chain(before) {
14863 if diagnostic.range.start != selection.start
14864 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14865 {
14866 found = Some(diagnostic);
14867 break;
14868 }
14869 }
14870 }
14871 let Some(next_diagnostic) = found else {
14872 return;
14873 };
14874
14875 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14876 return;
14877 };
14878 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14879 s.select_ranges(vec![
14880 next_diagnostic.range.start..next_diagnostic.range.start,
14881 ])
14882 });
14883 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14884 self.refresh_inline_completion(false, true, window, cx);
14885 }
14886
14887 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14888 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14889 let snapshot = self.snapshot(window, cx);
14890 let selection = self.selections.newest::<Point>(cx);
14891 self.go_to_hunk_before_or_after_position(
14892 &snapshot,
14893 selection.head(),
14894 Direction::Next,
14895 window,
14896 cx,
14897 );
14898 }
14899
14900 pub fn go_to_hunk_before_or_after_position(
14901 &mut self,
14902 snapshot: &EditorSnapshot,
14903 position: Point,
14904 direction: Direction,
14905 window: &mut Window,
14906 cx: &mut Context<Editor>,
14907 ) {
14908 let row = if direction == Direction::Next {
14909 self.hunk_after_position(snapshot, position)
14910 .map(|hunk| hunk.row_range.start)
14911 } else {
14912 self.hunk_before_position(snapshot, position)
14913 };
14914
14915 if let Some(row) = row {
14916 let destination = Point::new(row.0, 0);
14917 let autoscroll = Autoscroll::center();
14918
14919 self.unfold_ranges(&[destination..destination], false, false, cx);
14920 self.change_selections(Some(autoscroll), window, cx, |s| {
14921 s.select_ranges([destination..destination]);
14922 });
14923 }
14924 }
14925
14926 fn hunk_after_position(
14927 &mut self,
14928 snapshot: &EditorSnapshot,
14929 position: Point,
14930 ) -> Option<MultiBufferDiffHunk> {
14931 snapshot
14932 .buffer_snapshot
14933 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14934 .find(|hunk| hunk.row_range.start.0 > position.row)
14935 .or_else(|| {
14936 snapshot
14937 .buffer_snapshot
14938 .diff_hunks_in_range(Point::zero()..position)
14939 .find(|hunk| hunk.row_range.end.0 < position.row)
14940 })
14941 }
14942
14943 fn go_to_prev_hunk(
14944 &mut self,
14945 _: &GoToPreviousHunk,
14946 window: &mut Window,
14947 cx: &mut Context<Self>,
14948 ) {
14949 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14950 let snapshot = self.snapshot(window, cx);
14951 let selection = self.selections.newest::<Point>(cx);
14952 self.go_to_hunk_before_or_after_position(
14953 &snapshot,
14954 selection.head(),
14955 Direction::Prev,
14956 window,
14957 cx,
14958 );
14959 }
14960
14961 fn hunk_before_position(
14962 &mut self,
14963 snapshot: &EditorSnapshot,
14964 position: Point,
14965 ) -> Option<MultiBufferRow> {
14966 snapshot
14967 .buffer_snapshot
14968 .diff_hunk_before(position)
14969 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14970 }
14971
14972 fn go_to_next_change(
14973 &mut self,
14974 _: &GoToNextChange,
14975 window: &mut Window,
14976 cx: &mut Context<Self>,
14977 ) {
14978 if let Some(selections) = self
14979 .change_list
14980 .next_change(1, Direction::Next)
14981 .map(|s| s.to_vec())
14982 {
14983 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14984 let map = s.display_map();
14985 s.select_display_ranges(selections.iter().map(|a| {
14986 let point = a.to_display_point(&map);
14987 point..point
14988 }))
14989 })
14990 }
14991 }
14992
14993 fn go_to_previous_change(
14994 &mut self,
14995 _: &GoToPreviousChange,
14996 window: &mut Window,
14997 cx: &mut Context<Self>,
14998 ) {
14999 if let Some(selections) = self
15000 .change_list
15001 .next_change(1, Direction::Prev)
15002 .map(|s| s.to_vec())
15003 {
15004 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15005 let map = s.display_map();
15006 s.select_display_ranges(selections.iter().map(|a| {
15007 let point = a.to_display_point(&map);
15008 point..point
15009 }))
15010 })
15011 }
15012 }
15013
15014 fn go_to_line<T: 'static>(
15015 &mut self,
15016 position: Anchor,
15017 highlight_color: Option<Hsla>,
15018 window: &mut Window,
15019 cx: &mut Context<Self>,
15020 ) {
15021 let snapshot = self.snapshot(window, cx).display_snapshot;
15022 let position = position.to_point(&snapshot.buffer_snapshot);
15023 let start = snapshot
15024 .buffer_snapshot
15025 .clip_point(Point::new(position.row, 0), Bias::Left);
15026 let end = start + Point::new(1, 0);
15027 let start = snapshot.buffer_snapshot.anchor_before(start);
15028 let end = snapshot.buffer_snapshot.anchor_before(end);
15029
15030 self.highlight_rows::<T>(
15031 start..end,
15032 highlight_color
15033 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15034 Default::default(),
15035 cx,
15036 );
15037
15038 if self.buffer.read(cx).is_singleton() {
15039 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15040 }
15041 }
15042
15043 pub fn go_to_definition(
15044 &mut self,
15045 _: &GoToDefinition,
15046 window: &mut Window,
15047 cx: &mut Context<Self>,
15048 ) -> Task<Result<Navigated>> {
15049 let definition =
15050 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15051 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15052 cx.spawn_in(window, async move |editor, cx| {
15053 if definition.await? == Navigated::Yes {
15054 return Ok(Navigated::Yes);
15055 }
15056 match fallback_strategy {
15057 GoToDefinitionFallback::None => Ok(Navigated::No),
15058 GoToDefinitionFallback::FindAllReferences => {
15059 match editor.update_in(cx, |editor, window, cx| {
15060 editor.find_all_references(&FindAllReferences, window, cx)
15061 })? {
15062 Some(references) => references.await,
15063 None => Ok(Navigated::No),
15064 }
15065 }
15066 }
15067 })
15068 }
15069
15070 pub fn go_to_declaration(
15071 &mut self,
15072 _: &GoToDeclaration,
15073 window: &mut Window,
15074 cx: &mut Context<Self>,
15075 ) -> Task<Result<Navigated>> {
15076 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15077 }
15078
15079 pub fn go_to_declaration_split(
15080 &mut self,
15081 _: &GoToDeclaration,
15082 window: &mut Window,
15083 cx: &mut Context<Self>,
15084 ) -> Task<Result<Navigated>> {
15085 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15086 }
15087
15088 pub fn go_to_implementation(
15089 &mut self,
15090 _: &GoToImplementation,
15091 window: &mut Window,
15092 cx: &mut Context<Self>,
15093 ) -> Task<Result<Navigated>> {
15094 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15095 }
15096
15097 pub fn go_to_implementation_split(
15098 &mut self,
15099 _: &GoToImplementationSplit,
15100 window: &mut Window,
15101 cx: &mut Context<Self>,
15102 ) -> Task<Result<Navigated>> {
15103 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15104 }
15105
15106 pub fn go_to_type_definition(
15107 &mut self,
15108 _: &GoToTypeDefinition,
15109 window: &mut Window,
15110 cx: &mut Context<Self>,
15111 ) -> Task<Result<Navigated>> {
15112 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15113 }
15114
15115 pub fn go_to_definition_split(
15116 &mut self,
15117 _: &GoToDefinitionSplit,
15118 window: &mut Window,
15119 cx: &mut Context<Self>,
15120 ) -> Task<Result<Navigated>> {
15121 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15122 }
15123
15124 pub fn go_to_type_definition_split(
15125 &mut self,
15126 _: &GoToTypeDefinitionSplit,
15127 window: &mut Window,
15128 cx: &mut Context<Self>,
15129 ) -> Task<Result<Navigated>> {
15130 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15131 }
15132
15133 fn go_to_definition_of_kind(
15134 &mut self,
15135 kind: GotoDefinitionKind,
15136 split: bool,
15137 window: &mut Window,
15138 cx: &mut Context<Self>,
15139 ) -> Task<Result<Navigated>> {
15140 let Some(provider) = self.semantics_provider.clone() else {
15141 return Task::ready(Ok(Navigated::No));
15142 };
15143 let head = self.selections.newest::<usize>(cx).head();
15144 let buffer = self.buffer.read(cx);
15145 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
15146 text_anchor
15147 } else {
15148 return Task::ready(Ok(Navigated::No));
15149 };
15150
15151 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15152 return Task::ready(Ok(Navigated::No));
15153 };
15154
15155 cx.spawn_in(window, async move |editor, cx| {
15156 let definitions = definitions.await?;
15157 let navigated = editor
15158 .update_in(cx, |editor, window, cx| {
15159 editor.navigate_to_hover_links(
15160 Some(kind),
15161 definitions
15162 .into_iter()
15163 .filter(|location| {
15164 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15165 })
15166 .map(HoverLink::Text)
15167 .collect::<Vec<_>>(),
15168 split,
15169 window,
15170 cx,
15171 )
15172 })?
15173 .await?;
15174 anyhow::Ok(navigated)
15175 })
15176 }
15177
15178 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15179 let selection = self.selections.newest_anchor();
15180 let head = selection.head();
15181 let tail = selection.tail();
15182
15183 let Some((buffer, start_position)) =
15184 self.buffer.read(cx).text_anchor_for_position(head, cx)
15185 else {
15186 return;
15187 };
15188
15189 let end_position = if head != tail {
15190 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15191 return;
15192 };
15193 Some(pos)
15194 } else {
15195 None
15196 };
15197
15198 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15199 let url = if let Some(end_pos) = end_position {
15200 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15201 } else {
15202 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15203 };
15204
15205 if let Some(url) = url {
15206 editor.update(cx, |_, cx| {
15207 cx.open_url(&url);
15208 })
15209 } else {
15210 Ok(())
15211 }
15212 });
15213
15214 url_finder.detach();
15215 }
15216
15217 pub fn open_selected_filename(
15218 &mut self,
15219 _: &OpenSelectedFilename,
15220 window: &mut Window,
15221 cx: &mut Context<Self>,
15222 ) {
15223 let Some(workspace) = self.workspace() else {
15224 return;
15225 };
15226
15227 let position = self.selections.newest_anchor().head();
15228
15229 let Some((buffer, buffer_position)) =
15230 self.buffer.read(cx).text_anchor_for_position(position, cx)
15231 else {
15232 return;
15233 };
15234
15235 let project = self.project.clone();
15236
15237 cx.spawn_in(window, async move |_, cx| {
15238 let result = find_file(&buffer, project, buffer_position, cx).await;
15239
15240 if let Some((_, path)) = result {
15241 workspace
15242 .update_in(cx, |workspace, window, cx| {
15243 workspace.open_resolved_path(path, window, cx)
15244 })?
15245 .await?;
15246 }
15247 anyhow::Ok(())
15248 })
15249 .detach();
15250 }
15251
15252 pub(crate) fn navigate_to_hover_links(
15253 &mut self,
15254 kind: Option<GotoDefinitionKind>,
15255 mut definitions: Vec<HoverLink>,
15256 split: bool,
15257 window: &mut Window,
15258 cx: &mut Context<Editor>,
15259 ) -> Task<Result<Navigated>> {
15260 // If there is one definition, just open it directly
15261 if definitions.len() == 1 {
15262 let definition = definitions.pop().unwrap();
15263
15264 enum TargetTaskResult {
15265 Location(Option<Location>),
15266 AlreadyNavigated,
15267 }
15268
15269 let target_task = match definition {
15270 HoverLink::Text(link) => {
15271 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
15272 }
15273 HoverLink::InlayHint(lsp_location, server_id) => {
15274 let computation =
15275 self.compute_target_location(lsp_location, server_id, window, cx);
15276 cx.background_spawn(async move {
15277 let location = computation.await?;
15278 Ok(TargetTaskResult::Location(location))
15279 })
15280 }
15281 HoverLink::Url(url) => {
15282 cx.open_url(&url);
15283 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
15284 }
15285 HoverLink::File(path) => {
15286 if let Some(workspace) = self.workspace() {
15287 cx.spawn_in(window, async move |_, cx| {
15288 workspace
15289 .update_in(cx, |workspace, window, cx| {
15290 workspace.open_resolved_path(path, window, cx)
15291 })?
15292 .await
15293 .map(|_| TargetTaskResult::AlreadyNavigated)
15294 })
15295 } else {
15296 Task::ready(Ok(TargetTaskResult::Location(None)))
15297 }
15298 }
15299 };
15300 cx.spawn_in(window, async move |editor, cx| {
15301 let target = match target_task.await.context("target resolution task")? {
15302 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
15303 TargetTaskResult::Location(None) => return Ok(Navigated::No),
15304 TargetTaskResult::Location(Some(target)) => target,
15305 };
15306
15307 editor.update_in(cx, |editor, window, cx| {
15308 let Some(workspace) = editor.workspace() else {
15309 return Navigated::No;
15310 };
15311 let pane = workspace.read(cx).active_pane().clone();
15312
15313 let range = target.range.to_point(target.buffer.read(cx));
15314 let range = editor.range_for_match(&range);
15315 let range = collapse_multiline_range(range);
15316
15317 if !split
15318 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15319 {
15320 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15321 } else {
15322 window.defer(cx, move |window, cx| {
15323 let target_editor: Entity<Self> =
15324 workspace.update(cx, |workspace, cx| {
15325 let pane = if split {
15326 workspace.adjacent_pane(window, cx)
15327 } else {
15328 workspace.active_pane().clone()
15329 };
15330
15331 workspace.open_project_item(
15332 pane,
15333 target.buffer.clone(),
15334 true,
15335 true,
15336 window,
15337 cx,
15338 )
15339 });
15340 target_editor.update(cx, |target_editor, cx| {
15341 // When selecting a definition in a different buffer, disable the nav history
15342 // to avoid creating a history entry at the previous cursor location.
15343 pane.update(cx, |pane, _| pane.disable_history());
15344 target_editor.go_to_singleton_buffer_range(range, window, cx);
15345 pane.update(cx, |pane, _| pane.enable_history());
15346 });
15347 });
15348 }
15349 Navigated::Yes
15350 })
15351 })
15352 } else if !definitions.is_empty() {
15353 cx.spawn_in(window, async move |editor, cx| {
15354 let (title, location_tasks, workspace) = editor
15355 .update_in(cx, |editor, window, cx| {
15356 let tab_kind = match kind {
15357 Some(GotoDefinitionKind::Implementation) => "Implementations",
15358 _ => "Definitions",
15359 };
15360 let title = definitions
15361 .iter()
15362 .find_map(|definition| match definition {
15363 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15364 let buffer = origin.buffer.read(cx);
15365 format!(
15366 "{} for {}",
15367 tab_kind,
15368 buffer
15369 .text_for_range(origin.range.clone())
15370 .collect::<String>()
15371 )
15372 }),
15373 HoverLink::InlayHint(_, _) => None,
15374 HoverLink::Url(_) => None,
15375 HoverLink::File(_) => None,
15376 })
15377 .unwrap_or(tab_kind.to_string());
15378 let location_tasks = definitions
15379 .into_iter()
15380 .map(|definition| match definition {
15381 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15382 HoverLink::InlayHint(lsp_location, server_id) => editor
15383 .compute_target_location(lsp_location, server_id, window, cx),
15384 HoverLink::Url(_) => Task::ready(Ok(None)),
15385 HoverLink::File(_) => Task::ready(Ok(None)),
15386 })
15387 .collect::<Vec<_>>();
15388 (title, location_tasks, editor.workspace().clone())
15389 })
15390 .context("location tasks preparation")?;
15391
15392 let locations: Vec<Location> = future::join_all(location_tasks)
15393 .await
15394 .into_iter()
15395 .filter_map(|location| location.transpose())
15396 .collect::<Result<_>>()
15397 .context("location tasks")?;
15398
15399 if locations.is_empty() {
15400 return Ok(Navigated::No);
15401 }
15402
15403 let Some(workspace) = workspace else {
15404 return Ok(Navigated::No);
15405 };
15406
15407 let opened = workspace
15408 .update_in(cx, |workspace, window, cx| {
15409 Self::open_locations_in_multibuffer(
15410 workspace,
15411 locations,
15412 title,
15413 split,
15414 MultibufferSelectionMode::First,
15415 window,
15416 cx,
15417 )
15418 })
15419 .ok();
15420
15421 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15422 })
15423 } else {
15424 Task::ready(Ok(Navigated::No))
15425 }
15426 }
15427
15428 fn compute_target_location(
15429 &self,
15430 lsp_location: lsp::Location,
15431 server_id: LanguageServerId,
15432 window: &mut Window,
15433 cx: &mut Context<Self>,
15434 ) -> Task<anyhow::Result<Option<Location>>> {
15435 let Some(project) = self.project.clone() else {
15436 return Task::ready(Ok(None));
15437 };
15438
15439 cx.spawn_in(window, async move |editor, cx| {
15440 let location_task = editor.update(cx, |_, cx| {
15441 project.update(cx, |project, cx| {
15442 let language_server_name = project
15443 .language_server_statuses(cx)
15444 .find(|(id, _)| server_id == *id)
15445 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15446 language_server_name.map(|language_server_name| {
15447 project.open_local_buffer_via_lsp(
15448 lsp_location.uri.clone(),
15449 server_id,
15450 language_server_name,
15451 cx,
15452 )
15453 })
15454 })
15455 })?;
15456 let location = match location_task {
15457 Some(task) => Some({
15458 let target_buffer_handle = task.await.context("open local buffer")?;
15459 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15460 let target_start = target_buffer
15461 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15462 let target_end = target_buffer
15463 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15464 target_buffer.anchor_after(target_start)
15465 ..target_buffer.anchor_before(target_end)
15466 })?;
15467 Location {
15468 buffer: target_buffer_handle,
15469 range,
15470 }
15471 }),
15472 None => None,
15473 };
15474 Ok(location)
15475 })
15476 }
15477
15478 pub fn find_all_references(
15479 &mut self,
15480 _: &FindAllReferences,
15481 window: &mut Window,
15482 cx: &mut Context<Self>,
15483 ) -> Option<Task<Result<Navigated>>> {
15484 let selection = self.selections.newest::<usize>(cx);
15485 let multi_buffer = self.buffer.read(cx);
15486 let head = selection.head();
15487
15488 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15489 let head_anchor = multi_buffer_snapshot.anchor_at(
15490 head,
15491 if head < selection.tail() {
15492 Bias::Right
15493 } else {
15494 Bias::Left
15495 },
15496 );
15497
15498 match self
15499 .find_all_references_task_sources
15500 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15501 {
15502 Ok(_) => {
15503 log::info!(
15504 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15505 );
15506 return None;
15507 }
15508 Err(i) => {
15509 self.find_all_references_task_sources.insert(i, head_anchor);
15510 }
15511 }
15512
15513 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15514 let workspace = self.workspace()?;
15515 let project = workspace.read(cx).project().clone();
15516 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15517 Some(cx.spawn_in(window, async move |editor, cx| {
15518 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15519 if let Ok(i) = editor
15520 .find_all_references_task_sources
15521 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15522 {
15523 editor.find_all_references_task_sources.remove(i);
15524 }
15525 });
15526
15527 let locations = references.await?;
15528 if locations.is_empty() {
15529 return anyhow::Ok(Navigated::No);
15530 }
15531
15532 workspace.update_in(cx, |workspace, window, cx| {
15533 let title = locations
15534 .first()
15535 .as_ref()
15536 .map(|location| {
15537 let buffer = location.buffer.read(cx);
15538 format!(
15539 "References to `{}`",
15540 buffer
15541 .text_for_range(location.range.clone())
15542 .collect::<String>()
15543 )
15544 })
15545 .unwrap();
15546 Self::open_locations_in_multibuffer(
15547 workspace,
15548 locations,
15549 title,
15550 false,
15551 MultibufferSelectionMode::First,
15552 window,
15553 cx,
15554 );
15555 Navigated::Yes
15556 })
15557 }))
15558 }
15559
15560 /// Opens a multibuffer with the given project locations in it
15561 pub fn open_locations_in_multibuffer(
15562 workspace: &mut Workspace,
15563 mut locations: Vec<Location>,
15564 title: String,
15565 split: bool,
15566 multibuffer_selection_mode: MultibufferSelectionMode,
15567 window: &mut Window,
15568 cx: &mut Context<Workspace>,
15569 ) {
15570 if locations.is_empty() {
15571 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
15572 return;
15573 }
15574
15575 // If there are multiple definitions, open them in a multibuffer
15576 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15577 let mut locations = locations.into_iter().peekable();
15578 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15579 let capability = workspace.project().read(cx).capability();
15580
15581 let excerpt_buffer = cx.new(|cx| {
15582 let mut multibuffer = MultiBuffer::new(capability);
15583 while let Some(location) = locations.next() {
15584 let buffer = location.buffer.read(cx);
15585 let mut ranges_for_buffer = Vec::new();
15586 let range = location.range.to_point(buffer);
15587 ranges_for_buffer.push(range.clone());
15588
15589 while let Some(next_location) = locations.peek() {
15590 if next_location.buffer == location.buffer {
15591 ranges_for_buffer.push(next_location.range.to_point(buffer));
15592 locations.next();
15593 } else {
15594 break;
15595 }
15596 }
15597
15598 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15599 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15600 PathKey::for_buffer(&location.buffer, cx),
15601 location.buffer.clone(),
15602 ranges_for_buffer,
15603 DEFAULT_MULTIBUFFER_CONTEXT,
15604 cx,
15605 );
15606 ranges.extend(new_ranges)
15607 }
15608
15609 multibuffer.with_title(title)
15610 });
15611
15612 let editor = cx.new(|cx| {
15613 Editor::for_multibuffer(
15614 excerpt_buffer,
15615 Some(workspace.project().clone()),
15616 window,
15617 cx,
15618 )
15619 });
15620 editor.update(cx, |editor, cx| {
15621 match multibuffer_selection_mode {
15622 MultibufferSelectionMode::First => {
15623 if let Some(first_range) = ranges.first() {
15624 editor.change_selections(None, window, cx, |selections| {
15625 selections.clear_disjoint();
15626 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
15627 });
15628 }
15629 editor.highlight_background::<Self>(
15630 &ranges,
15631 |theme| theme.colors().editor_highlighted_line_background,
15632 cx,
15633 );
15634 }
15635 MultibufferSelectionMode::All => {
15636 editor.change_selections(None, window, cx, |selections| {
15637 selections.clear_disjoint();
15638 selections.select_anchor_ranges(ranges);
15639 });
15640 }
15641 }
15642 editor.register_buffers_with_language_servers(cx);
15643 });
15644
15645 let item = Box::new(editor);
15646 let item_id = item.item_id();
15647
15648 if split {
15649 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15650 } else {
15651 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15652 let (preview_item_id, preview_item_idx) =
15653 workspace.active_pane().read_with(cx, |pane, _| {
15654 (pane.preview_item_id(), pane.preview_item_idx())
15655 });
15656
15657 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15658
15659 if let Some(preview_item_id) = preview_item_id {
15660 workspace.active_pane().update(cx, |pane, cx| {
15661 pane.remove_item(preview_item_id, false, false, window, cx);
15662 });
15663 }
15664 } else {
15665 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15666 }
15667 }
15668 workspace.active_pane().update(cx, |pane, cx| {
15669 pane.set_preview_item_id(Some(item_id), cx);
15670 });
15671 }
15672
15673 pub fn rename(
15674 &mut self,
15675 _: &Rename,
15676 window: &mut Window,
15677 cx: &mut Context<Self>,
15678 ) -> Option<Task<Result<()>>> {
15679 use language::ToOffset as _;
15680
15681 let provider = self.semantics_provider.clone()?;
15682 let selection = self.selections.newest_anchor().clone();
15683 let (cursor_buffer, cursor_buffer_position) = self
15684 .buffer
15685 .read(cx)
15686 .text_anchor_for_position(selection.head(), cx)?;
15687 let (tail_buffer, cursor_buffer_position_end) = self
15688 .buffer
15689 .read(cx)
15690 .text_anchor_for_position(selection.tail(), cx)?;
15691 if tail_buffer != cursor_buffer {
15692 return None;
15693 }
15694
15695 let snapshot = cursor_buffer.read(cx).snapshot();
15696 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15697 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15698 let prepare_rename = provider
15699 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15700 .unwrap_or_else(|| Task::ready(Ok(None)));
15701 drop(snapshot);
15702
15703 Some(cx.spawn_in(window, async move |this, cx| {
15704 let rename_range = if let Some(range) = prepare_rename.await? {
15705 Some(range)
15706 } else {
15707 this.update(cx, |this, cx| {
15708 let buffer = this.buffer.read(cx).snapshot(cx);
15709 let mut buffer_highlights = this
15710 .document_highlights_for_position(selection.head(), &buffer)
15711 .filter(|highlight| {
15712 highlight.start.excerpt_id == selection.head().excerpt_id
15713 && highlight.end.excerpt_id == selection.head().excerpt_id
15714 });
15715 buffer_highlights
15716 .next()
15717 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15718 })?
15719 };
15720 if let Some(rename_range) = rename_range {
15721 this.update_in(cx, |this, window, cx| {
15722 let snapshot = cursor_buffer.read(cx).snapshot();
15723 let rename_buffer_range = rename_range.to_offset(&snapshot);
15724 let cursor_offset_in_rename_range =
15725 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15726 let cursor_offset_in_rename_range_end =
15727 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15728
15729 this.take_rename(false, window, cx);
15730 let buffer = this.buffer.read(cx).read(cx);
15731 let cursor_offset = selection.head().to_offset(&buffer);
15732 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15733 let rename_end = rename_start + rename_buffer_range.len();
15734 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15735 let mut old_highlight_id = None;
15736 let old_name: Arc<str> = buffer
15737 .chunks(rename_start..rename_end, true)
15738 .map(|chunk| {
15739 if old_highlight_id.is_none() {
15740 old_highlight_id = chunk.syntax_highlight_id;
15741 }
15742 chunk.text
15743 })
15744 .collect::<String>()
15745 .into();
15746
15747 drop(buffer);
15748
15749 // Position the selection in the rename editor so that it matches the current selection.
15750 this.show_local_selections = false;
15751 let rename_editor = cx.new(|cx| {
15752 let mut editor = Editor::single_line(window, cx);
15753 editor.buffer.update(cx, |buffer, cx| {
15754 buffer.edit([(0..0, old_name.clone())], None, cx)
15755 });
15756 let rename_selection_range = match cursor_offset_in_rename_range
15757 .cmp(&cursor_offset_in_rename_range_end)
15758 {
15759 Ordering::Equal => {
15760 editor.select_all(&SelectAll, window, cx);
15761 return editor;
15762 }
15763 Ordering::Less => {
15764 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15765 }
15766 Ordering::Greater => {
15767 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15768 }
15769 };
15770 if rename_selection_range.end > old_name.len() {
15771 editor.select_all(&SelectAll, window, cx);
15772 } else {
15773 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15774 s.select_ranges([rename_selection_range]);
15775 });
15776 }
15777 editor
15778 });
15779 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15780 if e == &EditorEvent::Focused {
15781 cx.emit(EditorEvent::FocusedIn)
15782 }
15783 })
15784 .detach();
15785
15786 let write_highlights =
15787 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
15788 let read_highlights =
15789 this.clear_background_highlights::<DocumentHighlightRead>(cx);
15790 let ranges = write_highlights
15791 .iter()
15792 .flat_map(|(_, ranges)| ranges.iter())
15793 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
15794 .cloned()
15795 .collect();
15796
15797 this.highlight_text::<Rename>(
15798 ranges,
15799 HighlightStyle {
15800 fade_out: Some(0.6),
15801 ..Default::default()
15802 },
15803 cx,
15804 );
15805 let rename_focus_handle = rename_editor.focus_handle(cx);
15806 window.focus(&rename_focus_handle);
15807 let block_id = this.insert_blocks(
15808 [BlockProperties {
15809 style: BlockStyle::Flex,
15810 placement: BlockPlacement::Below(range.start),
15811 height: Some(1),
15812 render: Arc::new({
15813 let rename_editor = rename_editor.clone();
15814 move |cx: &mut BlockContext| {
15815 let mut text_style = cx.editor_style.text.clone();
15816 if let Some(highlight_style) = old_highlight_id
15817 .and_then(|h| h.style(&cx.editor_style.syntax))
15818 {
15819 text_style = text_style.highlight(highlight_style);
15820 }
15821 div()
15822 .block_mouse_except_scroll()
15823 .pl(cx.anchor_x)
15824 .child(EditorElement::new(
15825 &rename_editor,
15826 EditorStyle {
15827 background: cx.theme().system().transparent,
15828 local_player: cx.editor_style.local_player,
15829 text: text_style,
15830 scrollbar_width: cx.editor_style.scrollbar_width,
15831 syntax: cx.editor_style.syntax.clone(),
15832 status: cx.editor_style.status.clone(),
15833 inlay_hints_style: HighlightStyle {
15834 font_weight: Some(FontWeight::BOLD),
15835 ..make_inlay_hints_style(cx.app)
15836 },
15837 inline_completion_styles: make_suggestion_styles(
15838 cx.app,
15839 ),
15840 ..EditorStyle::default()
15841 },
15842 ))
15843 .into_any_element()
15844 }
15845 }),
15846 priority: 0,
15847 render_in_minimap: true,
15848 }],
15849 Some(Autoscroll::fit()),
15850 cx,
15851 )[0];
15852 this.pending_rename = Some(RenameState {
15853 range,
15854 old_name,
15855 editor: rename_editor,
15856 block_id,
15857 });
15858 })?;
15859 }
15860
15861 Ok(())
15862 }))
15863 }
15864
15865 pub fn confirm_rename(
15866 &mut self,
15867 _: &ConfirmRename,
15868 window: &mut Window,
15869 cx: &mut Context<Self>,
15870 ) -> Option<Task<Result<()>>> {
15871 let rename = self.take_rename(false, window, cx)?;
15872 let workspace = self.workspace()?.downgrade();
15873 let (buffer, start) = self
15874 .buffer
15875 .read(cx)
15876 .text_anchor_for_position(rename.range.start, cx)?;
15877 let (end_buffer, _) = self
15878 .buffer
15879 .read(cx)
15880 .text_anchor_for_position(rename.range.end, cx)?;
15881 if buffer != end_buffer {
15882 return None;
15883 }
15884
15885 let old_name = rename.old_name;
15886 let new_name = rename.editor.read(cx).text(cx);
15887
15888 let rename = self.semantics_provider.as_ref()?.perform_rename(
15889 &buffer,
15890 start,
15891 new_name.clone(),
15892 cx,
15893 )?;
15894
15895 Some(cx.spawn_in(window, async move |editor, cx| {
15896 let project_transaction = rename.await?;
15897 Self::open_project_transaction(
15898 &editor,
15899 workspace,
15900 project_transaction,
15901 format!("Rename: {} → {}", old_name, new_name),
15902 cx,
15903 )
15904 .await?;
15905
15906 editor.update(cx, |editor, cx| {
15907 editor.refresh_document_highlights(cx);
15908 })?;
15909 Ok(())
15910 }))
15911 }
15912
15913 fn take_rename(
15914 &mut self,
15915 moving_cursor: bool,
15916 window: &mut Window,
15917 cx: &mut Context<Self>,
15918 ) -> Option<RenameState> {
15919 let rename = self.pending_rename.take()?;
15920 if rename.editor.focus_handle(cx).is_focused(window) {
15921 window.focus(&self.focus_handle);
15922 }
15923
15924 self.remove_blocks(
15925 [rename.block_id].into_iter().collect(),
15926 Some(Autoscroll::fit()),
15927 cx,
15928 );
15929 self.clear_highlights::<Rename>(cx);
15930 self.show_local_selections = true;
15931
15932 if moving_cursor {
15933 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15934 editor.selections.newest::<usize>(cx).head()
15935 });
15936
15937 // Update the selection to match the position of the selection inside
15938 // the rename editor.
15939 let snapshot = self.buffer.read(cx).read(cx);
15940 let rename_range = rename.range.to_offset(&snapshot);
15941 let cursor_in_editor = snapshot
15942 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15943 .min(rename_range.end);
15944 drop(snapshot);
15945
15946 self.change_selections(None, window, cx, |s| {
15947 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15948 });
15949 } else {
15950 self.refresh_document_highlights(cx);
15951 }
15952
15953 Some(rename)
15954 }
15955
15956 pub fn pending_rename(&self) -> Option<&RenameState> {
15957 self.pending_rename.as_ref()
15958 }
15959
15960 fn format(
15961 &mut self,
15962 _: &Format,
15963 window: &mut Window,
15964 cx: &mut Context<Self>,
15965 ) -> Option<Task<Result<()>>> {
15966 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15967
15968 let project = match &self.project {
15969 Some(project) => project.clone(),
15970 None => return None,
15971 };
15972
15973 Some(self.perform_format(
15974 project,
15975 FormatTrigger::Manual,
15976 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
15977 window,
15978 cx,
15979 ))
15980 }
15981
15982 fn format_selections(
15983 &mut self,
15984 _: &FormatSelections,
15985 window: &mut Window,
15986 cx: &mut Context<Self>,
15987 ) -> Option<Task<Result<()>>> {
15988 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15989
15990 let project = match &self.project {
15991 Some(project) => project.clone(),
15992 None => return None,
15993 };
15994
15995 let ranges = self
15996 .selections
15997 .all_adjusted(cx)
15998 .into_iter()
15999 .map(|selection| selection.range())
16000 .collect_vec();
16001
16002 Some(self.perform_format(
16003 project,
16004 FormatTrigger::Manual,
16005 FormatTarget::Ranges(ranges),
16006 window,
16007 cx,
16008 ))
16009 }
16010
16011 fn perform_format(
16012 &mut self,
16013 project: Entity<Project>,
16014 trigger: FormatTrigger,
16015 target: FormatTarget,
16016 window: &mut Window,
16017 cx: &mut Context<Self>,
16018 ) -> Task<Result<()>> {
16019 let buffer = self.buffer.clone();
16020 let (buffers, target) = match target {
16021 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16022 FormatTarget::Ranges(selection_ranges) => {
16023 let multi_buffer = buffer.read(cx);
16024 let snapshot = multi_buffer.read(cx);
16025 let mut buffers = HashSet::default();
16026 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16027 BTreeMap::new();
16028 for selection_range in selection_ranges {
16029 for (buffer, buffer_range, _) in
16030 snapshot.range_to_buffer_ranges(selection_range)
16031 {
16032 let buffer_id = buffer.remote_id();
16033 let start = buffer.anchor_before(buffer_range.start);
16034 let end = buffer.anchor_after(buffer_range.end);
16035 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16036 buffer_id_to_ranges
16037 .entry(buffer_id)
16038 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16039 .or_insert_with(|| vec![start..end]);
16040 }
16041 }
16042 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16043 }
16044 };
16045
16046 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16047 let selections_prev = transaction_id_prev
16048 .and_then(|transaction_id_prev| {
16049 // default to selections as they were after the last edit, if we have them,
16050 // instead of how they are now.
16051 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16052 // will take you back to where you made the last edit, instead of staying where you scrolled
16053 self.selection_history
16054 .transaction(transaction_id_prev)
16055 .map(|t| t.0.clone())
16056 })
16057 .unwrap_or_else(|| {
16058 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16059 self.selections.disjoint_anchors()
16060 });
16061
16062 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16063 let format = project.update(cx, |project, cx| {
16064 project.format(buffers, target, true, trigger, cx)
16065 });
16066
16067 cx.spawn_in(window, async move |editor, cx| {
16068 let transaction = futures::select_biased! {
16069 transaction = format.log_err().fuse() => transaction,
16070 () = timeout => {
16071 log::warn!("timed out waiting for formatting");
16072 None
16073 }
16074 };
16075
16076 buffer
16077 .update(cx, |buffer, cx| {
16078 if let Some(transaction) = transaction {
16079 if !buffer.is_singleton() {
16080 buffer.push_transaction(&transaction.0, cx);
16081 }
16082 }
16083 cx.notify();
16084 })
16085 .ok();
16086
16087 if let Some(transaction_id_now) =
16088 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16089 {
16090 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16091 if has_new_transaction {
16092 _ = editor.update(cx, |editor, _| {
16093 editor
16094 .selection_history
16095 .insert_transaction(transaction_id_now, selections_prev);
16096 });
16097 }
16098 }
16099
16100 Ok(())
16101 })
16102 }
16103
16104 fn organize_imports(
16105 &mut self,
16106 _: &OrganizeImports,
16107 window: &mut Window,
16108 cx: &mut Context<Self>,
16109 ) -> Option<Task<Result<()>>> {
16110 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16111 let project = match &self.project {
16112 Some(project) => project.clone(),
16113 None => return None,
16114 };
16115 Some(self.perform_code_action_kind(
16116 project,
16117 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16118 window,
16119 cx,
16120 ))
16121 }
16122
16123 fn perform_code_action_kind(
16124 &mut self,
16125 project: Entity<Project>,
16126 kind: CodeActionKind,
16127 window: &mut Window,
16128 cx: &mut Context<Self>,
16129 ) -> Task<Result<()>> {
16130 let buffer = self.buffer.clone();
16131 let buffers = buffer.read(cx).all_buffers();
16132 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16133 let apply_action = project.update(cx, |project, cx| {
16134 project.apply_code_action_kind(buffers, kind, true, cx)
16135 });
16136 cx.spawn_in(window, async move |_, cx| {
16137 let transaction = futures::select_biased! {
16138 () = timeout => {
16139 log::warn!("timed out waiting for executing code action");
16140 None
16141 }
16142 transaction = apply_action.log_err().fuse() => transaction,
16143 };
16144 buffer
16145 .update(cx, |buffer, cx| {
16146 // check if we need this
16147 if let Some(transaction) = transaction {
16148 if !buffer.is_singleton() {
16149 buffer.push_transaction(&transaction.0, cx);
16150 }
16151 }
16152 cx.notify();
16153 })
16154 .ok();
16155 Ok(())
16156 })
16157 }
16158
16159 fn restart_language_server(
16160 &mut self,
16161 _: &RestartLanguageServer,
16162 _: &mut Window,
16163 cx: &mut Context<Self>,
16164 ) {
16165 if let Some(project) = self.project.clone() {
16166 self.buffer.update(cx, |multi_buffer, cx| {
16167 project.update(cx, |project, cx| {
16168 project.restart_language_servers_for_buffers(
16169 multi_buffer.all_buffers().into_iter().collect(),
16170 cx,
16171 );
16172 });
16173 })
16174 }
16175 }
16176
16177 fn stop_language_server(
16178 &mut self,
16179 _: &StopLanguageServer,
16180 _: &mut Window,
16181 cx: &mut Context<Self>,
16182 ) {
16183 if let Some(project) = self.project.clone() {
16184 self.buffer.update(cx, |multi_buffer, cx| {
16185 project.update(cx, |project, cx| {
16186 project.stop_language_servers_for_buffers(
16187 multi_buffer.all_buffers().into_iter().collect(),
16188 cx,
16189 );
16190 cx.emit(project::Event::RefreshInlayHints);
16191 });
16192 });
16193 }
16194 }
16195
16196 fn cancel_language_server_work(
16197 workspace: &mut Workspace,
16198 _: &actions::CancelLanguageServerWork,
16199 _: &mut Window,
16200 cx: &mut Context<Workspace>,
16201 ) {
16202 let project = workspace.project();
16203 let buffers = workspace
16204 .active_item(cx)
16205 .and_then(|item| item.act_as::<Editor>(cx))
16206 .map_or(HashSet::default(), |editor| {
16207 editor.read(cx).buffer.read(cx).all_buffers()
16208 });
16209 project.update(cx, |project, cx| {
16210 project.cancel_language_server_work_for_buffers(buffers, cx);
16211 });
16212 }
16213
16214 fn show_character_palette(
16215 &mut self,
16216 _: &ShowCharacterPalette,
16217 window: &mut Window,
16218 _: &mut Context<Self>,
16219 ) {
16220 window.show_character_palette();
16221 }
16222
16223 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16224 if !self.diagnostics_enabled() {
16225 return;
16226 }
16227
16228 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16229 let buffer = self.buffer.read(cx).snapshot(cx);
16230 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16231 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16232 let is_valid = buffer
16233 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16234 .any(|entry| {
16235 entry.diagnostic.is_primary
16236 && !entry.range.is_empty()
16237 && entry.range.start == primary_range_start
16238 && entry.diagnostic.message == active_diagnostics.active_message
16239 });
16240
16241 if !is_valid {
16242 self.dismiss_diagnostics(cx);
16243 }
16244 }
16245 }
16246
16247 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16248 match &self.active_diagnostics {
16249 ActiveDiagnostic::Group(group) => Some(group),
16250 _ => None,
16251 }
16252 }
16253
16254 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16255 if !self.diagnostics_enabled() {
16256 return;
16257 }
16258 self.dismiss_diagnostics(cx);
16259 self.active_diagnostics = ActiveDiagnostic::All;
16260 }
16261
16262 fn activate_diagnostics(
16263 &mut self,
16264 buffer_id: BufferId,
16265 diagnostic: DiagnosticEntry<usize>,
16266 window: &mut Window,
16267 cx: &mut Context<Self>,
16268 ) {
16269 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16270 return;
16271 }
16272 self.dismiss_diagnostics(cx);
16273 let snapshot = self.snapshot(window, cx);
16274 let buffer = self.buffer.read(cx).snapshot(cx);
16275 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16276 return;
16277 };
16278
16279 let diagnostic_group = buffer
16280 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16281 .collect::<Vec<_>>();
16282
16283 let blocks =
16284 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16285
16286 let blocks = self.display_map.update(cx, |display_map, cx| {
16287 display_map.insert_blocks(blocks, cx).into_iter().collect()
16288 });
16289 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16290 active_range: buffer.anchor_before(diagnostic.range.start)
16291 ..buffer.anchor_after(diagnostic.range.end),
16292 active_message: diagnostic.diagnostic.message.clone(),
16293 group_id: diagnostic.diagnostic.group_id,
16294 blocks,
16295 });
16296 cx.notify();
16297 }
16298
16299 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16300 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16301 return;
16302 };
16303
16304 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16305 if let ActiveDiagnostic::Group(group) = prev {
16306 self.display_map.update(cx, |display_map, cx| {
16307 display_map.remove_blocks(group.blocks, cx);
16308 });
16309 cx.notify();
16310 }
16311 }
16312
16313 /// Disable inline diagnostics rendering for this editor.
16314 pub fn disable_inline_diagnostics(&mut self) {
16315 self.inline_diagnostics_enabled = false;
16316 self.inline_diagnostics_update = Task::ready(());
16317 self.inline_diagnostics.clear();
16318 }
16319
16320 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16321 self.diagnostics_enabled = false;
16322 self.dismiss_diagnostics(cx);
16323 self.inline_diagnostics_update = Task::ready(());
16324 self.inline_diagnostics.clear();
16325 }
16326
16327 pub fn diagnostics_enabled(&self) -> bool {
16328 self.diagnostics_enabled && self.mode.is_full()
16329 }
16330
16331 pub fn inline_diagnostics_enabled(&self) -> bool {
16332 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16333 }
16334
16335 pub fn show_inline_diagnostics(&self) -> bool {
16336 self.show_inline_diagnostics
16337 }
16338
16339 pub fn toggle_inline_diagnostics(
16340 &mut self,
16341 _: &ToggleInlineDiagnostics,
16342 window: &mut Window,
16343 cx: &mut Context<Editor>,
16344 ) {
16345 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16346 self.refresh_inline_diagnostics(false, window, cx);
16347 }
16348
16349 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16350 self.diagnostics_max_severity = severity;
16351 self.display_map.update(cx, |display_map, _| {
16352 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16353 });
16354 }
16355
16356 pub fn toggle_diagnostics(
16357 &mut self,
16358 _: &ToggleDiagnostics,
16359 window: &mut Window,
16360 cx: &mut Context<Editor>,
16361 ) {
16362 if !self.diagnostics_enabled() {
16363 return;
16364 }
16365
16366 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16367 EditorSettings::get_global(cx)
16368 .diagnostics_max_severity
16369 .filter(|severity| severity != &DiagnosticSeverity::Off)
16370 .unwrap_or(DiagnosticSeverity::Hint)
16371 } else {
16372 DiagnosticSeverity::Off
16373 };
16374 self.set_max_diagnostics_severity(new_severity, cx);
16375 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16376 self.active_diagnostics = ActiveDiagnostic::None;
16377 self.inline_diagnostics_update = Task::ready(());
16378 self.inline_diagnostics.clear();
16379 } else {
16380 self.refresh_inline_diagnostics(false, window, cx);
16381 }
16382
16383 cx.notify();
16384 }
16385
16386 pub fn toggle_minimap(
16387 &mut self,
16388 _: &ToggleMinimap,
16389 window: &mut Window,
16390 cx: &mut Context<Editor>,
16391 ) {
16392 if self.supports_minimap(cx) {
16393 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16394 }
16395 }
16396
16397 fn refresh_inline_diagnostics(
16398 &mut self,
16399 debounce: bool,
16400 window: &mut Window,
16401 cx: &mut Context<Self>,
16402 ) {
16403 let max_severity = ProjectSettings::get_global(cx)
16404 .diagnostics
16405 .inline
16406 .max_severity
16407 .unwrap_or(self.diagnostics_max_severity);
16408
16409 if !self.inline_diagnostics_enabled()
16410 || !self.show_inline_diagnostics
16411 || max_severity == DiagnosticSeverity::Off
16412 {
16413 self.inline_diagnostics_update = Task::ready(());
16414 self.inline_diagnostics.clear();
16415 return;
16416 }
16417
16418 let debounce_ms = ProjectSettings::get_global(cx)
16419 .diagnostics
16420 .inline
16421 .update_debounce_ms;
16422 let debounce = if debounce && debounce_ms > 0 {
16423 Some(Duration::from_millis(debounce_ms))
16424 } else {
16425 None
16426 };
16427 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16428 if let Some(debounce) = debounce {
16429 cx.background_executor().timer(debounce).await;
16430 }
16431 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16432 editor
16433 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16434 .ok()
16435 }) else {
16436 return;
16437 };
16438
16439 let new_inline_diagnostics = cx
16440 .background_spawn(async move {
16441 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16442 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16443 let message = diagnostic_entry
16444 .diagnostic
16445 .message
16446 .split_once('\n')
16447 .map(|(line, _)| line)
16448 .map(SharedString::new)
16449 .unwrap_or_else(|| {
16450 SharedString::from(diagnostic_entry.diagnostic.message)
16451 });
16452 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16453 let (Ok(i) | Err(i)) = inline_diagnostics
16454 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16455 inline_diagnostics.insert(
16456 i,
16457 (
16458 start_anchor,
16459 InlineDiagnostic {
16460 message,
16461 group_id: diagnostic_entry.diagnostic.group_id,
16462 start: diagnostic_entry.range.start.to_point(&snapshot),
16463 is_primary: diagnostic_entry.diagnostic.is_primary,
16464 severity: diagnostic_entry.diagnostic.severity,
16465 },
16466 ),
16467 );
16468 }
16469 inline_diagnostics
16470 })
16471 .await;
16472
16473 editor
16474 .update(cx, |editor, cx| {
16475 editor.inline_diagnostics = new_inline_diagnostics;
16476 cx.notify();
16477 })
16478 .ok();
16479 });
16480 }
16481
16482 fn pull_diagnostics(
16483 &mut self,
16484 buffer_id: Option<BufferId>,
16485 window: &Window,
16486 cx: &mut Context<Self>,
16487 ) -> Option<()> {
16488 if !self.mode().is_full() {
16489 return None;
16490 }
16491 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16492 .diagnostics
16493 .lsp_pull_diagnostics;
16494 if !pull_diagnostics_settings.enabled {
16495 return None;
16496 }
16497 let project = self.project.as_ref()?.downgrade();
16498 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16499 let mut buffers = self.buffer.read(cx).all_buffers();
16500 if let Some(buffer_id) = buffer_id {
16501 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16502 }
16503
16504 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16505 cx.background_executor().timer(debounce).await;
16506
16507 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16508 buffers
16509 .into_iter()
16510 .filter_map(|buffer| {
16511 project
16512 .update(cx, |project, cx| {
16513 project.lsp_store().update(cx, |lsp_store, cx| {
16514 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
16515 })
16516 })
16517 .ok()
16518 })
16519 .collect::<FuturesUnordered<_>>()
16520 }) else {
16521 return;
16522 };
16523
16524 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16525 match pull_task {
16526 Ok(()) => {
16527 if editor
16528 .update_in(cx, |editor, window, cx| {
16529 editor.update_diagnostics_state(window, cx);
16530 })
16531 .is_err()
16532 {
16533 return;
16534 }
16535 }
16536 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16537 }
16538 }
16539 });
16540
16541 Some(())
16542 }
16543
16544 pub fn set_selections_from_remote(
16545 &mut self,
16546 selections: Vec<Selection<Anchor>>,
16547 pending_selection: Option<Selection<Anchor>>,
16548 window: &mut Window,
16549 cx: &mut Context<Self>,
16550 ) {
16551 let old_cursor_position = self.selections.newest_anchor().head();
16552 self.selections.change_with(cx, |s| {
16553 s.select_anchors(selections);
16554 if let Some(pending_selection) = pending_selection {
16555 s.set_pending(pending_selection, SelectMode::Character);
16556 } else {
16557 s.clear_pending();
16558 }
16559 });
16560 self.selections_did_change(
16561 false,
16562 &old_cursor_position,
16563 SelectionEffects::default(),
16564 window,
16565 cx,
16566 );
16567 }
16568
16569 pub fn transact(
16570 &mut self,
16571 window: &mut Window,
16572 cx: &mut Context<Self>,
16573 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16574 ) -> Option<TransactionId> {
16575 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16576 this.start_transaction_at(Instant::now(), window, cx);
16577 update(this, window, cx);
16578 this.end_transaction_at(Instant::now(), cx)
16579 })
16580 }
16581
16582 pub fn start_transaction_at(
16583 &mut self,
16584 now: Instant,
16585 window: &mut Window,
16586 cx: &mut Context<Self>,
16587 ) {
16588 self.end_selection(window, cx);
16589 if let Some(tx_id) = self
16590 .buffer
16591 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16592 {
16593 self.selection_history
16594 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16595 cx.emit(EditorEvent::TransactionBegun {
16596 transaction_id: tx_id,
16597 })
16598 }
16599 }
16600
16601 pub fn end_transaction_at(
16602 &mut self,
16603 now: Instant,
16604 cx: &mut Context<Self>,
16605 ) -> Option<TransactionId> {
16606 if let Some(transaction_id) = self
16607 .buffer
16608 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16609 {
16610 if let Some((_, end_selections)) =
16611 self.selection_history.transaction_mut(transaction_id)
16612 {
16613 *end_selections = Some(self.selections.disjoint_anchors());
16614 } else {
16615 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16616 }
16617
16618 cx.emit(EditorEvent::Edited { transaction_id });
16619 Some(transaction_id)
16620 } else {
16621 None
16622 }
16623 }
16624
16625 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16626 if self.selection_mark_mode {
16627 self.change_selections(None, window, cx, |s| {
16628 s.move_with(|_, sel| {
16629 sel.collapse_to(sel.head(), SelectionGoal::None);
16630 });
16631 })
16632 }
16633 self.selection_mark_mode = true;
16634 cx.notify();
16635 }
16636
16637 pub fn swap_selection_ends(
16638 &mut self,
16639 _: &actions::SwapSelectionEnds,
16640 window: &mut Window,
16641 cx: &mut Context<Self>,
16642 ) {
16643 self.change_selections(None, window, cx, |s| {
16644 s.move_with(|_, sel| {
16645 if sel.start != sel.end {
16646 sel.reversed = !sel.reversed
16647 }
16648 });
16649 });
16650 self.request_autoscroll(Autoscroll::newest(), cx);
16651 cx.notify();
16652 }
16653
16654 pub fn toggle_fold(
16655 &mut self,
16656 _: &actions::ToggleFold,
16657 window: &mut Window,
16658 cx: &mut Context<Self>,
16659 ) {
16660 if self.is_singleton(cx) {
16661 let selection = self.selections.newest::<Point>(cx);
16662
16663 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16664 let range = if selection.is_empty() {
16665 let point = selection.head().to_display_point(&display_map);
16666 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16667 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16668 .to_point(&display_map);
16669 start..end
16670 } else {
16671 selection.range()
16672 };
16673 if display_map.folds_in_range(range).next().is_some() {
16674 self.unfold_lines(&Default::default(), window, cx)
16675 } else {
16676 self.fold(&Default::default(), window, cx)
16677 }
16678 } else {
16679 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16680 let buffer_ids: HashSet<_> = self
16681 .selections
16682 .disjoint_anchor_ranges()
16683 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16684 .collect();
16685
16686 let should_unfold = buffer_ids
16687 .iter()
16688 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16689
16690 for buffer_id in buffer_ids {
16691 if should_unfold {
16692 self.unfold_buffer(buffer_id, cx);
16693 } else {
16694 self.fold_buffer(buffer_id, cx);
16695 }
16696 }
16697 }
16698 }
16699
16700 pub fn toggle_fold_recursive(
16701 &mut self,
16702 _: &actions::ToggleFoldRecursive,
16703 window: &mut Window,
16704 cx: &mut Context<Self>,
16705 ) {
16706 let selection = self.selections.newest::<Point>(cx);
16707
16708 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16709 let range = if selection.is_empty() {
16710 let point = selection.head().to_display_point(&display_map);
16711 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16712 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16713 .to_point(&display_map);
16714 start..end
16715 } else {
16716 selection.range()
16717 };
16718 if display_map.folds_in_range(range).next().is_some() {
16719 self.unfold_recursive(&Default::default(), window, cx)
16720 } else {
16721 self.fold_recursive(&Default::default(), window, cx)
16722 }
16723 }
16724
16725 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16726 if self.is_singleton(cx) {
16727 let mut to_fold = Vec::new();
16728 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16729 let selections = self.selections.all_adjusted(cx);
16730
16731 for selection in selections {
16732 let range = selection.range().sorted();
16733 let buffer_start_row = range.start.row;
16734
16735 if range.start.row != range.end.row {
16736 let mut found = false;
16737 let mut row = range.start.row;
16738 while row <= range.end.row {
16739 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16740 {
16741 found = true;
16742 row = crease.range().end.row + 1;
16743 to_fold.push(crease);
16744 } else {
16745 row += 1
16746 }
16747 }
16748 if found {
16749 continue;
16750 }
16751 }
16752
16753 for row in (0..=range.start.row).rev() {
16754 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16755 if crease.range().end.row >= buffer_start_row {
16756 to_fold.push(crease);
16757 if row <= range.start.row {
16758 break;
16759 }
16760 }
16761 }
16762 }
16763 }
16764
16765 self.fold_creases(to_fold, true, window, cx);
16766 } else {
16767 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16768 let buffer_ids = self
16769 .selections
16770 .disjoint_anchor_ranges()
16771 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16772 .collect::<HashSet<_>>();
16773 for buffer_id in buffer_ids {
16774 self.fold_buffer(buffer_id, cx);
16775 }
16776 }
16777 }
16778
16779 fn fold_at_level(
16780 &mut self,
16781 fold_at: &FoldAtLevel,
16782 window: &mut Window,
16783 cx: &mut Context<Self>,
16784 ) {
16785 if !self.buffer.read(cx).is_singleton() {
16786 return;
16787 }
16788
16789 let fold_at_level = fold_at.0;
16790 let snapshot = self.buffer.read(cx).snapshot(cx);
16791 let mut to_fold = Vec::new();
16792 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16793
16794 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16795 while start_row < end_row {
16796 match self
16797 .snapshot(window, cx)
16798 .crease_for_buffer_row(MultiBufferRow(start_row))
16799 {
16800 Some(crease) => {
16801 let nested_start_row = crease.range().start.row + 1;
16802 let nested_end_row = crease.range().end.row;
16803
16804 if current_level < fold_at_level {
16805 stack.push((nested_start_row, nested_end_row, current_level + 1));
16806 } else if current_level == fold_at_level {
16807 to_fold.push(crease);
16808 }
16809
16810 start_row = nested_end_row + 1;
16811 }
16812 None => start_row += 1,
16813 }
16814 }
16815 }
16816
16817 self.fold_creases(to_fold, true, window, cx);
16818 }
16819
16820 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16821 if self.buffer.read(cx).is_singleton() {
16822 let mut fold_ranges = Vec::new();
16823 let snapshot = self.buffer.read(cx).snapshot(cx);
16824
16825 for row in 0..snapshot.max_row().0 {
16826 if let Some(foldable_range) = self
16827 .snapshot(window, cx)
16828 .crease_for_buffer_row(MultiBufferRow(row))
16829 {
16830 fold_ranges.push(foldable_range);
16831 }
16832 }
16833
16834 self.fold_creases(fold_ranges, true, window, cx);
16835 } else {
16836 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16837 editor
16838 .update_in(cx, |editor, _, cx| {
16839 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16840 editor.fold_buffer(buffer_id, cx);
16841 }
16842 })
16843 .ok();
16844 });
16845 }
16846 }
16847
16848 pub fn fold_function_bodies(
16849 &mut self,
16850 _: &actions::FoldFunctionBodies,
16851 window: &mut Window,
16852 cx: &mut Context<Self>,
16853 ) {
16854 let snapshot = self.buffer.read(cx).snapshot(cx);
16855
16856 let ranges = snapshot
16857 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16858 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16859 .collect::<Vec<_>>();
16860
16861 let creases = ranges
16862 .into_iter()
16863 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16864 .collect();
16865
16866 self.fold_creases(creases, true, window, cx);
16867 }
16868
16869 pub fn fold_recursive(
16870 &mut self,
16871 _: &actions::FoldRecursive,
16872 window: &mut Window,
16873 cx: &mut Context<Self>,
16874 ) {
16875 let mut to_fold = Vec::new();
16876 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16877 let selections = self.selections.all_adjusted(cx);
16878
16879 for selection in selections {
16880 let range = selection.range().sorted();
16881 let buffer_start_row = range.start.row;
16882
16883 if range.start.row != range.end.row {
16884 let mut found = false;
16885 for row in range.start.row..=range.end.row {
16886 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16887 found = true;
16888 to_fold.push(crease);
16889 }
16890 }
16891 if found {
16892 continue;
16893 }
16894 }
16895
16896 for row in (0..=range.start.row).rev() {
16897 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16898 if crease.range().end.row >= buffer_start_row {
16899 to_fold.push(crease);
16900 } else {
16901 break;
16902 }
16903 }
16904 }
16905 }
16906
16907 self.fold_creases(to_fold, true, window, cx);
16908 }
16909
16910 pub fn fold_at(
16911 &mut self,
16912 buffer_row: MultiBufferRow,
16913 window: &mut Window,
16914 cx: &mut Context<Self>,
16915 ) {
16916 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16917
16918 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16919 let autoscroll = self
16920 .selections
16921 .all::<Point>(cx)
16922 .iter()
16923 .any(|selection| crease.range().overlaps(&selection.range()));
16924
16925 self.fold_creases(vec![crease], autoscroll, window, cx);
16926 }
16927 }
16928
16929 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16930 if self.is_singleton(cx) {
16931 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16932 let buffer = &display_map.buffer_snapshot;
16933 let selections = self.selections.all::<Point>(cx);
16934 let ranges = selections
16935 .iter()
16936 .map(|s| {
16937 let range = s.display_range(&display_map).sorted();
16938 let mut start = range.start.to_point(&display_map);
16939 let mut end = range.end.to_point(&display_map);
16940 start.column = 0;
16941 end.column = buffer.line_len(MultiBufferRow(end.row));
16942 start..end
16943 })
16944 .collect::<Vec<_>>();
16945
16946 self.unfold_ranges(&ranges, true, true, cx);
16947 } else {
16948 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16949 let buffer_ids = self
16950 .selections
16951 .disjoint_anchor_ranges()
16952 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16953 .collect::<HashSet<_>>();
16954 for buffer_id in buffer_ids {
16955 self.unfold_buffer(buffer_id, cx);
16956 }
16957 }
16958 }
16959
16960 pub fn unfold_recursive(
16961 &mut self,
16962 _: &UnfoldRecursive,
16963 _window: &mut Window,
16964 cx: &mut Context<Self>,
16965 ) {
16966 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16967 let selections = self.selections.all::<Point>(cx);
16968 let ranges = selections
16969 .iter()
16970 .map(|s| {
16971 let mut range = s.display_range(&display_map).sorted();
16972 *range.start.column_mut() = 0;
16973 *range.end.column_mut() = display_map.line_len(range.end.row());
16974 let start = range.start.to_point(&display_map);
16975 let end = range.end.to_point(&display_map);
16976 start..end
16977 })
16978 .collect::<Vec<_>>();
16979
16980 self.unfold_ranges(&ranges, true, true, cx);
16981 }
16982
16983 pub fn unfold_at(
16984 &mut self,
16985 buffer_row: MultiBufferRow,
16986 _window: &mut Window,
16987 cx: &mut Context<Self>,
16988 ) {
16989 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16990
16991 let intersection_range = Point::new(buffer_row.0, 0)
16992 ..Point::new(
16993 buffer_row.0,
16994 display_map.buffer_snapshot.line_len(buffer_row),
16995 );
16996
16997 let autoscroll = self
16998 .selections
16999 .all::<Point>(cx)
17000 .iter()
17001 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17002
17003 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17004 }
17005
17006 pub fn unfold_all(
17007 &mut self,
17008 _: &actions::UnfoldAll,
17009 _window: &mut Window,
17010 cx: &mut Context<Self>,
17011 ) {
17012 if self.buffer.read(cx).is_singleton() {
17013 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17014 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17015 } else {
17016 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17017 editor
17018 .update(cx, |editor, cx| {
17019 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17020 editor.unfold_buffer(buffer_id, cx);
17021 }
17022 })
17023 .ok();
17024 });
17025 }
17026 }
17027
17028 pub fn fold_selected_ranges(
17029 &mut self,
17030 _: &FoldSelectedRanges,
17031 window: &mut Window,
17032 cx: &mut Context<Self>,
17033 ) {
17034 let selections = self.selections.all_adjusted(cx);
17035 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17036 let ranges = selections
17037 .into_iter()
17038 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17039 .collect::<Vec<_>>();
17040 self.fold_creases(ranges, true, window, cx);
17041 }
17042
17043 pub fn fold_ranges<T: ToOffset + Clone>(
17044 &mut self,
17045 ranges: Vec<Range<T>>,
17046 auto_scroll: bool,
17047 window: &mut Window,
17048 cx: &mut Context<Self>,
17049 ) {
17050 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17051 let ranges = ranges
17052 .into_iter()
17053 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17054 .collect::<Vec<_>>();
17055 self.fold_creases(ranges, auto_scroll, window, cx);
17056 }
17057
17058 pub fn fold_creases<T: ToOffset + Clone>(
17059 &mut self,
17060 creases: Vec<Crease<T>>,
17061 auto_scroll: bool,
17062 _window: &mut Window,
17063 cx: &mut Context<Self>,
17064 ) {
17065 if creases.is_empty() {
17066 return;
17067 }
17068
17069 let mut buffers_affected = HashSet::default();
17070 let multi_buffer = self.buffer().read(cx);
17071 for crease in &creases {
17072 if let Some((_, buffer, _)) =
17073 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
17074 {
17075 buffers_affected.insert(buffer.read(cx).remote_id());
17076 };
17077 }
17078
17079 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17080
17081 if auto_scroll {
17082 self.request_autoscroll(Autoscroll::fit(), cx);
17083 }
17084
17085 cx.notify();
17086
17087 self.scrollbar_marker_state.dirty = true;
17088 self.folds_did_change(cx);
17089 }
17090
17091 /// Removes any folds whose ranges intersect any of the given ranges.
17092 pub fn unfold_ranges<T: ToOffset + Clone>(
17093 &mut self,
17094 ranges: &[Range<T>],
17095 inclusive: bool,
17096 auto_scroll: bool,
17097 cx: &mut Context<Self>,
17098 ) {
17099 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17100 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17101 });
17102 self.folds_did_change(cx);
17103 }
17104
17105 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17106 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17107 return;
17108 }
17109 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17110 self.display_map.update(cx, |display_map, cx| {
17111 display_map.fold_buffers([buffer_id], cx)
17112 });
17113 cx.emit(EditorEvent::BufferFoldToggled {
17114 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17115 folded: true,
17116 });
17117 cx.notify();
17118 }
17119
17120 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17121 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17122 return;
17123 }
17124 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17125 self.display_map.update(cx, |display_map, cx| {
17126 display_map.unfold_buffers([buffer_id], cx);
17127 });
17128 cx.emit(EditorEvent::BufferFoldToggled {
17129 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17130 folded: false,
17131 });
17132 cx.notify();
17133 }
17134
17135 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17136 self.display_map.read(cx).is_buffer_folded(buffer)
17137 }
17138
17139 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17140 self.display_map.read(cx).folded_buffers()
17141 }
17142
17143 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17144 self.display_map.update(cx, |display_map, cx| {
17145 display_map.disable_header_for_buffer(buffer_id, cx);
17146 });
17147 cx.notify();
17148 }
17149
17150 /// Removes any folds with the given ranges.
17151 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17152 &mut self,
17153 ranges: &[Range<T>],
17154 type_id: TypeId,
17155 auto_scroll: bool,
17156 cx: &mut Context<Self>,
17157 ) {
17158 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17159 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17160 });
17161 self.folds_did_change(cx);
17162 }
17163
17164 fn remove_folds_with<T: ToOffset + Clone>(
17165 &mut self,
17166 ranges: &[Range<T>],
17167 auto_scroll: bool,
17168 cx: &mut Context<Self>,
17169 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17170 ) {
17171 if ranges.is_empty() {
17172 return;
17173 }
17174
17175 let mut buffers_affected = HashSet::default();
17176 let multi_buffer = self.buffer().read(cx);
17177 for range in ranges {
17178 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17179 buffers_affected.insert(buffer.read(cx).remote_id());
17180 };
17181 }
17182
17183 self.display_map.update(cx, update);
17184
17185 if auto_scroll {
17186 self.request_autoscroll(Autoscroll::fit(), cx);
17187 }
17188
17189 cx.notify();
17190 self.scrollbar_marker_state.dirty = true;
17191 self.active_indent_guides_state.dirty = true;
17192 }
17193
17194 pub fn update_fold_widths(
17195 &mut self,
17196 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
17197 cx: &mut Context<Self>,
17198 ) -> bool {
17199 self.display_map
17200 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17201 }
17202
17203 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17204 self.display_map.read(cx).fold_placeholder.clone()
17205 }
17206
17207 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17208 self.buffer.update(cx, |buffer, cx| {
17209 buffer.set_all_diff_hunks_expanded(cx);
17210 });
17211 }
17212
17213 pub fn expand_all_diff_hunks(
17214 &mut self,
17215 _: &ExpandAllDiffHunks,
17216 _window: &mut Window,
17217 cx: &mut Context<Self>,
17218 ) {
17219 self.buffer.update(cx, |buffer, cx| {
17220 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17221 });
17222 }
17223
17224 pub fn toggle_selected_diff_hunks(
17225 &mut self,
17226 _: &ToggleSelectedDiffHunks,
17227 _window: &mut Window,
17228 cx: &mut Context<Self>,
17229 ) {
17230 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17231 self.toggle_diff_hunks_in_ranges(ranges, cx);
17232 }
17233
17234 pub fn diff_hunks_in_ranges<'a>(
17235 &'a self,
17236 ranges: &'a [Range<Anchor>],
17237 buffer: &'a MultiBufferSnapshot,
17238 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17239 ranges.iter().flat_map(move |range| {
17240 let end_excerpt_id = range.end.excerpt_id;
17241 let range = range.to_point(buffer);
17242 let mut peek_end = range.end;
17243 if range.end.row < buffer.max_row().0 {
17244 peek_end = Point::new(range.end.row + 1, 0);
17245 }
17246 buffer
17247 .diff_hunks_in_range(range.start..peek_end)
17248 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17249 })
17250 }
17251
17252 pub fn has_stageable_diff_hunks_in_ranges(
17253 &self,
17254 ranges: &[Range<Anchor>],
17255 snapshot: &MultiBufferSnapshot,
17256 ) -> bool {
17257 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17258 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17259 }
17260
17261 pub fn toggle_staged_selected_diff_hunks(
17262 &mut self,
17263 _: &::git::ToggleStaged,
17264 _: &mut Window,
17265 cx: &mut Context<Self>,
17266 ) {
17267 let snapshot = self.buffer.read(cx).snapshot(cx);
17268 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17269 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17270 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17271 }
17272
17273 pub fn set_render_diff_hunk_controls(
17274 &mut self,
17275 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17276 cx: &mut Context<Self>,
17277 ) {
17278 self.render_diff_hunk_controls = render_diff_hunk_controls;
17279 cx.notify();
17280 }
17281
17282 pub fn stage_and_next(
17283 &mut self,
17284 _: &::git::StageAndNext,
17285 window: &mut Window,
17286 cx: &mut Context<Self>,
17287 ) {
17288 self.do_stage_or_unstage_and_next(true, window, cx);
17289 }
17290
17291 pub fn unstage_and_next(
17292 &mut self,
17293 _: &::git::UnstageAndNext,
17294 window: &mut Window,
17295 cx: &mut Context<Self>,
17296 ) {
17297 self.do_stage_or_unstage_and_next(false, window, cx);
17298 }
17299
17300 pub fn stage_or_unstage_diff_hunks(
17301 &mut self,
17302 stage: bool,
17303 ranges: Vec<Range<Anchor>>,
17304 cx: &mut Context<Self>,
17305 ) {
17306 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17307 cx.spawn(async move |this, cx| {
17308 task.await?;
17309 this.update(cx, |this, cx| {
17310 let snapshot = this.buffer.read(cx).snapshot(cx);
17311 let chunk_by = this
17312 .diff_hunks_in_ranges(&ranges, &snapshot)
17313 .chunk_by(|hunk| hunk.buffer_id);
17314 for (buffer_id, hunks) in &chunk_by {
17315 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17316 }
17317 })
17318 })
17319 .detach_and_log_err(cx);
17320 }
17321
17322 fn save_buffers_for_ranges_if_needed(
17323 &mut self,
17324 ranges: &[Range<Anchor>],
17325 cx: &mut Context<Editor>,
17326 ) -> Task<Result<()>> {
17327 let multibuffer = self.buffer.read(cx);
17328 let snapshot = multibuffer.read(cx);
17329 let buffer_ids: HashSet<_> = ranges
17330 .iter()
17331 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17332 .collect();
17333 drop(snapshot);
17334
17335 let mut buffers = HashSet::default();
17336 for buffer_id in buffer_ids {
17337 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17338 let buffer = buffer_entity.read(cx);
17339 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17340 {
17341 buffers.insert(buffer_entity);
17342 }
17343 }
17344 }
17345
17346 if let Some(project) = &self.project {
17347 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17348 } else {
17349 Task::ready(Ok(()))
17350 }
17351 }
17352
17353 fn do_stage_or_unstage_and_next(
17354 &mut self,
17355 stage: bool,
17356 window: &mut Window,
17357 cx: &mut Context<Self>,
17358 ) {
17359 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17360
17361 if ranges.iter().any(|range| range.start != range.end) {
17362 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17363 return;
17364 }
17365
17366 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17367 let snapshot = self.snapshot(window, cx);
17368 let position = self.selections.newest::<Point>(cx).head();
17369 let mut row = snapshot
17370 .buffer_snapshot
17371 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17372 .find(|hunk| hunk.row_range.start.0 > position.row)
17373 .map(|hunk| hunk.row_range.start);
17374
17375 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17376 // Outside of the project diff editor, wrap around to the beginning.
17377 if !all_diff_hunks_expanded {
17378 row = row.or_else(|| {
17379 snapshot
17380 .buffer_snapshot
17381 .diff_hunks_in_range(Point::zero()..position)
17382 .find(|hunk| hunk.row_range.end.0 < position.row)
17383 .map(|hunk| hunk.row_range.start)
17384 });
17385 }
17386
17387 if let Some(row) = row {
17388 let destination = Point::new(row.0, 0);
17389 let autoscroll = Autoscroll::center();
17390
17391 self.unfold_ranges(&[destination..destination], false, false, cx);
17392 self.change_selections(Some(autoscroll), window, cx, |s| {
17393 s.select_ranges([destination..destination]);
17394 });
17395 }
17396 }
17397
17398 fn do_stage_or_unstage(
17399 &self,
17400 stage: bool,
17401 buffer_id: BufferId,
17402 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17403 cx: &mut App,
17404 ) -> Option<()> {
17405 let project = self.project.as_ref()?;
17406 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17407 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17408 let buffer_snapshot = buffer.read(cx).snapshot();
17409 let file_exists = buffer_snapshot
17410 .file()
17411 .is_some_and(|file| file.disk_state().exists());
17412 diff.update(cx, |diff, cx| {
17413 diff.stage_or_unstage_hunks(
17414 stage,
17415 &hunks
17416 .map(|hunk| buffer_diff::DiffHunk {
17417 buffer_range: hunk.buffer_range,
17418 diff_base_byte_range: hunk.diff_base_byte_range,
17419 secondary_status: hunk.secondary_status,
17420 range: Point::zero()..Point::zero(), // unused
17421 })
17422 .collect::<Vec<_>>(),
17423 &buffer_snapshot,
17424 file_exists,
17425 cx,
17426 )
17427 });
17428 None
17429 }
17430
17431 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17432 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17433 self.buffer
17434 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17435 }
17436
17437 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17438 self.buffer.update(cx, |buffer, cx| {
17439 let ranges = vec![Anchor::min()..Anchor::max()];
17440 if !buffer.all_diff_hunks_expanded()
17441 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17442 {
17443 buffer.collapse_diff_hunks(ranges, cx);
17444 true
17445 } else {
17446 false
17447 }
17448 })
17449 }
17450
17451 fn toggle_diff_hunks_in_ranges(
17452 &mut self,
17453 ranges: Vec<Range<Anchor>>,
17454 cx: &mut Context<Editor>,
17455 ) {
17456 self.buffer.update(cx, |buffer, cx| {
17457 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17458 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17459 })
17460 }
17461
17462 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17463 self.buffer.update(cx, |buffer, cx| {
17464 let snapshot = buffer.snapshot(cx);
17465 let excerpt_id = range.end.excerpt_id;
17466 let point_range = range.to_point(&snapshot);
17467 let expand = !buffer.single_hunk_is_expanded(range, cx);
17468 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17469 })
17470 }
17471
17472 pub(crate) fn apply_all_diff_hunks(
17473 &mut self,
17474 _: &ApplyAllDiffHunks,
17475 window: &mut Window,
17476 cx: &mut Context<Self>,
17477 ) {
17478 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17479
17480 let buffers = self.buffer.read(cx).all_buffers();
17481 for branch_buffer in buffers {
17482 branch_buffer.update(cx, |branch_buffer, cx| {
17483 branch_buffer.merge_into_base(Vec::new(), cx);
17484 });
17485 }
17486
17487 if let Some(project) = self.project.clone() {
17488 self.save(
17489 SaveOptions {
17490 format: true,
17491 autosave: false,
17492 },
17493 project,
17494 window,
17495 cx,
17496 )
17497 .detach_and_log_err(cx);
17498 }
17499 }
17500
17501 pub(crate) fn apply_selected_diff_hunks(
17502 &mut self,
17503 _: &ApplyDiffHunk,
17504 window: &mut Window,
17505 cx: &mut Context<Self>,
17506 ) {
17507 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17508 let snapshot = self.snapshot(window, cx);
17509 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17510 let mut ranges_by_buffer = HashMap::default();
17511 self.transact(window, cx, |editor, _window, cx| {
17512 for hunk in hunks {
17513 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17514 ranges_by_buffer
17515 .entry(buffer.clone())
17516 .or_insert_with(Vec::new)
17517 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17518 }
17519 }
17520
17521 for (buffer, ranges) in ranges_by_buffer {
17522 buffer.update(cx, |buffer, cx| {
17523 buffer.merge_into_base(ranges, cx);
17524 });
17525 }
17526 });
17527
17528 if let Some(project) = self.project.clone() {
17529 self.save(
17530 SaveOptions {
17531 format: true,
17532 autosave: false,
17533 },
17534 project,
17535 window,
17536 cx,
17537 )
17538 .detach_and_log_err(cx);
17539 }
17540 }
17541
17542 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17543 if hovered != self.gutter_hovered {
17544 self.gutter_hovered = hovered;
17545 cx.notify();
17546 }
17547 }
17548
17549 pub fn insert_blocks(
17550 &mut self,
17551 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17552 autoscroll: Option<Autoscroll>,
17553 cx: &mut Context<Self>,
17554 ) -> Vec<CustomBlockId> {
17555 let blocks = self
17556 .display_map
17557 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17558 if let Some(autoscroll) = autoscroll {
17559 self.request_autoscroll(autoscroll, cx);
17560 }
17561 cx.notify();
17562 blocks
17563 }
17564
17565 pub fn resize_blocks(
17566 &mut self,
17567 heights: HashMap<CustomBlockId, u32>,
17568 autoscroll: Option<Autoscroll>,
17569 cx: &mut Context<Self>,
17570 ) {
17571 self.display_map
17572 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17573 if let Some(autoscroll) = autoscroll {
17574 self.request_autoscroll(autoscroll, cx);
17575 }
17576 cx.notify();
17577 }
17578
17579 pub fn replace_blocks(
17580 &mut self,
17581 renderers: HashMap<CustomBlockId, RenderBlock>,
17582 autoscroll: Option<Autoscroll>,
17583 cx: &mut Context<Self>,
17584 ) {
17585 self.display_map
17586 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17587 if let Some(autoscroll) = autoscroll {
17588 self.request_autoscroll(autoscroll, cx);
17589 }
17590 cx.notify();
17591 }
17592
17593 pub fn remove_blocks(
17594 &mut self,
17595 block_ids: HashSet<CustomBlockId>,
17596 autoscroll: Option<Autoscroll>,
17597 cx: &mut Context<Self>,
17598 ) {
17599 self.display_map.update(cx, |display_map, cx| {
17600 display_map.remove_blocks(block_ids, cx)
17601 });
17602 if let Some(autoscroll) = autoscroll {
17603 self.request_autoscroll(autoscroll, cx);
17604 }
17605 cx.notify();
17606 }
17607
17608 pub fn row_for_block(
17609 &self,
17610 block_id: CustomBlockId,
17611 cx: &mut Context<Self>,
17612 ) -> Option<DisplayRow> {
17613 self.display_map
17614 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17615 }
17616
17617 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17618 self.focused_block = Some(focused_block);
17619 }
17620
17621 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17622 self.focused_block.take()
17623 }
17624
17625 pub fn insert_creases(
17626 &mut self,
17627 creases: impl IntoIterator<Item = Crease<Anchor>>,
17628 cx: &mut Context<Self>,
17629 ) -> Vec<CreaseId> {
17630 self.display_map
17631 .update(cx, |map, cx| map.insert_creases(creases, cx))
17632 }
17633
17634 pub fn remove_creases(
17635 &mut self,
17636 ids: impl IntoIterator<Item = CreaseId>,
17637 cx: &mut Context<Self>,
17638 ) -> Vec<(CreaseId, Range<Anchor>)> {
17639 self.display_map
17640 .update(cx, |map, cx| map.remove_creases(ids, cx))
17641 }
17642
17643 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17644 self.display_map
17645 .update(cx, |map, cx| map.snapshot(cx))
17646 .longest_row()
17647 }
17648
17649 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17650 self.display_map
17651 .update(cx, |map, cx| map.snapshot(cx))
17652 .max_point()
17653 }
17654
17655 pub fn text(&self, cx: &App) -> String {
17656 self.buffer.read(cx).read(cx).text()
17657 }
17658
17659 pub fn is_empty(&self, cx: &App) -> bool {
17660 self.buffer.read(cx).read(cx).is_empty()
17661 }
17662
17663 pub fn text_option(&self, cx: &App) -> Option<String> {
17664 let text = self.text(cx);
17665 let text = text.trim();
17666
17667 if text.is_empty() {
17668 return None;
17669 }
17670
17671 Some(text.to_string())
17672 }
17673
17674 pub fn set_text(
17675 &mut self,
17676 text: impl Into<Arc<str>>,
17677 window: &mut Window,
17678 cx: &mut Context<Self>,
17679 ) {
17680 self.transact(window, cx, |this, _, cx| {
17681 this.buffer
17682 .read(cx)
17683 .as_singleton()
17684 .expect("you can only call set_text on editors for singleton buffers")
17685 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17686 });
17687 }
17688
17689 pub fn display_text(&self, cx: &mut App) -> String {
17690 self.display_map
17691 .update(cx, |map, cx| map.snapshot(cx))
17692 .text()
17693 }
17694
17695 fn create_minimap(
17696 &self,
17697 minimap_settings: MinimapSettings,
17698 window: &mut Window,
17699 cx: &mut Context<Self>,
17700 ) -> Option<Entity<Self>> {
17701 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17702 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17703 }
17704
17705 fn initialize_new_minimap(
17706 &self,
17707 minimap_settings: MinimapSettings,
17708 window: &mut Window,
17709 cx: &mut Context<Self>,
17710 ) -> Entity<Self> {
17711 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17712
17713 let mut minimap = Editor::new_internal(
17714 EditorMode::Minimap {
17715 parent: cx.weak_entity(),
17716 },
17717 self.buffer.clone(),
17718 self.project.clone(),
17719 Some(self.display_map.clone()),
17720 window,
17721 cx,
17722 );
17723 minimap.scroll_manager.clone_state(&self.scroll_manager);
17724 minimap.set_text_style_refinement(TextStyleRefinement {
17725 font_size: Some(MINIMAP_FONT_SIZE),
17726 font_weight: Some(MINIMAP_FONT_WEIGHT),
17727 ..Default::default()
17728 });
17729 minimap.update_minimap_configuration(minimap_settings, cx);
17730 cx.new(|_| minimap)
17731 }
17732
17733 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17734 let current_line_highlight = minimap_settings
17735 .current_line_highlight
17736 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17737 self.set_current_line_highlight(Some(current_line_highlight));
17738 }
17739
17740 pub fn minimap(&self) -> Option<&Entity<Self>> {
17741 self.minimap
17742 .as_ref()
17743 .filter(|_| self.minimap_visibility.visible())
17744 }
17745
17746 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17747 let mut wrap_guides = smallvec![];
17748
17749 if self.show_wrap_guides == Some(false) {
17750 return wrap_guides;
17751 }
17752
17753 let settings = self.buffer.read(cx).language_settings(cx);
17754 if settings.show_wrap_guides {
17755 match self.soft_wrap_mode(cx) {
17756 SoftWrap::Column(soft_wrap) => {
17757 wrap_guides.push((soft_wrap as usize, true));
17758 }
17759 SoftWrap::Bounded(soft_wrap) => {
17760 wrap_guides.push((soft_wrap as usize, true));
17761 }
17762 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17763 }
17764 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17765 }
17766
17767 wrap_guides
17768 }
17769
17770 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17771 let settings = self.buffer.read(cx).language_settings(cx);
17772 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17773 match mode {
17774 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17775 SoftWrap::None
17776 }
17777 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17778 language_settings::SoftWrap::PreferredLineLength => {
17779 SoftWrap::Column(settings.preferred_line_length)
17780 }
17781 language_settings::SoftWrap::Bounded => {
17782 SoftWrap::Bounded(settings.preferred_line_length)
17783 }
17784 }
17785 }
17786
17787 pub fn set_soft_wrap_mode(
17788 &mut self,
17789 mode: language_settings::SoftWrap,
17790
17791 cx: &mut Context<Self>,
17792 ) {
17793 self.soft_wrap_mode_override = Some(mode);
17794 cx.notify();
17795 }
17796
17797 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17798 self.hard_wrap = hard_wrap;
17799 cx.notify();
17800 }
17801
17802 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17803 self.text_style_refinement = Some(style);
17804 }
17805
17806 /// called by the Element so we know what style we were most recently rendered with.
17807 pub(crate) fn set_style(
17808 &mut self,
17809 style: EditorStyle,
17810 window: &mut Window,
17811 cx: &mut Context<Self>,
17812 ) {
17813 // We intentionally do not inform the display map about the minimap style
17814 // so that wrapping is not recalculated and stays consistent for the editor
17815 // and its linked minimap.
17816 if !self.mode.is_minimap() {
17817 let rem_size = window.rem_size();
17818 self.display_map.update(cx, |map, cx| {
17819 map.set_font(
17820 style.text.font(),
17821 style.text.font_size.to_pixels(rem_size),
17822 cx,
17823 )
17824 });
17825 }
17826 self.style = Some(style);
17827 }
17828
17829 pub fn style(&self) -> Option<&EditorStyle> {
17830 self.style.as_ref()
17831 }
17832
17833 // Called by the element. This method is not designed to be called outside of the editor
17834 // element's layout code because it does not notify when rewrapping is computed synchronously.
17835 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17836 self.display_map
17837 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17838 }
17839
17840 pub fn set_soft_wrap(&mut self) {
17841 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17842 }
17843
17844 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17845 if self.soft_wrap_mode_override.is_some() {
17846 self.soft_wrap_mode_override.take();
17847 } else {
17848 let soft_wrap = match self.soft_wrap_mode(cx) {
17849 SoftWrap::GitDiff => return,
17850 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17851 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17852 language_settings::SoftWrap::None
17853 }
17854 };
17855 self.soft_wrap_mode_override = Some(soft_wrap);
17856 }
17857 cx.notify();
17858 }
17859
17860 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17861 let Some(workspace) = self.workspace() else {
17862 return;
17863 };
17864 let fs = workspace.read(cx).app_state().fs.clone();
17865 let current_show = TabBarSettings::get_global(cx).show;
17866 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17867 setting.show = Some(!current_show);
17868 });
17869 }
17870
17871 pub fn toggle_indent_guides(
17872 &mut self,
17873 _: &ToggleIndentGuides,
17874 _: &mut Window,
17875 cx: &mut Context<Self>,
17876 ) {
17877 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17878 self.buffer
17879 .read(cx)
17880 .language_settings(cx)
17881 .indent_guides
17882 .enabled
17883 });
17884 self.show_indent_guides = Some(!currently_enabled);
17885 cx.notify();
17886 }
17887
17888 fn should_show_indent_guides(&self) -> Option<bool> {
17889 self.show_indent_guides
17890 }
17891
17892 pub fn toggle_line_numbers(
17893 &mut self,
17894 _: &ToggleLineNumbers,
17895 _: &mut Window,
17896 cx: &mut Context<Self>,
17897 ) {
17898 let mut editor_settings = EditorSettings::get_global(cx).clone();
17899 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17900 EditorSettings::override_global(editor_settings, cx);
17901 }
17902
17903 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17904 if let Some(show_line_numbers) = self.show_line_numbers {
17905 return show_line_numbers;
17906 }
17907 EditorSettings::get_global(cx).gutter.line_numbers
17908 }
17909
17910 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17911 self.use_relative_line_numbers
17912 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17913 }
17914
17915 pub fn toggle_relative_line_numbers(
17916 &mut self,
17917 _: &ToggleRelativeLineNumbers,
17918 _: &mut Window,
17919 cx: &mut Context<Self>,
17920 ) {
17921 let is_relative = self.should_use_relative_line_numbers(cx);
17922 self.set_relative_line_number(Some(!is_relative), cx)
17923 }
17924
17925 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17926 self.use_relative_line_numbers = is_relative;
17927 cx.notify();
17928 }
17929
17930 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17931 self.show_gutter = show_gutter;
17932 cx.notify();
17933 }
17934
17935 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17936 self.show_scrollbars = ScrollbarAxes {
17937 horizontal: show,
17938 vertical: show,
17939 };
17940 cx.notify();
17941 }
17942
17943 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17944 self.show_scrollbars.vertical = show;
17945 cx.notify();
17946 }
17947
17948 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17949 self.show_scrollbars.horizontal = show;
17950 cx.notify();
17951 }
17952
17953 pub fn set_minimap_visibility(
17954 &mut self,
17955 minimap_visibility: MinimapVisibility,
17956 window: &mut Window,
17957 cx: &mut Context<Self>,
17958 ) {
17959 if self.minimap_visibility != minimap_visibility {
17960 if minimap_visibility.visible() && self.minimap.is_none() {
17961 let minimap_settings = EditorSettings::get_global(cx).minimap;
17962 self.minimap =
17963 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17964 }
17965 self.minimap_visibility = minimap_visibility;
17966 cx.notify();
17967 }
17968 }
17969
17970 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17971 self.set_show_scrollbars(false, cx);
17972 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17973 }
17974
17975 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17976 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17977 }
17978
17979 /// Normally the text in full mode and auto height editors is padded on the
17980 /// left side by roughly half a character width for improved hit testing.
17981 ///
17982 /// Use this method to disable this for cases where this is not wanted (e.g.
17983 /// if you want to align the editor text with some other text above or below)
17984 /// or if you want to add this padding to single-line editors.
17985 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17986 self.offset_content = offset_content;
17987 cx.notify();
17988 }
17989
17990 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17991 self.show_line_numbers = Some(show_line_numbers);
17992 cx.notify();
17993 }
17994
17995 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17996 self.disable_expand_excerpt_buttons = true;
17997 cx.notify();
17998 }
17999
18000 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18001 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18002 cx.notify();
18003 }
18004
18005 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18006 self.show_code_actions = Some(show_code_actions);
18007 cx.notify();
18008 }
18009
18010 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18011 self.show_runnables = Some(show_runnables);
18012 cx.notify();
18013 }
18014
18015 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18016 self.show_breakpoints = Some(show_breakpoints);
18017 cx.notify();
18018 }
18019
18020 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18021 if self.display_map.read(cx).masked != masked {
18022 self.display_map.update(cx, |map, _| map.masked = masked);
18023 }
18024 cx.notify()
18025 }
18026
18027 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18028 self.show_wrap_guides = Some(show_wrap_guides);
18029 cx.notify();
18030 }
18031
18032 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18033 self.show_indent_guides = Some(show_indent_guides);
18034 cx.notify();
18035 }
18036
18037 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18038 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18039 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
18040 if let Some(dir) = file.abs_path(cx).parent() {
18041 return Some(dir.to_owned());
18042 }
18043 }
18044
18045 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18046 return Some(project_path.path.to_path_buf());
18047 }
18048 }
18049
18050 None
18051 }
18052
18053 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18054 self.active_excerpt(cx)?
18055 .1
18056 .read(cx)
18057 .file()
18058 .and_then(|f| f.as_local())
18059 }
18060
18061 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18062 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18063 let buffer = buffer.read(cx);
18064 if let Some(project_path) = buffer.project_path(cx) {
18065 let project = self.project.as_ref()?.read(cx);
18066 project.absolute_path(&project_path, cx)
18067 } else {
18068 buffer
18069 .file()
18070 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18071 }
18072 })
18073 }
18074
18075 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18076 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18077 let project_path = buffer.read(cx).project_path(cx)?;
18078 let project = self.project.as_ref()?.read(cx);
18079 let entry = project.entry_for_path(&project_path, cx)?;
18080 let path = entry.path.to_path_buf();
18081 Some(path)
18082 })
18083 }
18084
18085 pub fn reveal_in_finder(
18086 &mut self,
18087 _: &RevealInFileManager,
18088 _window: &mut Window,
18089 cx: &mut Context<Self>,
18090 ) {
18091 if let Some(target) = self.target_file(cx) {
18092 cx.reveal_path(&target.abs_path(cx));
18093 }
18094 }
18095
18096 pub fn copy_path(
18097 &mut self,
18098 _: &zed_actions::workspace::CopyPath,
18099 _window: &mut Window,
18100 cx: &mut Context<Self>,
18101 ) {
18102 if let Some(path) = self.target_file_abs_path(cx) {
18103 if let Some(path) = path.to_str() {
18104 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18105 }
18106 }
18107 }
18108
18109 pub fn copy_relative_path(
18110 &mut self,
18111 _: &zed_actions::workspace::CopyRelativePath,
18112 _window: &mut Window,
18113 cx: &mut Context<Self>,
18114 ) {
18115 if let Some(path) = self.target_file_path(cx) {
18116 if let Some(path) = path.to_str() {
18117 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18118 }
18119 }
18120 }
18121
18122 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18123 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18124 buffer.read(cx).project_path(cx)
18125 } else {
18126 None
18127 }
18128 }
18129
18130 // Returns true if the editor handled a go-to-line request
18131 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18132 maybe!({
18133 let breakpoint_store = self.breakpoint_store.as_ref()?;
18134
18135 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18136 else {
18137 self.clear_row_highlights::<ActiveDebugLine>();
18138 return None;
18139 };
18140
18141 let position = active_stack_frame.position;
18142 let buffer_id = position.buffer_id?;
18143 let snapshot = self
18144 .project
18145 .as_ref()?
18146 .read(cx)
18147 .buffer_for_id(buffer_id, cx)?
18148 .read(cx)
18149 .snapshot();
18150
18151 let mut handled = false;
18152 for (id, ExcerptRange { context, .. }) in
18153 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18154 {
18155 if context.start.cmp(&position, &snapshot).is_ge()
18156 || context.end.cmp(&position, &snapshot).is_lt()
18157 {
18158 continue;
18159 }
18160 let snapshot = self.buffer.read(cx).snapshot(cx);
18161 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18162
18163 handled = true;
18164 self.clear_row_highlights::<ActiveDebugLine>();
18165
18166 self.go_to_line::<ActiveDebugLine>(
18167 multibuffer_anchor,
18168 Some(cx.theme().colors().editor_debugger_active_line_background),
18169 window,
18170 cx,
18171 );
18172
18173 cx.notify();
18174 }
18175
18176 handled.then_some(())
18177 })
18178 .is_some()
18179 }
18180
18181 pub fn copy_file_name_without_extension(
18182 &mut self,
18183 _: &CopyFileNameWithoutExtension,
18184 _: &mut Window,
18185 cx: &mut Context<Self>,
18186 ) {
18187 if let Some(file) = self.target_file(cx) {
18188 if let Some(file_stem) = file.path().file_stem() {
18189 if let Some(name) = file_stem.to_str() {
18190 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18191 }
18192 }
18193 }
18194 }
18195
18196 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18197 if let Some(file) = self.target_file(cx) {
18198 if let Some(file_name) = file.path().file_name() {
18199 if let Some(name) = file_name.to_str() {
18200 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18201 }
18202 }
18203 }
18204 }
18205
18206 pub fn toggle_git_blame(
18207 &mut self,
18208 _: &::git::Blame,
18209 window: &mut Window,
18210 cx: &mut Context<Self>,
18211 ) {
18212 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18213
18214 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18215 self.start_git_blame(true, window, cx);
18216 }
18217
18218 cx.notify();
18219 }
18220
18221 pub fn toggle_git_blame_inline(
18222 &mut self,
18223 _: &ToggleGitBlameInline,
18224 window: &mut Window,
18225 cx: &mut Context<Self>,
18226 ) {
18227 self.toggle_git_blame_inline_internal(true, window, cx);
18228 cx.notify();
18229 }
18230
18231 pub fn open_git_blame_commit(
18232 &mut self,
18233 _: &OpenGitBlameCommit,
18234 window: &mut Window,
18235 cx: &mut Context<Self>,
18236 ) {
18237 self.open_git_blame_commit_internal(window, cx);
18238 }
18239
18240 fn open_git_blame_commit_internal(
18241 &mut self,
18242 window: &mut Window,
18243 cx: &mut Context<Self>,
18244 ) -> Option<()> {
18245 let blame = self.blame.as_ref()?;
18246 let snapshot = self.snapshot(window, cx);
18247 let cursor = self.selections.newest::<Point>(cx).head();
18248 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18249 let blame_entry = blame
18250 .update(cx, |blame, cx| {
18251 blame
18252 .blame_for_rows(
18253 &[RowInfo {
18254 buffer_id: Some(buffer.remote_id()),
18255 buffer_row: Some(point.row),
18256 ..Default::default()
18257 }],
18258 cx,
18259 )
18260 .next()
18261 })
18262 .flatten()?;
18263 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18264 let repo = blame.read(cx).repository(cx)?;
18265 let workspace = self.workspace()?.downgrade();
18266 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18267 None
18268 }
18269
18270 pub fn git_blame_inline_enabled(&self) -> bool {
18271 self.git_blame_inline_enabled
18272 }
18273
18274 pub fn toggle_selection_menu(
18275 &mut self,
18276 _: &ToggleSelectionMenu,
18277 _: &mut Window,
18278 cx: &mut Context<Self>,
18279 ) {
18280 self.show_selection_menu = self
18281 .show_selection_menu
18282 .map(|show_selections_menu| !show_selections_menu)
18283 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18284
18285 cx.notify();
18286 }
18287
18288 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18289 self.show_selection_menu
18290 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18291 }
18292
18293 fn start_git_blame(
18294 &mut self,
18295 user_triggered: bool,
18296 window: &mut Window,
18297 cx: &mut Context<Self>,
18298 ) {
18299 if let Some(project) = self.project.as_ref() {
18300 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18301 return;
18302 };
18303
18304 if buffer.read(cx).file().is_none() {
18305 return;
18306 }
18307
18308 let focused = self.focus_handle(cx).contains_focused(window, cx);
18309
18310 let project = project.clone();
18311 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18312 self.blame_subscription =
18313 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18314 self.blame = Some(blame);
18315 }
18316 }
18317
18318 fn toggle_git_blame_inline_internal(
18319 &mut self,
18320 user_triggered: bool,
18321 window: &mut Window,
18322 cx: &mut Context<Self>,
18323 ) {
18324 if self.git_blame_inline_enabled {
18325 self.git_blame_inline_enabled = false;
18326 self.show_git_blame_inline = false;
18327 self.show_git_blame_inline_delay_task.take();
18328 } else {
18329 self.git_blame_inline_enabled = true;
18330 self.start_git_blame_inline(user_triggered, window, cx);
18331 }
18332
18333 cx.notify();
18334 }
18335
18336 fn start_git_blame_inline(
18337 &mut self,
18338 user_triggered: bool,
18339 window: &mut Window,
18340 cx: &mut Context<Self>,
18341 ) {
18342 self.start_git_blame(user_triggered, window, cx);
18343
18344 if ProjectSettings::get_global(cx)
18345 .git
18346 .inline_blame_delay()
18347 .is_some()
18348 {
18349 self.start_inline_blame_timer(window, cx);
18350 } else {
18351 self.show_git_blame_inline = true
18352 }
18353 }
18354
18355 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18356 self.blame.as_ref()
18357 }
18358
18359 pub fn show_git_blame_gutter(&self) -> bool {
18360 self.show_git_blame_gutter
18361 }
18362
18363 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18364 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18365 }
18366
18367 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18368 self.show_git_blame_inline
18369 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18370 && !self.newest_selection_head_on_empty_line(cx)
18371 && self.has_blame_entries(cx)
18372 }
18373
18374 fn has_blame_entries(&self, cx: &App) -> bool {
18375 self.blame()
18376 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18377 }
18378
18379 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18380 let cursor_anchor = self.selections.newest_anchor().head();
18381
18382 let snapshot = self.buffer.read(cx).snapshot(cx);
18383 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18384
18385 snapshot.line_len(buffer_row) == 0
18386 }
18387
18388 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18389 let buffer_and_selection = maybe!({
18390 let selection = self.selections.newest::<Point>(cx);
18391 let selection_range = selection.range();
18392
18393 let multi_buffer = self.buffer().read(cx);
18394 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18395 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18396
18397 let (buffer, range, _) = if selection.reversed {
18398 buffer_ranges.first()
18399 } else {
18400 buffer_ranges.last()
18401 }?;
18402
18403 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18404 ..text::ToPoint::to_point(&range.end, &buffer).row;
18405 Some((
18406 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18407 selection,
18408 ))
18409 });
18410
18411 let Some((buffer, selection)) = buffer_and_selection else {
18412 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18413 };
18414
18415 let Some(project) = self.project.as_ref() else {
18416 return Task::ready(Err(anyhow!("editor does not have project")));
18417 };
18418
18419 project.update(cx, |project, cx| {
18420 project.get_permalink_to_line(&buffer, selection, cx)
18421 })
18422 }
18423
18424 pub fn copy_permalink_to_line(
18425 &mut self,
18426 _: &CopyPermalinkToLine,
18427 window: &mut Window,
18428 cx: &mut Context<Self>,
18429 ) {
18430 let permalink_task = self.get_permalink_to_line(cx);
18431 let workspace = self.workspace();
18432
18433 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18434 Ok(permalink) => {
18435 cx.update(|_, cx| {
18436 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18437 })
18438 .ok();
18439 }
18440 Err(err) => {
18441 let message = format!("Failed to copy permalink: {err}");
18442
18443 anyhow::Result::<()>::Err(err).log_err();
18444
18445 if let Some(workspace) = workspace {
18446 workspace
18447 .update_in(cx, |workspace, _, cx| {
18448 struct CopyPermalinkToLine;
18449
18450 workspace.show_toast(
18451 Toast::new(
18452 NotificationId::unique::<CopyPermalinkToLine>(),
18453 message,
18454 ),
18455 cx,
18456 )
18457 })
18458 .ok();
18459 }
18460 }
18461 })
18462 .detach();
18463 }
18464
18465 pub fn copy_file_location(
18466 &mut self,
18467 _: &CopyFileLocation,
18468 _: &mut Window,
18469 cx: &mut Context<Self>,
18470 ) {
18471 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18472 if let Some(file) = self.target_file(cx) {
18473 if let Some(path) = file.path().to_str() {
18474 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18475 }
18476 }
18477 }
18478
18479 pub fn open_permalink_to_line(
18480 &mut self,
18481 _: &OpenPermalinkToLine,
18482 window: &mut Window,
18483 cx: &mut Context<Self>,
18484 ) {
18485 let permalink_task = self.get_permalink_to_line(cx);
18486 let workspace = self.workspace();
18487
18488 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18489 Ok(permalink) => {
18490 cx.update(|_, cx| {
18491 cx.open_url(permalink.as_ref());
18492 })
18493 .ok();
18494 }
18495 Err(err) => {
18496 let message = format!("Failed to open permalink: {err}");
18497
18498 anyhow::Result::<()>::Err(err).log_err();
18499
18500 if let Some(workspace) = workspace {
18501 workspace
18502 .update(cx, |workspace, cx| {
18503 struct OpenPermalinkToLine;
18504
18505 workspace.show_toast(
18506 Toast::new(
18507 NotificationId::unique::<OpenPermalinkToLine>(),
18508 message,
18509 ),
18510 cx,
18511 )
18512 })
18513 .ok();
18514 }
18515 }
18516 })
18517 .detach();
18518 }
18519
18520 pub fn insert_uuid_v4(
18521 &mut self,
18522 _: &InsertUuidV4,
18523 window: &mut Window,
18524 cx: &mut Context<Self>,
18525 ) {
18526 self.insert_uuid(UuidVersion::V4, window, cx);
18527 }
18528
18529 pub fn insert_uuid_v7(
18530 &mut self,
18531 _: &InsertUuidV7,
18532 window: &mut Window,
18533 cx: &mut Context<Self>,
18534 ) {
18535 self.insert_uuid(UuidVersion::V7, window, cx);
18536 }
18537
18538 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18539 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18540 self.transact(window, cx, |this, window, cx| {
18541 let edits = this
18542 .selections
18543 .all::<Point>(cx)
18544 .into_iter()
18545 .map(|selection| {
18546 let uuid = match version {
18547 UuidVersion::V4 => uuid::Uuid::new_v4(),
18548 UuidVersion::V7 => uuid::Uuid::now_v7(),
18549 };
18550
18551 (selection.range(), uuid.to_string())
18552 });
18553 this.edit(edits, cx);
18554 this.refresh_inline_completion(true, false, window, cx);
18555 });
18556 }
18557
18558 pub fn open_selections_in_multibuffer(
18559 &mut self,
18560 _: &OpenSelectionsInMultibuffer,
18561 window: &mut Window,
18562 cx: &mut Context<Self>,
18563 ) {
18564 let multibuffer = self.buffer.read(cx);
18565
18566 let Some(buffer) = multibuffer.as_singleton() else {
18567 return;
18568 };
18569
18570 let Some(workspace) = self.workspace() else {
18571 return;
18572 };
18573
18574 let title = multibuffer.title(cx).to_string();
18575
18576 let locations = self
18577 .selections
18578 .all_anchors(cx)
18579 .into_iter()
18580 .map(|selection| Location {
18581 buffer: buffer.clone(),
18582 range: selection.start.text_anchor..selection.end.text_anchor,
18583 })
18584 .collect::<Vec<_>>();
18585
18586 cx.spawn_in(window, async move |_, cx| {
18587 workspace.update_in(cx, |workspace, window, cx| {
18588 Self::open_locations_in_multibuffer(
18589 workspace,
18590 locations,
18591 format!("Selections for '{title}'"),
18592 false,
18593 MultibufferSelectionMode::All,
18594 window,
18595 cx,
18596 );
18597 })
18598 })
18599 .detach();
18600 }
18601
18602 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18603 /// last highlight added will be used.
18604 ///
18605 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18606 pub fn highlight_rows<T: 'static>(
18607 &mut self,
18608 range: Range<Anchor>,
18609 color: Hsla,
18610 options: RowHighlightOptions,
18611 cx: &mut Context<Self>,
18612 ) {
18613 let snapshot = self.buffer().read(cx).snapshot(cx);
18614 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18615 let ix = row_highlights.binary_search_by(|highlight| {
18616 Ordering::Equal
18617 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18618 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18619 });
18620
18621 if let Err(mut ix) = ix {
18622 let index = post_inc(&mut self.highlight_order);
18623
18624 // If this range intersects with the preceding highlight, then merge it with
18625 // the preceding highlight. Otherwise insert a new highlight.
18626 let mut merged = false;
18627 if ix > 0 {
18628 let prev_highlight = &mut row_highlights[ix - 1];
18629 if prev_highlight
18630 .range
18631 .end
18632 .cmp(&range.start, &snapshot)
18633 .is_ge()
18634 {
18635 ix -= 1;
18636 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18637 prev_highlight.range.end = range.end;
18638 }
18639 merged = true;
18640 prev_highlight.index = index;
18641 prev_highlight.color = color;
18642 prev_highlight.options = options;
18643 }
18644 }
18645
18646 if !merged {
18647 row_highlights.insert(
18648 ix,
18649 RowHighlight {
18650 range: range.clone(),
18651 index,
18652 color,
18653 options,
18654 type_id: TypeId::of::<T>(),
18655 },
18656 );
18657 }
18658
18659 // If any of the following highlights intersect with this one, merge them.
18660 while let Some(next_highlight) = row_highlights.get(ix + 1) {
18661 let highlight = &row_highlights[ix];
18662 if next_highlight
18663 .range
18664 .start
18665 .cmp(&highlight.range.end, &snapshot)
18666 .is_le()
18667 {
18668 if next_highlight
18669 .range
18670 .end
18671 .cmp(&highlight.range.end, &snapshot)
18672 .is_gt()
18673 {
18674 row_highlights[ix].range.end = next_highlight.range.end;
18675 }
18676 row_highlights.remove(ix + 1);
18677 } else {
18678 break;
18679 }
18680 }
18681 }
18682 }
18683
18684 /// Remove any highlighted row ranges of the given type that intersect the
18685 /// given ranges.
18686 pub fn remove_highlighted_rows<T: 'static>(
18687 &mut self,
18688 ranges_to_remove: Vec<Range<Anchor>>,
18689 cx: &mut Context<Self>,
18690 ) {
18691 let snapshot = self.buffer().read(cx).snapshot(cx);
18692 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18693 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18694 row_highlights.retain(|highlight| {
18695 while let Some(range_to_remove) = ranges_to_remove.peek() {
18696 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18697 Ordering::Less | Ordering::Equal => {
18698 ranges_to_remove.next();
18699 }
18700 Ordering::Greater => {
18701 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18702 Ordering::Less | Ordering::Equal => {
18703 return false;
18704 }
18705 Ordering::Greater => break,
18706 }
18707 }
18708 }
18709 }
18710
18711 true
18712 })
18713 }
18714
18715 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18716 pub fn clear_row_highlights<T: 'static>(&mut self) {
18717 self.highlighted_rows.remove(&TypeId::of::<T>());
18718 }
18719
18720 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18721 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18722 self.highlighted_rows
18723 .get(&TypeId::of::<T>())
18724 .map_or(&[] as &[_], |vec| vec.as_slice())
18725 .iter()
18726 .map(|highlight| (highlight.range.clone(), highlight.color))
18727 }
18728
18729 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18730 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18731 /// Allows to ignore certain kinds of highlights.
18732 pub fn highlighted_display_rows(
18733 &self,
18734 window: &mut Window,
18735 cx: &mut App,
18736 ) -> BTreeMap<DisplayRow, LineHighlight> {
18737 let snapshot = self.snapshot(window, cx);
18738 let mut used_highlight_orders = HashMap::default();
18739 self.highlighted_rows
18740 .iter()
18741 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18742 .fold(
18743 BTreeMap::<DisplayRow, LineHighlight>::new(),
18744 |mut unique_rows, highlight| {
18745 let start = highlight.range.start.to_display_point(&snapshot);
18746 let end = highlight.range.end.to_display_point(&snapshot);
18747 let start_row = start.row().0;
18748 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18749 && end.column() == 0
18750 {
18751 end.row().0.saturating_sub(1)
18752 } else {
18753 end.row().0
18754 };
18755 for row in start_row..=end_row {
18756 let used_index =
18757 used_highlight_orders.entry(row).or_insert(highlight.index);
18758 if highlight.index >= *used_index {
18759 *used_index = highlight.index;
18760 unique_rows.insert(
18761 DisplayRow(row),
18762 LineHighlight {
18763 include_gutter: highlight.options.include_gutter,
18764 border: None,
18765 background: highlight.color.into(),
18766 type_id: Some(highlight.type_id),
18767 },
18768 );
18769 }
18770 }
18771 unique_rows
18772 },
18773 )
18774 }
18775
18776 pub fn highlighted_display_row_for_autoscroll(
18777 &self,
18778 snapshot: &DisplaySnapshot,
18779 ) -> Option<DisplayRow> {
18780 self.highlighted_rows
18781 .values()
18782 .flat_map(|highlighted_rows| highlighted_rows.iter())
18783 .filter_map(|highlight| {
18784 if highlight.options.autoscroll {
18785 Some(highlight.range.start.to_display_point(snapshot).row())
18786 } else {
18787 None
18788 }
18789 })
18790 .min()
18791 }
18792
18793 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18794 self.highlight_background::<SearchWithinRange>(
18795 ranges,
18796 |colors| colors.colors().editor_document_highlight_read_background,
18797 cx,
18798 )
18799 }
18800
18801 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18802 self.breadcrumb_header = Some(new_header);
18803 }
18804
18805 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18806 self.clear_background_highlights::<SearchWithinRange>(cx);
18807 }
18808
18809 pub fn highlight_background<T: 'static>(
18810 &mut self,
18811 ranges: &[Range<Anchor>],
18812 color_fetcher: fn(&Theme) -> Hsla,
18813 cx: &mut Context<Self>,
18814 ) {
18815 self.background_highlights.insert(
18816 HighlightKey::Type(TypeId::of::<T>()),
18817 (color_fetcher, Arc::from(ranges)),
18818 );
18819 self.scrollbar_marker_state.dirty = true;
18820 cx.notify();
18821 }
18822
18823 pub fn highlight_background_key<T: 'static>(
18824 &mut self,
18825 key: usize,
18826 ranges: &[Range<Anchor>],
18827 color_fetcher: fn(&Theme) -> Hsla,
18828 cx: &mut Context<Self>,
18829 ) {
18830 self.background_highlights.insert(
18831 HighlightKey::TypePlus(TypeId::of::<T>(), key),
18832 (color_fetcher, Arc::from(ranges)),
18833 );
18834 self.scrollbar_marker_state.dirty = true;
18835 cx.notify();
18836 }
18837
18838 pub fn clear_background_highlights<T: 'static>(
18839 &mut self,
18840 cx: &mut Context<Self>,
18841 ) -> Option<BackgroundHighlight> {
18842 let text_highlights = self
18843 .background_highlights
18844 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
18845 if !text_highlights.1.is_empty() {
18846 self.scrollbar_marker_state.dirty = true;
18847 cx.notify();
18848 }
18849 Some(text_highlights)
18850 }
18851
18852 pub fn highlight_gutter<T: 'static>(
18853 &mut self,
18854 ranges: impl Into<Vec<Range<Anchor>>>,
18855 color_fetcher: fn(&App) -> Hsla,
18856 cx: &mut Context<Self>,
18857 ) {
18858 self.gutter_highlights
18859 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
18860 cx.notify();
18861 }
18862
18863 pub fn clear_gutter_highlights<T: 'static>(
18864 &mut self,
18865 cx: &mut Context<Self>,
18866 ) -> Option<GutterHighlight> {
18867 cx.notify();
18868 self.gutter_highlights.remove(&TypeId::of::<T>())
18869 }
18870
18871 pub fn insert_gutter_highlight<T: 'static>(
18872 &mut self,
18873 range: Range<Anchor>,
18874 color_fetcher: fn(&App) -> Hsla,
18875 cx: &mut Context<Self>,
18876 ) {
18877 let snapshot = self.buffer().read(cx).snapshot(cx);
18878 let mut highlights = self
18879 .gutter_highlights
18880 .remove(&TypeId::of::<T>())
18881 .map(|(_, highlights)| highlights)
18882 .unwrap_or_default();
18883 let ix = highlights.binary_search_by(|highlight| {
18884 Ordering::Equal
18885 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
18886 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
18887 });
18888 if let Err(ix) = ix {
18889 highlights.insert(ix, range);
18890 }
18891 self.gutter_highlights
18892 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
18893 }
18894
18895 pub fn remove_gutter_highlights<T: 'static>(
18896 &mut self,
18897 ranges_to_remove: Vec<Range<Anchor>>,
18898 cx: &mut Context<Self>,
18899 ) {
18900 let snapshot = self.buffer().read(cx).snapshot(cx);
18901 let Some((color_fetcher, mut gutter_highlights)) =
18902 self.gutter_highlights.remove(&TypeId::of::<T>())
18903 else {
18904 return;
18905 };
18906 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18907 gutter_highlights.retain(|highlight| {
18908 while let Some(range_to_remove) = ranges_to_remove.peek() {
18909 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
18910 Ordering::Less | Ordering::Equal => {
18911 ranges_to_remove.next();
18912 }
18913 Ordering::Greater => {
18914 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
18915 Ordering::Less | Ordering::Equal => {
18916 return false;
18917 }
18918 Ordering::Greater => break,
18919 }
18920 }
18921 }
18922 }
18923
18924 true
18925 });
18926 self.gutter_highlights
18927 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
18928 }
18929
18930 #[cfg(feature = "test-support")]
18931 pub fn all_text_highlights(
18932 &self,
18933 window: &mut Window,
18934 cx: &mut Context<Self>,
18935 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
18936 let snapshot = self.snapshot(window, cx);
18937 self.display_map.update(cx, |display_map, _| {
18938 display_map
18939 .all_text_highlights()
18940 .map(|highlight| {
18941 let (style, ranges) = highlight.as_ref();
18942 (
18943 *style,
18944 ranges
18945 .iter()
18946 .map(|range| range.clone().to_display_points(&snapshot))
18947 .collect(),
18948 )
18949 })
18950 .collect()
18951 })
18952 }
18953
18954 #[cfg(feature = "test-support")]
18955 pub fn all_text_background_highlights(
18956 &self,
18957 window: &mut Window,
18958 cx: &mut Context<Self>,
18959 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18960 let snapshot = self.snapshot(window, cx);
18961 let buffer = &snapshot.buffer_snapshot;
18962 let start = buffer.anchor_before(0);
18963 let end = buffer.anchor_after(buffer.len());
18964 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
18965 }
18966
18967 #[cfg(feature = "test-support")]
18968 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18969 let snapshot = self.buffer().read(cx).snapshot(cx);
18970
18971 let highlights = self
18972 .background_highlights
18973 .get(&HighlightKey::Type(TypeId::of::<
18974 items::BufferSearchHighlights,
18975 >()));
18976
18977 if let Some((_color, ranges)) = highlights {
18978 ranges
18979 .iter()
18980 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
18981 .collect_vec()
18982 } else {
18983 vec![]
18984 }
18985 }
18986
18987 fn document_highlights_for_position<'a>(
18988 &'a self,
18989 position: Anchor,
18990 buffer: &'a MultiBufferSnapshot,
18991 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
18992 let read_highlights = self
18993 .background_highlights
18994 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
18995 .map(|h| &h.1);
18996 let write_highlights = self
18997 .background_highlights
18998 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
18999 .map(|h| &h.1);
19000 let left_position = position.bias_left(buffer);
19001 let right_position = position.bias_right(buffer);
19002 read_highlights
19003 .into_iter()
19004 .chain(write_highlights)
19005 .flat_map(move |ranges| {
19006 let start_ix = match ranges.binary_search_by(|probe| {
19007 let cmp = probe.end.cmp(&left_position, buffer);
19008 if cmp.is_ge() {
19009 Ordering::Greater
19010 } else {
19011 Ordering::Less
19012 }
19013 }) {
19014 Ok(i) | Err(i) => i,
19015 };
19016
19017 ranges[start_ix..]
19018 .iter()
19019 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19020 })
19021 }
19022
19023 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19024 self.background_highlights
19025 .get(&HighlightKey::Type(TypeId::of::<T>()))
19026 .map_or(false, |(_, highlights)| !highlights.is_empty())
19027 }
19028
19029 pub fn background_highlights_in_range(
19030 &self,
19031 search_range: Range<Anchor>,
19032 display_snapshot: &DisplaySnapshot,
19033 theme: &Theme,
19034 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19035 let mut results = Vec::new();
19036 for (color_fetcher, ranges) in self.background_highlights.values() {
19037 let color = color_fetcher(theme);
19038 let start_ix = match ranges.binary_search_by(|probe| {
19039 let cmp = probe
19040 .end
19041 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19042 if cmp.is_gt() {
19043 Ordering::Greater
19044 } else {
19045 Ordering::Less
19046 }
19047 }) {
19048 Ok(i) | Err(i) => i,
19049 };
19050 for range in &ranges[start_ix..] {
19051 if range
19052 .start
19053 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19054 .is_ge()
19055 {
19056 break;
19057 }
19058
19059 let start = range.start.to_display_point(display_snapshot);
19060 let end = range.end.to_display_point(display_snapshot);
19061 results.push((start..end, color))
19062 }
19063 }
19064 results
19065 }
19066
19067 pub fn background_highlight_row_ranges<T: 'static>(
19068 &self,
19069 search_range: Range<Anchor>,
19070 display_snapshot: &DisplaySnapshot,
19071 count: usize,
19072 ) -> Vec<RangeInclusive<DisplayPoint>> {
19073 let mut results = Vec::new();
19074 let Some((_, ranges)) = self
19075 .background_highlights
19076 .get(&HighlightKey::Type(TypeId::of::<T>()))
19077 else {
19078 return vec![];
19079 };
19080
19081 let start_ix = match ranges.binary_search_by(|probe| {
19082 let cmp = probe
19083 .end
19084 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19085 if cmp.is_gt() {
19086 Ordering::Greater
19087 } else {
19088 Ordering::Less
19089 }
19090 }) {
19091 Ok(i) | Err(i) => i,
19092 };
19093 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19094 if let (Some(start_display), Some(end_display)) = (start, end) {
19095 results.push(
19096 start_display.to_display_point(display_snapshot)
19097 ..=end_display.to_display_point(display_snapshot),
19098 );
19099 }
19100 };
19101 let mut start_row: Option<Point> = None;
19102 let mut end_row: Option<Point> = None;
19103 if ranges.len() > count {
19104 return Vec::new();
19105 }
19106 for range in &ranges[start_ix..] {
19107 if range
19108 .start
19109 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19110 .is_ge()
19111 {
19112 break;
19113 }
19114 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19115 if let Some(current_row) = &end_row {
19116 if end.row == current_row.row {
19117 continue;
19118 }
19119 }
19120 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19121 if start_row.is_none() {
19122 assert_eq!(end_row, None);
19123 start_row = Some(start);
19124 end_row = Some(end);
19125 continue;
19126 }
19127 if let Some(current_end) = end_row.as_mut() {
19128 if start.row > current_end.row + 1 {
19129 push_region(start_row, end_row);
19130 start_row = Some(start);
19131 end_row = Some(end);
19132 } else {
19133 // Merge two hunks.
19134 *current_end = end;
19135 }
19136 } else {
19137 unreachable!();
19138 }
19139 }
19140 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19141 push_region(start_row, end_row);
19142 results
19143 }
19144
19145 pub fn gutter_highlights_in_range(
19146 &self,
19147 search_range: Range<Anchor>,
19148 display_snapshot: &DisplaySnapshot,
19149 cx: &App,
19150 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19151 let mut results = Vec::new();
19152 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19153 let color = color_fetcher(cx);
19154 let start_ix = match ranges.binary_search_by(|probe| {
19155 let cmp = probe
19156 .end
19157 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19158 if cmp.is_gt() {
19159 Ordering::Greater
19160 } else {
19161 Ordering::Less
19162 }
19163 }) {
19164 Ok(i) | Err(i) => i,
19165 };
19166 for range in &ranges[start_ix..] {
19167 if range
19168 .start
19169 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19170 .is_ge()
19171 {
19172 break;
19173 }
19174
19175 let start = range.start.to_display_point(display_snapshot);
19176 let end = range.end.to_display_point(display_snapshot);
19177 results.push((start..end, color))
19178 }
19179 }
19180 results
19181 }
19182
19183 /// Get the text ranges corresponding to the redaction query
19184 pub fn redacted_ranges(
19185 &self,
19186 search_range: Range<Anchor>,
19187 display_snapshot: &DisplaySnapshot,
19188 cx: &App,
19189 ) -> Vec<Range<DisplayPoint>> {
19190 display_snapshot
19191 .buffer_snapshot
19192 .redacted_ranges(search_range, |file| {
19193 if let Some(file) = file {
19194 file.is_private()
19195 && EditorSettings::get(
19196 Some(SettingsLocation {
19197 worktree_id: file.worktree_id(cx),
19198 path: file.path().as_ref(),
19199 }),
19200 cx,
19201 )
19202 .redact_private_values
19203 } else {
19204 false
19205 }
19206 })
19207 .map(|range| {
19208 range.start.to_display_point(display_snapshot)
19209 ..range.end.to_display_point(display_snapshot)
19210 })
19211 .collect()
19212 }
19213
19214 pub fn highlight_text_key<T: 'static>(
19215 &mut self,
19216 key: usize,
19217 ranges: Vec<Range<Anchor>>,
19218 style: HighlightStyle,
19219 cx: &mut Context<Self>,
19220 ) {
19221 self.display_map.update(cx, |map, _| {
19222 map.highlight_text(
19223 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19224 ranges,
19225 style,
19226 );
19227 });
19228 cx.notify();
19229 }
19230
19231 pub fn highlight_text<T: 'static>(
19232 &mut self,
19233 ranges: Vec<Range<Anchor>>,
19234 style: HighlightStyle,
19235 cx: &mut Context<Self>,
19236 ) {
19237 self.display_map.update(cx, |map, _| {
19238 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19239 });
19240 cx.notify();
19241 }
19242
19243 pub(crate) fn highlight_inlays<T: 'static>(
19244 &mut self,
19245 highlights: Vec<InlayHighlight>,
19246 style: HighlightStyle,
19247 cx: &mut Context<Self>,
19248 ) {
19249 self.display_map.update(cx, |map, _| {
19250 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19251 });
19252 cx.notify();
19253 }
19254
19255 pub fn text_highlights<'a, T: 'static>(
19256 &'a self,
19257 cx: &'a App,
19258 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19259 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19260 }
19261
19262 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19263 let cleared = self
19264 .display_map
19265 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19266 if cleared {
19267 cx.notify();
19268 }
19269 }
19270
19271 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19272 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19273 && self.focus_handle.is_focused(window)
19274 }
19275
19276 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19277 self.show_cursor_when_unfocused = is_enabled;
19278 cx.notify();
19279 }
19280
19281 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19282 cx.notify();
19283 }
19284
19285 fn on_debug_session_event(
19286 &mut self,
19287 _session: Entity<Session>,
19288 event: &SessionEvent,
19289 cx: &mut Context<Self>,
19290 ) {
19291 match event {
19292 SessionEvent::InvalidateInlineValue => {
19293 self.refresh_inline_values(cx);
19294 }
19295 _ => {}
19296 }
19297 }
19298
19299 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19300 let Some(project) = self.project.clone() else {
19301 return;
19302 };
19303
19304 if !self.inline_value_cache.enabled {
19305 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19306 self.splice_inlays(&inlays, Vec::new(), cx);
19307 return;
19308 }
19309
19310 let current_execution_position = self
19311 .highlighted_rows
19312 .get(&TypeId::of::<ActiveDebugLine>())
19313 .and_then(|lines| lines.last().map(|line| line.range.end));
19314
19315 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19316 let inline_values = editor
19317 .update(cx, |editor, cx| {
19318 let Some(current_execution_position) = current_execution_position else {
19319 return Some(Task::ready(Ok(Vec::new())));
19320 };
19321
19322 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19323 let snapshot = buffer.snapshot(cx);
19324
19325 let excerpt = snapshot.excerpt_containing(
19326 current_execution_position..current_execution_position,
19327 )?;
19328
19329 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19330 })?;
19331
19332 let range =
19333 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19334
19335 project.inline_values(buffer, range, cx)
19336 })
19337 .ok()
19338 .flatten()?
19339 .await
19340 .context("refreshing debugger inlays")
19341 .log_err()?;
19342
19343 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19344
19345 for (buffer_id, inline_value) in inline_values
19346 .into_iter()
19347 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19348 {
19349 buffer_inline_values
19350 .entry(buffer_id)
19351 .or_default()
19352 .push(inline_value);
19353 }
19354
19355 editor
19356 .update(cx, |editor, cx| {
19357 let snapshot = editor.buffer.read(cx).snapshot(cx);
19358 let mut new_inlays = Vec::default();
19359
19360 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19361 let buffer_id = buffer_snapshot.remote_id();
19362 buffer_inline_values
19363 .get(&buffer_id)
19364 .into_iter()
19365 .flatten()
19366 .for_each(|hint| {
19367 let inlay = Inlay::debugger(
19368 post_inc(&mut editor.next_inlay_id),
19369 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19370 hint.text(),
19371 );
19372
19373 new_inlays.push(inlay);
19374 });
19375 }
19376
19377 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19378 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19379
19380 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19381 })
19382 .ok()?;
19383 Some(())
19384 });
19385 }
19386
19387 fn on_buffer_event(
19388 &mut self,
19389 multibuffer: &Entity<MultiBuffer>,
19390 event: &multi_buffer::Event,
19391 window: &mut Window,
19392 cx: &mut Context<Self>,
19393 ) {
19394 match event {
19395 multi_buffer::Event::Edited {
19396 singleton_buffer_edited,
19397 edited_buffer,
19398 } => {
19399 self.scrollbar_marker_state.dirty = true;
19400 self.active_indent_guides_state.dirty = true;
19401 self.refresh_active_diagnostics(cx);
19402 self.refresh_code_actions(window, cx);
19403 self.refresh_selected_text_highlights(true, window, cx);
19404 refresh_matching_bracket_highlights(self, window, cx);
19405 if self.has_active_inline_completion() {
19406 self.update_visible_inline_completion(window, cx);
19407 }
19408 if let Some(project) = self.project.as_ref() {
19409 if let Some(edited_buffer) = edited_buffer {
19410 project.update(cx, |project, cx| {
19411 self.registered_buffers
19412 .entry(edited_buffer.read(cx).remote_id())
19413 .or_insert_with(|| {
19414 project
19415 .register_buffer_with_language_servers(&edited_buffer, cx)
19416 });
19417 });
19418 }
19419 }
19420 cx.emit(EditorEvent::BufferEdited);
19421 cx.emit(SearchEvent::MatchesInvalidated);
19422
19423 if let Some(buffer) = edited_buffer {
19424 self.update_lsp_data(None, Some(buffer.read(cx).remote_id()), window, cx);
19425 }
19426
19427 if *singleton_buffer_edited {
19428 if let Some(buffer) = edited_buffer {
19429 if buffer.read(cx).file().is_none() {
19430 cx.emit(EditorEvent::TitleChanged);
19431 }
19432 }
19433 if let Some(project) = &self.project {
19434 #[allow(clippy::mutable_key_type)]
19435 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19436 multibuffer
19437 .all_buffers()
19438 .into_iter()
19439 .filter_map(|buffer| {
19440 buffer.update(cx, |buffer, cx| {
19441 let language = buffer.language()?;
19442 let should_discard = project.update(cx, |project, cx| {
19443 project.is_local()
19444 && !project.has_language_servers_for(buffer, cx)
19445 });
19446 should_discard.not().then_some(language.clone())
19447 })
19448 })
19449 .collect::<HashSet<_>>()
19450 });
19451 if !languages_affected.is_empty() {
19452 self.refresh_inlay_hints(
19453 InlayHintRefreshReason::BufferEdited(languages_affected),
19454 cx,
19455 );
19456 }
19457 }
19458 }
19459
19460 let Some(project) = &self.project else { return };
19461 let (telemetry, is_via_ssh) = {
19462 let project = project.read(cx);
19463 let telemetry = project.client().telemetry().clone();
19464 let is_via_ssh = project.is_via_ssh();
19465 (telemetry, is_via_ssh)
19466 };
19467 refresh_linked_ranges(self, window, cx);
19468 telemetry.log_edit_event("editor", is_via_ssh);
19469 }
19470 multi_buffer::Event::ExcerptsAdded {
19471 buffer,
19472 predecessor,
19473 excerpts,
19474 } => {
19475 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19476 let buffer_id = buffer.read(cx).remote_id();
19477 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19478 if let Some(project) = &self.project {
19479 update_uncommitted_diff_for_buffer(
19480 cx.entity(),
19481 project,
19482 [buffer.clone()],
19483 self.buffer.clone(),
19484 cx,
19485 )
19486 .detach();
19487 }
19488 }
19489 self.update_lsp_data(None, Some(buffer_id), window, cx);
19490 cx.emit(EditorEvent::ExcerptsAdded {
19491 buffer: buffer.clone(),
19492 predecessor: *predecessor,
19493 excerpts: excerpts.clone(),
19494 });
19495 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19496 }
19497 multi_buffer::Event::ExcerptsRemoved {
19498 ids,
19499 removed_buffer_ids,
19500 } => {
19501 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19502 let buffer = self.buffer.read(cx);
19503 self.registered_buffers
19504 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19505 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19506 cx.emit(EditorEvent::ExcerptsRemoved {
19507 ids: ids.clone(),
19508 removed_buffer_ids: removed_buffer_ids.clone(),
19509 });
19510 }
19511 multi_buffer::Event::ExcerptsEdited {
19512 excerpt_ids,
19513 buffer_ids,
19514 } => {
19515 self.display_map.update(cx, |map, cx| {
19516 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19517 });
19518 cx.emit(EditorEvent::ExcerptsEdited {
19519 ids: excerpt_ids.clone(),
19520 });
19521 }
19522 multi_buffer::Event::ExcerptsExpanded { ids } => {
19523 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19524 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19525 }
19526 multi_buffer::Event::Reparsed(buffer_id) => {
19527 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19528 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19529
19530 cx.emit(EditorEvent::Reparsed(*buffer_id));
19531 }
19532 multi_buffer::Event::DiffHunksToggled => {
19533 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19534 }
19535 multi_buffer::Event::LanguageChanged(buffer_id) => {
19536 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19537 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19538 cx.emit(EditorEvent::Reparsed(*buffer_id));
19539 cx.notify();
19540 }
19541 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19542 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19543 multi_buffer::Event::FileHandleChanged
19544 | multi_buffer::Event::Reloaded
19545 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19546 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19547 multi_buffer::Event::DiagnosticsUpdated => {
19548 self.update_diagnostics_state(window, cx);
19549 }
19550 _ => {}
19551 };
19552 }
19553
19554 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19555 if !self.diagnostics_enabled() {
19556 return;
19557 }
19558 self.refresh_active_diagnostics(cx);
19559 self.refresh_inline_diagnostics(true, window, cx);
19560 self.scrollbar_marker_state.dirty = true;
19561 cx.notify();
19562 }
19563
19564 pub fn start_temporary_diff_override(&mut self) {
19565 self.load_diff_task.take();
19566 self.temporary_diff_override = true;
19567 }
19568
19569 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19570 self.temporary_diff_override = false;
19571 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19572 self.buffer.update(cx, |buffer, cx| {
19573 buffer.set_all_diff_hunks_collapsed(cx);
19574 });
19575
19576 if let Some(project) = self.project.clone() {
19577 self.load_diff_task = Some(
19578 update_uncommitted_diff_for_buffer(
19579 cx.entity(),
19580 &project,
19581 self.buffer.read(cx).all_buffers(),
19582 self.buffer.clone(),
19583 cx,
19584 )
19585 .shared(),
19586 );
19587 }
19588 }
19589
19590 fn on_display_map_changed(
19591 &mut self,
19592 _: Entity<DisplayMap>,
19593 _: &mut Window,
19594 cx: &mut Context<Self>,
19595 ) {
19596 cx.notify();
19597 }
19598
19599 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19600 let new_severity = if self.diagnostics_enabled() {
19601 EditorSettings::get_global(cx)
19602 .diagnostics_max_severity
19603 .unwrap_or(DiagnosticSeverity::Hint)
19604 } else {
19605 DiagnosticSeverity::Off
19606 };
19607 self.set_max_diagnostics_severity(new_severity, cx);
19608 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19609 self.update_edit_prediction_settings(cx);
19610 self.refresh_inline_completion(true, false, window, cx);
19611 self.refresh_inlay_hints(
19612 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19613 self.selections.newest_anchor().head(),
19614 &self.buffer.read(cx).snapshot(cx),
19615 cx,
19616 )),
19617 cx,
19618 );
19619
19620 let old_cursor_shape = self.cursor_shape;
19621
19622 {
19623 let editor_settings = EditorSettings::get_global(cx);
19624 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19625 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19626 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19627 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19628 self.drag_and_drop_selection_enabled = editor_settings.drag_and_drop_selection;
19629 }
19630
19631 if old_cursor_shape != self.cursor_shape {
19632 cx.emit(EditorEvent::CursorShapeChanged);
19633 }
19634
19635 let project_settings = ProjectSettings::get_global(cx);
19636 self.serialize_dirty_buffers =
19637 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19638
19639 if self.mode.is_full() {
19640 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19641 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19642 if self.show_inline_diagnostics != show_inline_diagnostics {
19643 self.show_inline_diagnostics = show_inline_diagnostics;
19644 self.refresh_inline_diagnostics(false, window, cx);
19645 }
19646
19647 if self.git_blame_inline_enabled != inline_blame_enabled {
19648 self.toggle_git_blame_inline_internal(false, window, cx);
19649 }
19650
19651 let minimap_settings = EditorSettings::get_global(cx).minimap;
19652 if self.minimap_visibility != MinimapVisibility::Disabled {
19653 if self.minimap_visibility.settings_visibility()
19654 != minimap_settings.minimap_enabled()
19655 {
19656 self.set_minimap_visibility(
19657 MinimapVisibility::for_mode(self.mode(), cx),
19658 window,
19659 cx,
19660 );
19661 } else if let Some(minimap_entity) = self.minimap.as_ref() {
19662 minimap_entity.update(cx, |minimap_editor, cx| {
19663 minimap_editor.update_minimap_configuration(minimap_settings, cx)
19664 })
19665 }
19666 }
19667 }
19668
19669 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
19670 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
19671 }) {
19672 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
19673 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
19674 }
19675 self.refresh_colors(None, None, window, cx);
19676 }
19677
19678 cx.notify();
19679 }
19680
19681 pub fn set_searchable(&mut self, searchable: bool) {
19682 self.searchable = searchable;
19683 }
19684
19685 pub fn searchable(&self) -> bool {
19686 self.searchable
19687 }
19688
19689 fn open_proposed_changes_editor(
19690 &mut self,
19691 _: &OpenProposedChangesEditor,
19692 window: &mut Window,
19693 cx: &mut Context<Self>,
19694 ) {
19695 let Some(workspace) = self.workspace() else {
19696 cx.propagate();
19697 return;
19698 };
19699
19700 let selections = self.selections.all::<usize>(cx);
19701 let multi_buffer = self.buffer.read(cx);
19702 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19703 let mut new_selections_by_buffer = HashMap::default();
19704 for selection in selections {
19705 for (buffer, range, _) in
19706 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
19707 {
19708 let mut range = range.to_point(buffer);
19709 range.start.column = 0;
19710 range.end.column = buffer.line_len(range.end.row);
19711 new_selections_by_buffer
19712 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
19713 .or_insert(Vec::new())
19714 .push(range)
19715 }
19716 }
19717
19718 let proposed_changes_buffers = new_selections_by_buffer
19719 .into_iter()
19720 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
19721 .collect::<Vec<_>>();
19722 let proposed_changes_editor = cx.new(|cx| {
19723 ProposedChangesEditor::new(
19724 "Proposed changes",
19725 proposed_changes_buffers,
19726 self.project.clone(),
19727 window,
19728 cx,
19729 )
19730 });
19731
19732 window.defer(cx, move |window, cx| {
19733 workspace.update(cx, |workspace, cx| {
19734 workspace.active_pane().update(cx, |pane, cx| {
19735 pane.add_item(
19736 Box::new(proposed_changes_editor),
19737 true,
19738 true,
19739 None,
19740 window,
19741 cx,
19742 );
19743 });
19744 });
19745 });
19746 }
19747
19748 pub fn open_excerpts_in_split(
19749 &mut self,
19750 _: &OpenExcerptsSplit,
19751 window: &mut Window,
19752 cx: &mut Context<Self>,
19753 ) {
19754 self.open_excerpts_common(None, true, window, cx)
19755 }
19756
19757 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
19758 self.open_excerpts_common(None, false, window, cx)
19759 }
19760
19761 fn open_excerpts_common(
19762 &mut self,
19763 jump_data: Option<JumpData>,
19764 split: bool,
19765 window: &mut Window,
19766 cx: &mut Context<Self>,
19767 ) {
19768 let Some(workspace) = self.workspace() else {
19769 cx.propagate();
19770 return;
19771 };
19772
19773 if self.buffer.read(cx).is_singleton() {
19774 cx.propagate();
19775 return;
19776 }
19777
19778 let mut new_selections_by_buffer = HashMap::default();
19779 match &jump_data {
19780 Some(JumpData::MultiBufferPoint {
19781 excerpt_id,
19782 position,
19783 anchor,
19784 line_offset_from_top,
19785 }) => {
19786 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19787 if let Some(buffer) = multi_buffer_snapshot
19788 .buffer_id_for_excerpt(*excerpt_id)
19789 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
19790 {
19791 let buffer_snapshot = buffer.read(cx).snapshot();
19792 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
19793 language::ToPoint::to_point(anchor, &buffer_snapshot)
19794 } else {
19795 buffer_snapshot.clip_point(*position, Bias::Left)
19796 };
19797 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
19798 new_selections_by_buffer.insert(
19799 buffer,
19800 (
19801 vec![jump_to_offset..jump_to_offset],
19802 Some(*line_offset_from_top),
19803 ),
19804 );
19805 }
19806 }
19807 Some(JumpData::MultiBufferRow {
19808 row,
19809 line_offset_from_top,
19810 }) => {
19811 let point = MultiBufferPoint::new(row.0, 0);
19812 if let Some((buffer, buffer_point, _)) =
19813 self.buffer.read(cx).point_to_buffer_point(point, cx)
19814 {
19815 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
19816 new_selections_by_buffer
19817 .entry(buffer)
19818 .or_insert((Vec::new(), Some(*line_offset_from_top)))
19819 .0
19820 .push(buffer_offset..buffer_offset)
19821 }
19822 }
19823 None => {
19824 let selections = self.selections.all::<usize>(cx);
19825 let multi_buffer = self.buffer.read(cx);
19826 for selection in selections {
19827 for (snapshot, range, _, anchor) in multi_buffer
19828 .snapshot(cx)
19829 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
19830 {
19831 if let Some(anchor) = anchor {
19832 // selection is in a deleted hunk
19833 let Some(buffer_id) = anchor.buffer_id else {
19834 continue;
19835 };
19836 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
19837 continue;
19838 };
19839 let offset = text::ToOffset::to_offset(
19840 &anchor.text_anchor,
19841 &buffer_handle.read(cx).snapshot(),
19842 );
19843 let range = offset..offset;
19844 new_selections_by_buffer
19845 .entry(buffer_handle)
19846 .or_insert((Vec::new(), None))
19847 .0
19848 .push(range)
19849 } else {
19850 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
19851 else {
19852 continue;
19853 };
19854 new_selections_by_buffer
19855 .entry(buffer_handle)
19856 .or_insert((Vec::new(), None))
19857 .0
19858 .push(range)
19859 }
19860 }
19861 }
19862 }
19863 }
19864
19865 new_selections_by_buffer
19866 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
19867
19868 if new_selections_by_buffer.is_empty() {
19869 return;
19870 }
19871
19872 // We defer the pane interaction because we ourselves are a workspace item
19873 // and activating a new item causes the pane to call a method on us reentrantly,
19874 // which panics if we're on the stack.
19875 window.defer(cx, move |window, cx| {
19876 workspace.update(cx, |workspace, cx| {
19877 let pane = if split {
19878 workspace.adjacent_pane(window, cx)
19879 } else {
19880 workspace.active_pane().clone()
19881 };
19882
19883 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
19884 let editor = buffer
19885 .read(cx)
19886 .file()
19887 .is_none()
19888 .then(|| {
19889 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
19890 // so `workspace.open_project_item` will never find them, always opening a new editor.
19891 // Instead, we try to activate the existing editor in the pane first.
19892 let (editor, pane_item_index) =
19893 pane.read(cx).items().enumerate().find_map(|(i, item)| {
19894 let editor = item.downcast::<Editor>()?;
19895 let singleton_buffer =
19896 editor.read(cx).buffer().read(cx).as_singleton()?;
19897 if singleton_buffer == buffer {
19898 Some((editor, i))
19899 } else {
19900 None
19901 }
19902 })?;
19903 pane.update(cx, |pane, cx| {
19904 pane.activate_item(pane_item_index, true, true, window, cx)
19905 });
19906 Some(editor)
19907 })
19908 .flatten()
19909 .unwrap_or_else(|| {
19910 workspace.open_project_item::<Self>(
19911 pane.clone(),
19912 buffer,
19913 true,
19914 true,
19915 window,
19916 cx,
19917 )
19918 });
19919
19920 editor.update(cx, |editor, cx| {
19921 let autoscroll = match scroll_offset {
19922 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
19923 None => Autoscroll::newest(),
19924 };
19925 let nav_history = editor.nav_history.take();
19926 editor.change_selections(Some(autoscroll), window, cx, |s| {
19927 s.select_ranges(ranges);
19928 });
19929 editor.nav_history = nav_history;
19930 });
19931 }
19932 })
19933 });
19934 }
19935
19936 // For now, don't allow opening excerpts in buffers that aren't backed by
19937 // regular project files.
19938 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
19939 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
19940 }
19941
19942 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
19943 let snapshot = self.buffer.read(cx).read(cx);
19944 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
19945 Some(
19946 ranges
19947 .iter()
19948 .map(move |range| {
19949 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19950 })
19951 .collect(),
19952 )
19953 }
19954
19955 fn selection_replacement_ranges(
19956 &self,
19957 range: Range<OffsetUtf16>,
19958 cx: &mut App,
19959 ) -> Vec<Range<OffsetUtf16>> {
19960 let selections = self.selections.all::<OffsetUtf16>(cx);
19961 let newest_selection = selections
19962 .iter()
19963 .max_by_key(|selection| selection.id)
19964 .unwrap();
19965 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19966 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19967 let snapshot = self.buffer.read(cx).read(cx);
19968 selections
19969 .into_iter()
19970 .map(|mut selection| {
19971 selection.start.0 =
19972 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19973 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19974 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19975 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19976 })
19977 .collect()
19978 }
19979
19980 fn report_editor_event(
19981 &self,
19982 event_type: &'static str,
19983 file_extension: Option<String>,
19984 cx: &App,
19985 ) {
19986 if cfg!(any(test, feature = "test-support")) {
19987 return;
19988 }
19989
19990 let Some(project) = &self.project else { return };
19991
19992 // If None, we are in a file without an extension
19993 let file = self
19994 .buffer
19995 .read(cx)
19996 .as_singleton()
19997 .and_then(|b| b.read(cx).file());
19998 let file_extension = file_extension.or(file
19999 .as_ref()
20000 .and_then(|file| Path::new(file.file_name(cx)).extension())
20001 .and_then(|e| e.to_str())
20002 .map(|a| a.to_string()));
20003
20004 let vim_mode = vim_enabled(cx);
20005
20006 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20007 let copilot_enabled = edit_predictions_provider
20008 == language::language_settings::EditPredictionProvider::Copilot;
20009 let copilot_enabled_for_language = self
20010 .buffer
20011 .read(cx)
20012 .language_settings(cx)
20013 .show_edit_predictions;
20014
20015 let project = project.read(cx);
20016 telemetry::event!(
20017 event_type,
20018 file_extension,
20019 vim_mode,
20020 copilot_enabled,
20021 copilot_enabled_for_language,
20022 edit_predictions_provider,
20023 is_via_ssh = project.is_via_ssh(),
20024 );
20025 }
20026
20027 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20028 /// with each line being an array of {text, highlight} objects.
20029 fn copy_highlight_json(
20030 &mut self,
20031 _: &CopyHighlightJson,
20032 window: &mut Window,
20033 cx: &mut Context<Self>,
20034 ) {
20035 #[derive(Serialize)]
20036 struct Chunk<'a> {
20037 text: String,
20038 highlight: Option<&'a str>,
20039 }
20040
20041 let snapshot = self.buffer.read(cx).snapshot(cx);
20042 let range = self
20043 .selected_text_range(false, window, cx)
20044 .and_then(|selection| {
20045 if selection.range.is_empty() {
20046 None
20047 } else {
20048 Some(selection.range)
20049 }
20050 })
20051 .unwrap_or_else(|| 0..snapshot.len());
20052
20053 let chunks = snapshot.chunks(range, true);
20054 let mut lines = Vec::new();
20055 let mut line: VecDeque<Chunk> = VecDeque::new();
20056
20057 let Some(style) = self.style.as_ref() else {
20058 return;
20059 };
20060
20061 for chunk in chunks {
20062 let highlight = chunk
20063 .syntax_highlight_id
20064 .and_then(|id| id.name(&style.syntax));
20065 let mut chunk_lines = chunk.text.split('\n').peekable();
20066 while let Some(text) = chunk_lines.next() {
20067 let mut merged_with_last_token = false;
20068 if let Some(last_token) = line.back_mut() {
20069 if last_token.highlight == highlight {
20070 last_token.text.push_str(text);
20071 merged_with_last_token = true;
20072 }
20073 }
20074
20075 if !merged_with_last_token {
20076 line.push_back(Chunk {
20077 text: text.into(),
20078 highlight,
20079 });
20080 }
20081
20082 if chunk_lines.peek().is_some() {
20083 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20084 line.pop_front();
20085 }
20086 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20087 line.pop_back();
20088 }
20089
20090 lines.push(mem::take(&mut line));
20091 }
20092 }
20093 }
20094
20095 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20096 return;
20097 };
20098 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20099 }
20100
20101 pub fn open_context_menu(
20102 &mut self,
20103 _: &OpenContextMenu,
20104 window: &mut Window,
20105 cx: &mut Context<Self>,
20106 ) {
20107 self.request_autoscroll(Autoscroll::newest(), cx);
20108 let position = self.selections.newest_display(cx).start;
20109 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20110 }
20111
20112 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20113 &self.inlay_hint_cache
20114 }
20115
20116 pub fn replay_insert_event(
20117 &mut self,
20118 text: &str,
20119 relative_utf16_range: Option<Range<isize>>,
20120 window: &mut Window,
20121 cx: &mut Context<Self>,
20122 ) {
20123 if !self.input_enabled {
20124 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20125 return;
20126 }
20127 if let Some(relative_utf16_range) = relative_utf16_range {
20128 let selections = self.selections.all::<OffsetUtf16>(cx);
20129 self.change_selections(None, window, cx, |s| {
20130 let new_ranges = selections.into_iter().map(|range| {
20131 let start = OffsetUtf16(
20132 range
20133 .head()
20134 .0
20135 .saturating_add_signed(relative_utf16_range.start),
20136 );
20137 let end = OffsetUtf16(
20138 range
20139 .head()
20140 .0
20141 .saturating_add_signed(relative_utf16_range.end),
20142 );
20143 start..end
20144 });
20145 s.select_ranges(new_ranges);
20146 });
20147 }
20148
20149 self.handle_input(text, window, cx);
20150 }
20151
20152 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20153 let Some(provider) = self.semantics_provider.as_ref() else {
20154 return false;
20155 };
20156
20157 let mut supports = false;
20158 self.buffer().update(cx, |this, cx| {
20159 this.for_each_buffer(|buffer| {
20160 supports |= provider.supports_inlay_hints(buffer, cx);
20161 });
20162 });
20163
20164 supports
20165 }
20166
20167 pub fn is_focused(&self, window: &Window) -> bool {
20168 self.focus_handle.is_focused(window)
20169 }
20170
20171 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20172 cx.emit(EditorEvent::Focused);
20173
20174 if let Some(descendant) = self
20175 .last_focused_descendant
20176 .take()
20177 .and_then(|descendant| descendant.upgrade())
20178 {
20179 window.focus(&descendant);
20180 } else {
20181 if let Some(blame) = self.blame.as_ref() {
20182 blame.update(cx, GitBlame::focus)
20183 }
20184
20185 self.blink_manager.update(cx, BlinkManager::enable);
20186 self.show_cursor_names(window, cx);
20187 self.buffer.update(cx, |buffer, cx| {
20188 buffer.finalize_last_transaction(cx);
20189 if self.leader_id.is_none() {
20190 buffer.set_active_selections(
20191 &self.selections.disjoint_anchors(),
20192 self.selections.line_mode,
20193 self.cursor_shape,
20194 cx,
20195 );
20196 }
20197 });
20198 }
20199 }
20200
20201 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20202 cx.emit(EditorEvent::FocusedIn)
20203 }
20204
20205 fn handle_focus_out(
20206 &mut self,
20207 event: FocusOutEvent,
20208 _window: &mut Window,
20209 cx: &mut Context<Self>,
20210 ) {
20211 if event.blurred != self.focus_handle {
20212 self.last_focused_descendant = Some(event.blurred);
20213 }
20214 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20215 }
20216
20217 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20218 self.blink_manager.update(cx, BlinkManager::disable);
20219 self.buffer
20220 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20221
20222 if let Some(blame) = self.blame.as_ref() {
20223 blame.update(cx, GitBlame::blur)
20224 }
20225 if !self.hover_state.focused(window, cx) {
20226 hide_hover(self, cx);
20227 }
20228 if !self
20229 .context_menu
20230 .borrow()
20231 .as_ref()
20232 .is_some_and(|context_menu| context_menu.focused(window, cx))
20233 {
20234 self.hide_context_menu(window, cx);
20235 }
20236 self.discard_inline_completion(false, cx);
20237 cx.emit(EditorEvent::Blurred);
20238 cx.notify();
20239 }
20240
20241 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20242 let mut pending: String = window
20243 .pending_input_keystrokes()
20244 .into_iter()
20245 .flatten()
20246 .filter_map(|keystroke| {
20247 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20248 keystroke.key_char.clone()
20249 } else {
20250 None
20251 }
20252 })
20253 .collect();
20254
20255 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20256 pending = "".to_string();
20257 }
20258
20259 let existing_pending = self
20260 .text_highlights::<PendingInput>(cx)
20261 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20262 if existing_pending.is_none() && pending.is_empty() {
20263 return;
20264 }
20265 let transaction =
20266 self.transact(window, cx, |this, window, cx| {
20267 let selections = this.selections.all::<usize>(cx);
20268 let edits = selections
20269 .iter()
20270 .map(|selection| (selection.end..selection.end, pending.clone()));
20271 this.edit(edits, cx);
20272 this.change_selections(None, window, cx, |s| {
20273 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20274 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20275 }));
20276 });
20277 if let Some(existing_ranges) = existing_pending {
20278 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20279 this.edit(edits, cx);
20280 }
20281 });
20282
20283 let snapshot = self.snapshot(window, cx);
20284 let ranges = self
20285 .selections
20286 .all::<usize>(cx)
20287 .into_iter()
20288 .map(|selection| {
20289 snapshot.buffer_snapshot.anchor_after(selection.end)
20290 ..snapshot
20291 .buffer_snapshot
20292 .anchor_before(selection.end + pending.len())
20293 })
20294 .collect();
20295
20296 if pending.is_empty() {
20297 self.clear_highlights::<PendingInput>(cx);
20298 } else {
20299 self.highlight_text::<PendingInput>(
20300 ranges,
20301 HighlightStyle {
20302 underline: Some(UnderlineStyle {
20303 thickness: px(1.),
20304 color: None,
20305 wavy: false,
20306 }),
20307 ..Default::default()
20308 },
20309 cx,
20310 );
20311 }
20312
20313 self.ime_transaction = self.ime_transaction.or(transaction);
20314 if let Some(transaction) = self.ime_transaction {
20315 self.buffer.update(cx, |buffer, cx| {
20316 buffer.group_until_transaction(transaction, cx);
20317 });
20318 }
20319
20320 if self.text_highlights::<PendingInput>(cx).is_none() {
20321 self.ime_transaction.take();
20322 }
20323 }
20324
20325 pub fn register_action_renderer(
20326 &mut self,
20327 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20328 ) -> Subscription {
20329 let id = self.next_editor_action_id.post_inc();
20330 self.editor_actions
20331 .borrow_mut()
20332 .insert(id, Box::new(listener));
20333
20334 let editor_actions = self.editor_actions.clone();
20335 Subscription::new(move || {
20336 editor_actions.borrow_mut().remove(&id);
20337 })
20338 }
20339
20340 pub fn register_action<A: Action>(
20341 &mut self,
20342 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20343 ) -> Subscription {
20344 let id = self.next_editor_action_id.post_inc();
20345 let listener = Arc::new(listener);
20346 self.editor_actions.borrow_mut().insert(
20347 id,
20348 Box::new(move |_, window, _| {
20349 let listener = listener.clone();
20350 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20351 let action = action.downcast_ref().unwrap();
20352 if phase == DispatchPhase::Bubble {
20353 listener(action, window, cx)
20354 }
20355 })
20356 }),
20357 );
20358
20359 let editor_actions = self.editor_actions.clone();
20360 Subscription::new(move || {
20361 editor_actions.borrow_mut().remove(&id);
20362 })
20363 }
20364
20365 pub fn file_header_size(&self) -> u32 {
20366 FILE_HEADER_HEIGHT
20367 }
20368
20369 pub fn restore(
20370 &mut self,
20371 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20372 window: &mut Window,
20373 cx: &mut Context<Self>,
20374 ) {
20375 let workspace = self.workspace();
20376 let project = self.project.as_ref();
20377 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20378 let mut tasks = Vec::new();
20379 for (buffer_id, changes) in revert_changes {
20380 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20381 buffer.update(cx, |buffer, cx| {
20382 buffer.edit(
20383 changes
20384 .into_iter()
20385 .map(|(range, text)| (range, text.to_string())),
20386 None,
20387 cx,
20388 );
20389 });
20390
20391 if let Some(project) =
20392 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20393 {
20394 project.update(cx, |project, cx| {
20395 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20396 })
20397 }
20398 }
20399 }
20400 tasks
20401 });
20402 cx.spawn_in(window, async move |_, cx| {
20403 for (buffer, task) in save_tasks {
20404 let result = task.await;
20405 if result.is_err() {
20406 let Some(path) = buffer
20407 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20408 .ok()
20409 else {
20410 continue;
20411 };
20412 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20413 let Some(task) = cx
20414 .update_window_entity(&workspace, |workspace, window, cx| {
20415 workspace
20416 .open_path_preview(path, None, false, false, false, window, cx)
20417 })
20418 .ok()
20419 else {
20420 continue;
20421 };
20422 task.await.log_err();
20423 }
20424 }
20425 }
20426 })
20427 .detach();
20428 self.change_selections(None, window, cx, |selections| selections.refresh());
20429 }
20430
20431 pub fn to_pixel_point(
20432 &self,
20433 source: multi_buffer::Anchor,
20434 editor_snapshot: &EditorSnapshot,
20435 window: &mut Window,
20436 ) -> Option<gpui::Point<Pixels>> {
20437 let source_point = source.to_display_point(editor_snapshot);
20438 self.display_to_pixel_point(source_point, editor_snapshot, window)
20439 }
20440
20441 pub fn display_to_pixel_point(
20442 &self,
20443 source: DisplayPoint,
20444 editor_snapshot: &EditorSnapshot,
20445 window: &mut Window,
20446 ) -> Option<gpui::Point<Pixels>> {
20447 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20448 let text_layout_details = self.text_layout_details(window);
20449 let scroll_top = text_layout_details
20450 .scroll_anchor
20451 .scroll_position(editor_snapshot)
20452 .y;
20453
20454 if source.row().as_f32() < scroll_top.floor() {
20455 return None;
20456 }
20457 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20458 let source_y = line_height * (source.row().as_f32() - scroll_top);
20459 Some(gpui::Point::new(source_x, source_y))
20460 }
20461
20462 pub fn has_visible_completions_menu(&self) -> bool {
20463 !self.edit_prediction_preview_is_active()
20464 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20465 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20466 })
20467 }
20468
20469 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20470 if self.mode.is_minimap() {
20471 return;
20472 }
20473 self.addons
20474 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20475 }
20476
20477 pub fn unregister_addon<T: Addon>(&mut self) {
20478 self.addons.remove(&std::any::TypeId::of::<T>());
20479 }
20480
20481 pub fn addon<T: Addon>(&self) -> Option<&T> {
20482 let type_id = std::any::TypeId::of::<T>();
20483 self.addons
20484 .get(&type_id)
20485 .and_then(|item| item.to_any().downcast_ref::<T>())
20486 }
20487
20488 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20489 let type_id = std::any::TypeId::of::<T>();
20490 self.addons
20491 .get_mut(&type_id)
20492 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20493 }
20494
20495 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
20496 let text_layout_details = self.text_layout_details(window);
20497 let style = &text_layout_details.editor_style;
20498 let font_id = window.text_system().resolve_font(&style.text.font());
20499 let font_size = style.text.font_size.to_pixels(window.rem_size());
20500 let line_height = style.text.line_height_in_pixels(window.rem_size());
20501 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20502
20503 gpui::Size::new(em_width, line_height)
20504 }
20505
20506 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20507 self.load_diff_task.clone()
20508 }
20509
20510 fn read_metadata_from_db(
20511 &mut self,
20512 item_id: u64,
20513 workspace_id: WorkspaceId,
20514 window: &mut Window,
20515 cx: &mut Context<Editor>,
20516 ) {
20517 if self.is_singleton(cx)
20518 && !self.mode.is_minimap()
20519 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20520 {
20521 let buffer_snapshot = OnceCell::new();
20522
20523 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20524 if !folds.is_empty() {
20525 let snapshot =
20526 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20527 self.fold_ranges(
20528 folds
20529 .into_iter()
20530 .map(|(start, end)| {
20531 snapshot.clip_offset(start, Bias::Left)
20532 ..snapshot.clip_offset(end, Bias::Right)
20533 })
20534 .collect(),
20535 false,
20536 window,
20537 cx,
20538 );
20539 }
20540 }
20541
20542 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20543 if !selections.is_empty() {
20544 let snapshot =
20545 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20546 // skip adding the initial selection to selection history
20547 self.selection_history.mode = SelectionHistoryMode::Skipping;
20548 self.change_selections(None, window, cx, |s| {
20549 s.select_ranges(selections.into_iter().map(|(start, end)| {
20550 snapshot.clip_offset(start, Bias::Left)
20551 ..snapshot.clip_offset(end, Bias::Right)
20552 }));
20553 });
20554 self.selection_history.mode = SelectionHistoryMode::Normal;
20555 }
20556 };
20557 }
20558
20559 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20560 }
20561
20562 fn update_lsp_data(
20563 &mut self,
20564 for_server_id: Option<LanguageServerId>,
20565 for_buffer: Option<BufferId>,
20566 window: &mut Window,
20567 cx: &mut Context<'_, Self>,
20568 ) {
20569 self.pull_diagnostics(for_buffer, window, cx);
20570 self.refresh_colors(for_server_id, for_buffer, window, cx);
20571 }
20572}
20573
20574fn vim_enabled(cx: &App) -> bool {
20575 cx.global::<SettingsStore>()
20576 .raw_user_settings()
20577 .get("vim_mode")
20578 == Some(&serde_json::Value::Bool(true))
20579}
20580
20581fn process_completion_for_edit(
20582 completion: &Completion,
20583 intent: CompletionIntent,
20584 buffer: &Entity<Buffer>,
20585 cursor_position: &text::Anchor,
20586 cx: &mut Context<Editor>,
20587) -> CompletionEdit {
20588 let buffer = buffer.read(cx);
20589 let buffer_snapshot = buffer.snapshot();
20590 let (snippet, new_text) = if completion.is_snippet() {
20591 // Workaround for typescript language server issues so that methods don't expand within
20592 // strings and functions with type expressions. The previous point is used because the query
20593 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
20594 let mut snippet_source = completion.new_text.clone();
20595 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
20596 previous_point.column = previous_point.column.saturating_sub(1);
20597 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
20598 if scope.prefers_label_for_snippet_in_completion() {
20599 if let Some(label) = completion.label() {
20600 if matches!(
20601 completion.kind(),
20602 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20603 ) {
20604 snippet_source = label;
20605 }
20606 }
20607 }
20608 }
20609 match Snippet::parse(&snippet_source).log_err() {
20610 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20611 None => (None, completion.new_text.clone()),
20612 }
20613 } else {
20614 (None, completion.new_text.clone())
20615 };
20616
20617 let mut range_to_replace = {
20618 let replace_range = &completion.replace_range;
20619 if let CompletionSource::Lsp {
20620 insert_range: Some(insert_range),
20621 ..
20622 } = &completion.source
20623 {
20624 debug_assert_eq!(
20625 insert_range.start, replace_range.start,
20626 "insert_range and replace_range should start at the same position"
20627 );
20628 debug_assert!(
20629 insert_range
20630 .start
20631 .cmp(&cursor_position, &buffer_snapshot)
20632 .is_le(),
20633 "insert_range should start before or at cursor position"
20634 );
20635 debug_assert!(
20636 replace_range
20637 .start
20638 .cmp(&cursor_position, &buffer_snapshot)
20639 .is_le(),
20640 "replace_range should start before or at cursor position"
20641 );
20642 debug_assert!(
20643 insert_range
20644 .end
20645 .cmp(&cursor_position, &buffer_snapshot)
20646 .is_le(),
20647 "insert_range should end before or at cursor position"
20648 );
20649
20650 let should_replace = match intent {
20651 CompletionIntent::CompleteWithInsert => false,
20652 CompletionIntent::CompleteWithReplace => true,
20653 CompletionIntent::Complete | CompletionIntent::Compose => {
20654 let insert_mode =
20655 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
20656 .completions
20657 .lsp_insert_mode;
20658 match insert_mode {
20659 LspInsertMode::Insert => false,
20660 LspInsertMode::Replace => true,
20661 LspInsertMode::ReplaceSubsequence => {
20662 let mut text_to_replace = buffer.chars_for_range(
20663 buffer.anchor_before(replace_range.start)
20664 ..buffer.anchor_after(replace_range.end),
20665 );
20666 let mut current_needle = text_to_replace.next();
20667 for haystack_ch in completion.label.text.chars() {
20668 if let Some(needle_ch) = current_needle {
20669 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
20670 current_needle = text_to_replace.next();
20671 }
20672 }
20673 }
20674 current_needle.is_none()
20675 }
20676 LspInsertMode::ReplaceSuffix => {
20677 if replace_range
20678 .end
20679 .cmp(&cursor_position, &buffer_snapshot)
20680 .is_gt()
20681 {
20682 let range_after_cursor = *cursor_position..replace_range.end;
20683 let text_after_cursor = buffer
20684 .text_for_range(
20685 buffer.anchor_before(range_after_cursor.start)
20686 ..buffer.anchor_after(range_after_cursor.end),
20687 )
20688 .collect::<String>()
20689 .to_ascii_lowercase();
20690 completion
20691 .label
20692 .text
20693 .to_ascii_lowercase()
20694 .ends_with(&text_after_cursor)
20695 } else {
20696 true
20697 }
20698 }
20699 }
20700 }
20701 };
20702
20703 if should_replace {
20704 replace_range.clone()
20705 } else {
20706 insert_range.clone()
20707 }
20708 } else {
20709 replace_range.clone()
20710 }
20711 };
20712
20713 if range_to_replace
20714 .end
20715 .cmp(&cursor_position, &buffer_snapshot)
20716 .is_lt()
20717 {
20718 range_to_replace.end = *cursor_position;
20719 }
20720
20721 CompletionEdit {
20722 new_text,
20723 replace_range: range_to_replace.to_offset(&buffer),
20724 snippet,
20725 }
20726}
20727
20728struct CompletionEdit {
20729 new_text: String,
20730 replace_range: Range<usize>,
20731 snippet: Option<Snippet>,
20732}
20733
20734fn insert_extra_newline_brackets(
20735 buffer: &MultiBufferSnapshot,
20736 range: Range<usize>,
20737 language: &language::LanguageScope,
20738) -> bool {
20739 let leading_whitespace_len = buffer
20740 .reversed_chars_at(range.start)
20741 .take_while(|c| c.is_whitespace() && *c != '\n')
20742 .map(|c| c.len_utf8())
20743 .sum::<usize>();
20744 let trailing_whitespace_len = buffer
20745 .chars_at(range.end)
20746 .take_while(|c| c.is_whitespace() && *c != '\n')
20747 .map(|c| c.len_utf8())
20748 .sum::<usize>();
20749 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
20750
20751 language.brackets().any(|(pair, enabled)| {
20752 let pair_start = pair.start.trim_end();
20753 let pair_end = pair.end.trim_start();
20754
20755 enabled
20756 && pair.newline
20757 && buffer.contains_str_at(range.end, pair_end)
20758 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
20759 })
20760}
20761
20762fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
20763 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
20764 [(buffer, range, _)] => (*buffer, range.clone()),
20765 _ => return false,
20766 };
20767 let pair = {
20768 let mut result: Option<BracketMatch> = None;
20769
20770 for pair in buffer
20771 .all_bracket_ranges(range.clone())
20772 .filter(move |pair| {
20773 pair.open_range.start <= range.start && pair.close_range.end >= range.end
20774 })
20775 {
20776 let len = pair.close_range.end - pair.open_range.start;
20777
20778 if let Some(existing) = &result {
20779 let existing_len = existing.close_range.end - existing.open_range.start;
20780 if len > existing_len {
20781 continue;
20782 }
20783 }
20784
20785 result = Some(pair);
20786 }
20787
20788 result
20789 };
20790 let Some(pair) = pair else {
20791 return false;
20792 };
20793 pair.newline_only
20794 && buffer
20795 .chars_for_range(pair.open_range.end..range.start)
20796 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
20797 .all(|c| c.is_whitespace() && c != '\n')
20798}
20799
20800fn update_uncommitted_diff_for_buffer(
20801 editor: Entity<Editor>,
20802 project: &Entity<Project>,
20803 buffers: impl IntoIterator<Item = Entity<Buffer>>,
20804 buffer: Entity<MultiBuffer>,
20805 cx: &mut App,
20806) -> Task<()> {
20807 let mut tasks = Vec::new();
20808 project.update(cx, |project, cx| {
20809 for buffer in buffers {
20810 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
20811 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
20812 }
20813 }
20814 });
20815 cx.spawn(async move |cx| {
20816 let diffs = future::join_all(tasks).await;
20817 if editor
20818 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
20819 .unwrap_or(false)
20820 {
20821 return;
20822 }
20823
20824 buffer
20825 .update(cx, |buffer, cx| {
20826 for diff in diffs.into_iter().flatten() {
20827 buffer.add_diff(diff, cx);
20828 }
20829 })
20830 .ok();
20831 })
20832}
20833
20834fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
20835 let tab_size = tab_size.get() as usize;
20836 let mut width = offset;
20837
20838 for ch in text.chars() {
20839 width += if ch == '\t' {
20840 tab_size - (width % tab_size)
20841 } else {
20842 1
20843 };
20844 }
20845
20846 width - offset
20847}
20848
20849#[cfg(test)]
20850mod tests {
20851 use super::*;
20852
20853 #[test]
20854 fn test_string_size_with_expanded_tabs() {
20855 let nz = |val| NonZeroU32::new(val).unwrap();
20856 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
20857 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
20858 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
20859 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
20860 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
20861 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
20862 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
20863 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
20864 }
20865}
20866
20867/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
20868struct WordBreakingTokenizer<'a> {
20869 input: &'a str,
20870}
20871
20872impl<'a> WordBreakingTokenizer<'a> {
20873 fn new(input: &'a str) -> Self {
20874 Self { input }
20875 }
20876}
20877
20878fn is_char_ideographic(ch: char) -> bool {
20879 use unicode_script::Script::*;
20880 use unicode_script::UnicodeScript;
20881 matches!(ch.script(), Han | Tangut | Yi)
20882}
20883
20884fn is_grapheme_ideographic(text: &str) -> bool {
20885 text.chars().any(is_char_ideographic)
20886}
20887
20888fn is_grapheme_whitespace(text: &str) -> bool {
20889 text.chars().any(|x| x.is_whitespace())
20890}
20891
20892fn should_stay_with_preceding_ideograph(text: &str) -> bool {
20893 text.chars().next().map_or(false, |ch| {
20894 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
20895 })
20896}
20897
20898#[derive(PartialEq, Eq, Debug, Clone, Copy)]
20899enum WordBreakToken<'a> {
20900 Word { token: &'a str, grapheme_len: usize },
20901 InlineWhitespace { token: &'a str, grapheme_len: usize },
20902 Newline,
20903}
20904
20905impl<'a> Iterator for WordBreakingTokenizer<'a> {
20906 /// Yields a span, the count of graphemes in the token, and whether it was
20907 /// whitespace. Note that it also breaks at word boundaries.
20908 type Item = WordBreakToken<'a>;
20909
20910 fn next(&mut self) -> Option<Self::Item> {
20911 use unicode_segmentation::UnicodeSegmentation;
20912 if self.input.is_empty() {
20913 return None;
20914 }
20915
20916 let mut iter = self.input.graphemes(true).peekable();
20917 let mut offset = 0;
20918 let mut grapheme_len = 0;
20919 if let Some(first_grapheme) = iter.next() {
20920 let is_newline = first_grapheme == "\n";
20921 let is_whitespace = is_grapheme_whitespace(first_grapheme);
20922 offset += first_grapheme.len();
20923 grapheme_len += 1;
20924 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
20925 if let Some(grapheme) = iter.peek().copied() {
20926 if should_stay_with_preceding_ideograph(grapheme) {
20927 offset += grapheme.len();
20928 grapheme_len += 1;
20929 }
20930 }
20931 } else {
20932 let mut words = self.input[offset..].split_word_bound_indices().peekable();
20933 let mut next_word_bound = words.peek().copied();
20934 if next_word_bound.map_or(false, |(i, _)| i == 0) {
20935 next_word_bound = words.next();
20936 }
20937 while let Some(grapheme) = iter.peek().copied() {
20938 if next_word_bound.map_or(false, |(i, _)| i == offset) {
20939 break;
20940 };
20941 if is_grapheme_whitespace(grapheme) != is_whitespace
20942 || (grapheme == "\n") != is_newline
20943 {
20944 break;
20945 };
20946 offset += grapheme.len();
20947 grapheme_len += 1;
20948 iter.next();
20949 }
20950 }
20951 let token = &self.input[..offset];
20952 self.input = &self.input[offset..];
20953 if token == "\n" {
20954 Some(WordBreakToken::Newline)
20955 } else if is_whitespace {
20956 Some(WordBreakToken::InlineWhitespace {
20957 token,
20958 grapheme_len,
20959 })
20960 } else {
20961 Some(WordBreakToken::Word {
20962 token,
20963 grapheme_len,
20964 })
20965 }
20966 } else {
20967 None
20968 }
20969 }
20970}
20971
20972#[test]
20973fn test_word_breaking_tokenizer() {
20974 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
20975 ("", &[]),
20976 (" ", &[whitespace(" ", 2)]),
20977 ("Ʒ", &[word("Ʒ", 1)]),
20978 ("Ǽ", &[word("Ǽ", 1)]),
20979 ("⋑", &[word("⋑", 1)]),
20980 ("⋑⋑", &[word("⋑⋑", 2)]),
20981 (
20982 "原理,进而",
20983 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
20984 ),
20985 (
20986 "hello world",
20987 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
20988 ),
20989 (
20990 "hello, world",
20991 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
20992 ),
20993 (
20994 " hello world",
20995 &[
20996 whitespace(" ", 2),
20997 word("hello", 5),
20998 whitespace(" ", 1),
20999 word("world", 5),
21000 ],
21001 ),
21002 (
21003 "这是什么 \n 钢笔",
21004 &[
21005 word("这", 1),
21006 word("是", 1),
21007 word("什", 1),
21008 word("么", 1),
21009 whitespace(" ", 1),
21010 newline(),
21011 whitespace(" ", 1),
21012 word("钢", 1),
21013 word("笔", 1),
21014 ],
21015 ),
21016 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21017 ];
21018
21019 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21020 WordBreakToken::Word {
21021 token,
21022 grapheme_len,
21023 }
21024 }
21025
21026 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21027 WordBreakToken::InlineWhitespace {
21028 token,
21029 grapheme_len,
21030 }
21031 }
21032
21033 fn newline() -> WordBreakToken<'static> {
21034 WordBreakToken::Newline
21035 }
21036
21037 for (input, result) in tests {
21038 assert_eq!(
21039 WordBreakingTokenizer::new(input)
21040 .collect::<Vec<_>>()
21041 .as_slice(),
21042 *result,
21043 );
21044 }
21045}
21046
21047fn wrap_with_prefix(
21048 line_prefix: String,
21049 unwrapped_text: String,
21050 wrap_column: usize,
21051 tab_size: NonZeroU32,
21052 preserve_existing_whitespace: bool,
21053) -> String {
21054 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
21055 let mut wrapped_text = String::new();
21056 let mut current_line = line_prefix.clone();
21057
21058 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21059 let mut current_line_len = line_prefix_len;
21060 let mut in_whitespace = false;
21061 for token in tokenizer {
21062 let have_preceding_whitespace = in_whitespace;
21063 match token {
21064 WordBreakToken::Word {
21065 token,
21066 grapheme_len,
21067 } => {
21068 in_whitespace = false;
21069 if current_line_len + grapheme_len > wrap_column
21070 && current_line_len != line_prefix_len
21071 {
21072 wrapped_text.push_str(current_line.trim_end());
21073 wrapped_text.push('\n');
21074 current_line.truncate(line_prefix.len());
21075 current_line_len = line_prefix_len;
21076 }
21077 current_line.push_str(token);
21078 current_line_len += grapheme_len;
21079 }
21080 WordBreakToken::InlineWhitespace {
21081 mut token,
21082 mut grapheme_len,
21083 } => {
21084 in_whitespace = true;
21085 if have_preceding_whitespace && !preserve_existing_whitespace {
21086 continue;
21087 }
21088 if !preserve_existing_whitespace {
21089 token = " ";
21090 grapheme_len = 1;
21091 }
21092 if current_line_len + grapheme_len > wrap_column {
21093 wrapped_text.push_str(current_line.trim_end());
21094 wrapped_text.push('\n');
21095 current_line.truncate(line_prefix.len());
21096 current_line_len = line_prefix_len;
21097 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
21098 current_line.push_str(token);
21099 current_line_len += grapheme_len;
21100 }
21101 }
21102 WordBreakToken::Newline => {
21103 in_whitespace = true;
21104 if preserve_existing_whitespace {
21105 wrapped_text.push_str(current_line.trim_end());
21106 wrapped_text.push('\n');
21107 current_line.truncate(line_prefix.len());
21108 current_line_len = line_prefix_len;
21109 } else if have_preceding_whitespace {
21110 continue;
21111 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
21112 {
21113 wrapped_text.push_str(current_line.trim_end());
21114 wrapped_text.push('\n');
21115 current_line.truncate(line_prefix.len());
21116 current_line_len = line_prefix_len;
21117 } else if current_line_len != line_prefix_len {
21118 current_line.push(' ');
21119 current_line_len += 1;
21120 }
21121 }
21122 }
21123 }
21124
21125 if !current_line.is_empty() {
21126 wrapped_text.push_str(¤t_line);
21127 }
21128 wrapped_text
21129}
21130
21131#[test]
21132fn test_wrap_with_prefix() {
21133 assert_eq!(
21134 wrap_with_prefix(
21135 "# ".to_string(),
21136 "abcdefg".to_string(),
21137 4,
21138 NonZeroU32::new(4).unwrap(),
21139 false,
21140 ),
21141 "# abcdefg"
21142 );
21143 assert_eq!(
21144 wrap_with_prefix(
21145 "".to_string(),
21146 "\thello world".to_string(),
21147 8,
21148 NonZeroU32::new(4).unwrap(),
21149 false,
21150 ),
21151 "hello\nworld"
21152 );
21153 assert_eq!(
21154 wrap_with_prefix(
21155 "// ".to_string(),
21156 "xx \nyy zz aa bb cc".to_string(),
21157 12,
21158 NonZeroU32::new(4).unwrap(),
21159 false,
21160 ),
21161 "// xx yy zz\n// aa bb cc"
21162 );
21163 assert_eq!(
21164 wrap_with_prefix(
21165 String::new(),
21166 "这是什么 \n 钢笔".to_string(),
21167 3,
21168 NonZeroU32::new(4).unwrap(),
21169 false,
21170 ),
21171 "这是什\n么 钢\n笔"
21172 );
21173}
21174
21175pub trait CollaborationHub {
21176 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21177 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21178 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21179}
21180
21181impl CollaborationHub for Entity<Project> {
21182 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21183 self.read(cx).collaborators()
21184 }
21185
21186 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21187 self.read(cx).user_store().read(cx).participant_indices()
21188 }
21189
21190 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21191 let this = self.read(cx);
21192 let user_ids = this.collaborators().values().map(|c| c.user_id);
21193 this.user_store().read(cx).participant_names(user_ids, cx)
21194 }
21195}
21196
21197pub trait SemanticsProvider {
21198 fn hover(
21199 &self,
21200 buffer: &Entity<Buffer>,
21201 position: text::Anchor,
21202 cx: &mut App,
21203 ) -> Option<Task<Vec<project::Hover>>>;
21204
21205 fn inline_values(
21206 &self,
21207 buffer_handle: Entity<Buffer>,
21208 range: Range<text::Anchor>,
21209 cx: &mut App,
21210 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21211
21212 fn inlay_hints(
21213 &self,
21214 buffer_handle: Entity<Buffer>,
21215 range: Range<text::Anchor>,
21216 cx: &mut App,
21217 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21218
21219 fn resolve_inlay_hint(
21220 &self,
21221 hint: InlayHint,
21222 buffer_handle: Entity<Buffer>,
21223 server_id: LanguageServerId,
21224 cx: &mut App,
21225 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21226
21227 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21228
21229 fn document_highlights(
21230 &self,
21231 buffer: &Entity<Buffer>,
21232 position: text::Anchor,
21233 cx: &mut App,
21234 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21235
21236 fn definitions(
21237 &self,
21238 buffer: &Entity<Buffer>,
21239 position: text::Anchor,
21240 kind: GotoDefinitionKind,
21241 cx: &mut App,
21242 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21243
21244 fn range_for_rename(
21245 &self,
21246 buffer: &Entity<Buffer>,
21247 position: text::Anchor,
21248 cx: &mut App,
21249 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21250
21251 fn perform_rename(
21252 &self,
21253 buffer: &Entity<Buffer>,
21254 position: text::Anchor,
21255 new_name: String,
21256 cx: &mut App,
21257 ) -> Option<Task<Result<ProjectTransaction>>>;
21258}
21259
21260pub trait CompletionProvider {
21261 fn completions(
21262 &self,
21263 excerpt_id: ExcerptId,
21264 buffer: &Entity<Buffer>,
21265 buffer_position: text::Anchor,
21266 trigger: CompletionContext,
21267 window: &mut Window,
21268 cx: &mut Context<Editor>,
21269 ) -> Task<Result<Vec<CompletionResponse>>>;
21270
21271 fn resolve_completions(
21272 &self,
21273 _buffer: Entity<Buffer>,
21274 _completion_indices: Vec<usize>,
21275 _completions: Rc<RefCell<Box<[Completion]>>>,
21276 _cx: &mut Context<Editor>,
21277 ) -> Task<Result<bool>> {
21278 Task::ready(Ok(false))
21279 }
21280
21281 fn apply_additional_edits_for_completion(
21282 &self,
21283 _buffer: Entity<Buffer>,
21284 _completions: Rc<RefCell<Box<[Completion]>>>,
21285 _completion_index: usize,
21286 _push_to_history: bool,
21287 _cx: &mut Context<Editor>,
21288 ) -> Task<Result<Option<language::Transaction>>> {
21289 Task::ready(Ok(None))
21290 }
21291
21292 fn is_completion_trigger(
21293 &self,
21294 buffer: &Entity<Buffer>,
21295 position: language::Anchor,
21296 text: &str,
21297 trigger_in_words: bool,
21298 menu_is_open: bool,
21299 cx: &mut Context<Editor>,
21300 ) -> bool;
21301
21302 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21303
21304 fn sort_completions(&self) -> bool {
21305 true
21306 }
21307
21308 fn filter_completions(&self) -> bool {
21309 true
21310 }
21311}
21312
21313pub trait CodeActionProvider {
21314 fn id(&self) -> Arc<str>;
21315
21316 fn code_actions(
21317 &self,
21318 buffer: &Entity<Buffer>,
21319 range: Range<text::Anchor>,
21320 window: &mut Window,
21321 cx: &mut App,
21322 ) -> Task<Result<Vec<CodeAction>>>;
21323
21324 fn apply_code_action(
21325 &self,
21326 buffer_handle: Entity<Buffer>,
21327 action: CodeAction,
21328 excerpt_id: ExcerptId,
21329 push_to_history: bool,
21330 window: &mut Window,
21331 cx: &mut App,
21332 ) -> Task<Result<ProjectTransaction>>;
21333}
21334
21335impl CodeActionProvider for Entity<Project> {
21336 fn id(&self) -> Arc<str> {
21337 "project".into()
21338 }
21339
21340 fn code_actions(
21341 &self,
21342 buffer: &Entity<Buffer>,
21343 range: Range<text::Anchor>,
21344 _window: &mut Window,
21345 cx: &mut App,
21346 ) -> Task<Result<Vec<CodeAction>>> {
21347 self.update(cx, |project, cx| {
21348 let code_lens = project.code_lens(buffer, range.clone(), cx);
21349 let code_actions = project.code_actions(buffer, range, None, cx);
21350 cx.background_spawn(async move {
21351 let (code_lens, code_actions) = join(code_lens, code_actions).await;
21352 Ok(code_lens
21353 .context("code lens fetch")?
21354 .into_iter()
21355 .chain(code_actions.context("code action fetch")?)
21356 .collect())
21357 })
21358 })
21359 }
21360
21361 fn apply_code_action(
21362 &self,
21363 buffer_handle: Entity<Buffer>,
21364 action: CodeAction,
21365 _excerpt_id: ExcerptId,
21366 push_to_history: bool,
21367 _window: &mut Window,
21368 cx: &mut App,
21369 ) -> Task<Result<ProjectTransaction>> {
21370 self.update(cx, |project, cx| {
21371 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21372 })
21373 }
21374}
21375
21376fn snippet_completions(
21377 project: &Project,
21378 buffer: &Entity<Buffer>,
21379 buffer_position: text::Anchor,
21380 cx: &mut App,
21381) -> Task<Result<CompletionResponse>> {
21382 let languages = buffer.read(cx).languages_at(buffer_position);
21383 let snippet_store = project.snippets().read(cx);
21384
21385 let scopes: Vec<_> = languages
21386 .iter()
21387 .filter_map(|language| {
21388 let language_name = language.lsp_id();
21389 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21390
21391 if snippets.is_empty() {
21392 None
21393 } else {
21394 Some((language.default_scope(), snippets))
21395 }
21396 })
21397 .collect();
21398
21399 if scopes.is_empty() {
21400 return Task::ready(Ok(CompletionResponse {
21401 completions: vec![],
21402 is_incomplete: false,
21403 }));
21404 }
21405
21406 let snapshot = buffer.read(cx).text_snapshot();
21407 let chars: String = snapshot
21408 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
21409 .collect();
21410 let executor = cx.background_executor().clone();
21411
21412 cx.background_spawn(async move {
21413 let mut is_incomplete = false;
21414 let mut completions: Vec<Completion> = Vec::new();
21415 for (scope, snippets) in scopes.into_iter() {
21416 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
21417 let mut last_word = chars
21418 .chars()
21419 .take_while(|c| classifier.is_word(*c))
21420 .collect::<String>();
21421 last_word = last_word.chars().rev().collect();
21422
21423 if last_word.is_empty() {
21424 return Ok(CompletionResponse {
21425 completions: vec![],
21426 is_incomplete: true,
21427 });
21428 }
21429
21430 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
21431 let to_lsp = |point: &text::Anchor| {
21432 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
21433 point_to_lsp(end)
21434 };
21435 let lsp_end = to_lsp(&buffer_position);
21436
21437 let candidates = snippets
21438 .iter()
21439 .enumerate()
21440 .flat_map(|(ix, snippet)| {
21441 snippet
21442 .prefix
21443 .iter()
21444 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21445 })
21446 .collect::<Vec<StringMatchCandidate>>();
21447
21448 const MAX_RESULTS: usize = 100;
21449 let mut matches = fuzzy::match_strings(
21450 &candidates,
21451 &last_word,
21452 last_word.chars().any(|c| c.is_uppercase()),
21453 true,
21454 MAX_RESULTS,
21455 &Default::default(),
21456 executor.clone(),
21457 )
21458 .await;
21459
21460 if matches.len() >= MAX_RESULTS {
21461 is_incomplete = true;
21462 }
21463
21464 // Remove all candidates where the query's start does not match the start of any word in the candidate
21465 if let Some(query_start) = last_word.chars().next() {
21466 matches.retain(|string_match| {
21467 split_words(&string_match.string).any(|word| {
21468 // Check that the first codepoint of the word as lowercase matches the first
21469 // codepoint of the query as lowercase
21470 word.chars()
21471 .flat_map(|codepoint| codepoint.to_lowercase())
21472 .zip(query_start.to_lowercase())
21473 .all(|(word_cp, query_cp)| word_cp == query_cp)
21474 })
21475 });
21476 }
21477
21478 let matched_strings = matches
21479 .into_iter()
21480 .map(|m| m.string)
21481 .collect::<HashSet<_>>();
21482
21483 completions.extend(snippets.iter().filter_map(|snippet| {
21484 let matching_prefix = snippet
21485 .prefix
21486 .iter()
21487 .find(|prefix| matched_strings.contains(*prefix))?;
21488 let start = as_offset - last_word.len();
21489 let start = snapshot.anchor_before(start);
21490 let range = start..buffer_position;
21491 let lsp_start = to_lsp(&start);
21492 let lsp_range = lsp::Range {
21493 start: lsp_start,
21494 end: lsp_end,
21495 };
21496 Some(Completion {
21497 replace_range: range,
21498 new_text: snippet.body.clone(),
21499 source: CompletionSource::Lsp {
21500 insert_range: None,
21501 server_id: LanguageServerId(usize::MAX),
21502 resolved: true,
21503 lsp_completion: Box::new(lsp::CompletionItem {
21504 label: snippet.prefix.first().unwrap().clone(),
21505 kind: Some(CompletionItemKind::SNIPPET),
21506 label_details: snippet.description.as_ref().map(|description| {
21507 lsp::CompletionItemLabelDetails {
21508 detail: Some(description.clone()),
21509 description: None,
21510 }
21511 }),
21512 insert_text_format: Some(InsertTextFormat::SNIPPET),
21513 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21514 lsp::InsertReplaceEdit {
21515 new_text: snippet.body.clone(),
21516 insert: lsp_range,
21517 replace: lsp_range,
21518 },
21519 )),
21520 filter_text: Some(snippet.body.clone()),
21521 sort_text: Some(char::MAX.to_string()),
21522 ..lsp::CompletionItem::default()
21523 }),
21524 lsp_defaults: None,
21525 },
21526 label: CodeLabel {
21527 text: matching_prefix.clone(),
21528 runs: Vec::new(),
21529 filter_range: 0..matching_prefix.len(),
21530 },
21531 icon_path: None,
21532 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
21533 single_line: snippet.name.clone().into(),
21534 plain_text: snippet
21535 .description
21536 .clone()
21537 .map(|description| description.into()),
21538 }),
21539 insert_text_mode: None,
21540 confirm: None,
21541 })
21542 }))
21543 }
21544
21545 Ok(CompletionResponse {
21546 completions,
21547 is_incomplete,
21548 })
21549 })
21550}
21551
21552impl CompletionProvider for Entity<Project> {
21553 fn completions(
21554 &self,
21555 _excerpt_id: ExcerptId,
21556 buffer: &Entity<Buffer>,
21557 buffer_position: text::Anchor,
21558 options: CompletionContext,
21559 _window: &mut Window,
21560 cx: &mut Context<Editor>,
21561 ) -> Task<Result<Vec<CompletionResponse>>> {
21562 self.update(cx, |project, cx| {
21563 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21564 let project_completions = project.completions(buffer, buffer_position, options, cx);
21565 cx.background_spawn(async move {
21566 let mut responses = project_completions.await?;
21567 let snippets = snippets.await?;
21568 if !snippets.completions.is_empty() {
21569 responses.push(snippets);
21570 }
21571 Ok(responses)
21572 })
21573 })
21574 }
21575
21576 fn resolve_completions(
21577 &self,
21578 buffer: Entity<Buffer>,
21579 completion_indices: Vec<usize>,
21580 completions: Rc<RefCell<Box<[Completion]>>>,
21581 cx: &mut Context<Editor>,
21582 ) -> Task<Result<bool>> {
21583 self.update(cx, |project, cx| {
21584 project.lsp_store().update(cx, |lsp_store, cx| {
21585 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21586 })
21587 })
21588 }
21589
21590 fn apply_additional_edits_for_completion(
21591 &self,
21592 buffer: Entity<Buffer>,
21593 completions: Rc<RefCell<Box<[Completion]>>>,
21594 completion_index: usize,
21595 push_to_history: bool,
21596 cx: &mut Context<Editor>,
21597 ) -> Task<Result<Option<language::Transaction>>> {
21598 self.update(cx, |project, cx| {
21599 project.lsp_store().update(cx, |lsp_store, cx| {
21600 lsp_store.apply_additional_edits_for_completion(
21601 buffer,
21602 completions,
21603 completion_index,
21604 push_to_history,
21605 cx,
21606 )
21607 })
21608 })
21609 }
21610
21611 fn is_completion_trigger(
21612 &self,
21613 buffer: &Entity<Buffer>,
21614 position: language::Anchor,
21615 text: &str,
21616 trigger_in_words: bool,
21617 menu_is_open: bool,
21618 cx: &mut Context<Editor>,
21619 ) -> bool {
21620 let mut chars = text.chars();
21621 let char = if let Some(char) = chars.next() {
21622 char
21623 } else {
21624 return false;
21625 };
21626 if chars.next().is_some() {
21627 return false;
21628 }
21629
21630 let buffer = buffer.read(cx);
21631 let snapshot = buffer.snapshot();
21632 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
21633 return false;
21634 }
21635 let classifier = snapshot.char_classifier_at(position).for_completion(true);
21636 if trigger_in_words && classifier.is_word(char) {
21637 return true;
21638 }
21639
21640 buffer.completion_triggers().contains(text)
21641 }
21642}
21643
21644impl SemanticsProvider for Entity<Project> {
21645 fn hover(
21646 &self,
21647 buffer: &Entity<Buffer>,
21648 position: text::Anchor,
21649 cx: &mut App,
21650 ) -> Option<Task<Vec<project::Hover>>> {
21651 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
21652 }
21653
21654 fn document_highlights(
21655 &self,
21656 buffer: &Entity<Buffer>,
21657 position: text::Anchor,
21658 cx: &mut App,
21659 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
21660 Some(self.update(cx, |project, cx| {
21661 project.document_highlights(buffer, position, cx)
21662 }))
21663 }
21664
21665 fn definitions(
21666 &self,
21667 buffer: &Entity<Buffer>,
21668 position: text::Anchor,
21669 kind: GotoDefinitionKind,
21670 cx: &mut App,
21671 ) -> Option<Task<Result<Vec<LocationLink>>>> {
21672 Some(self.update(cx, |project, cx| match kind {
21673 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
21674 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
21675 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
21676 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
21677 }))
21678 }
21679
21680 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
21681 // TODO: make this work for remote projects
21682 self.update(cx, |project, cx| {
21683 if project
21684 .active_debug_session(cx)
21685 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
21686 {
21687 return true;
21688 }
21689
21690 buffer.update(cx, |buffer, cx| {
21691 project.any_language_server_supports_inlay_hints(buffer, cx)
21692 })
21693 })
21694 }
21695
21696 fn inline_values(
21697 &self,
21698 buffer_handle: Entity<Buffer>,
21699 range: Range<text::Anchor>,
21700 cx: &mut App,
21701 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21702 self.update(cx, |project, cx| {
21703 let (session, active_stack_frame) = project.active_debug_session(cx)?;
21704
21705 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
21706 })
21707 }
21708
21709 fn inlay_hints(
21710 &self,
21711 buffer_handle: Entity<Buffer>,
21712 range: Range<text::Anchor>,
21713 cx: &mut App,
21714 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21715 Some(self.update(cx, |project, cx| {
21716 project.inlay_hints(buffer_handle, range, cx)
21717 }))
21718 }
21719
21720 fn resolve_inlay_hint(
21721 &self,
21722 hint: InlayHint,
21723 buffer_handle: Entity<Buffer>,
21724 server_id: LanguageServerId,
21725 cx: &mut App,
21726 ) -> Option<Task<anyhow::Result<InlayHint>>> {
21727 Some(self.update(cx, |project, cx| {
21728 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
21729 }))
21730 }
21731
21732 fn range_for_rename(
21733 &self,
21734 buffer: &Entity<Buffer>,
21735 position: text::Anchor,
21736 cx: &mut App,
21737 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
21738 Some(self.update(cx, |project, cx| {
21739 let buffer = buffer.clone();
21740 let task = project.prepare_rename(buffer.clone(), position, cx);
21741 cx.spawn(async move |_, cx| {
21742 Ok(match task.await? {
21743 PrepareRenameResponse::Success(range) => Some(range),
21744 PrepareRenameResponse::InvalidPosition => None,
21745 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
21746 // Fallback on using TreeSitter info to determine identifier range
21747 buffer.read_with(cx, |buffer, _| {
21748 let snapshot = buffer.snapshot();
21749 let (range, kind) = snapshot.surrounding_word(position);
21750 if kind != Some(CharKind::Word) {
21751 return None;
21752 }
21753 Some(
21754 snapshot.anchor_before(range.start)
21755 ..snapshot.anchor_after(range.end),
21756 )
21757 })?
21758 }
21759 })
21760 })
21761 }))
21762 }
21763
21764 fn perform_rename(
21765 &self,
21766 buffer: &Entity<Buffer>,
21767 position: text::Anchor,
21768 new_name: String,
21769 cx: &mut App,
21770 ) -> Option<Task<Result<ProjectTransaction>>> {
21771 Some(self.update(cx, |project, cx| {
21772 project.perform_rename(buffer.clone(), position, new_name, cx)
21773 }))
21774 }
21775}
21776
21777fn inlay_hint_settings(
21778 location: Anchor,
21779 snapshot: &MultiBufferSnapshot,
21780 cx: &mut Context<Editor>,
21781) -> InlayHintSettings {
21782 let file = snapshot.file_at(location);
21783 let language = snapshot.language_at(location).map(|l| l.name());
21784 language_settings(language, file, cx).inlay_hints
21785}
21786
21787fn consume_contiguous_rows(
21788 contiguous_row_selections: &mut Vec<Selection<Point>>,
21789 selection: &Selection<Point>,
21790 display_map: &DisplaySnapshot,
21791 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
21792) -> (MultiBufferRow, MultiBufferRow) {
21793 contiguous_row_selections.push(selection.clone());
21794 let start_row = MultiBufferRow(selection.start.row);
21795 let mut end_row = ending_row(selection, display_map);
21796
21797 while let Some(next_selection) = selections.peek() {
21798 if next_selection.start.row <= end_row.0 {
21799 end_row = ending_row(next_selection, display_map);
21800 contiguous_row_selections.push(selections.next().unwrap().clone());
21801 } else {
21802 break;
21803 }
21804 }
21805 (start_row, end_row)
21806}
21807
21808fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
21809 if next_selection.end.column > 0 || next_selection.is_empty() {
21810 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
21811 } else {
21812 MultiBufferRow(next_selection.end.row)
21813 }
21814}
21815
21816impl EditorSnapshot {
21817 pub fn remote_selections_in_range<'a>(
21818 &'a self,
21819 range: &'a Range<Anchor>,
21820 collaboration_hub: &dyn CollaborationHub,
21821 cx: &'a App,
21822 ) -> impl 'a + Iterator<Item = RemoteSelection> {
21823 let participant_names = collaboration_hub.user_names(cx);
21824 let participant_indices = collaboration_hub.user_participant_indices(cx);
21825 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
21826 let collaborators_by_replica_id = collaborators_by_peer_id
21827 .values()
21828 .map(|collaborator| (collaborator.replica_id, collaborator))
21829 .collect::<HashMap<_, _>>();
21830 self.buffer_snapshot
21831 .selections_in_range(range, false)
21832 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
21833 if replica_id == AGENT_REPLICA_ID {
21834 Some(RemoteSelection {
21835 replica_id,
21836 selection,
21837 cursor_shape,
21838 line_mode,
21839 collaborator_id: CollaboratorId::Agent,
21840 user_name: Some("Agent".into()),
21841 color: cx.theme().players().agent(),
21842 })
21843 } else {
21844 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
21845 let participant_index = participant_indices.get(&collaborator.user_id).copied();
21846 let user_name = participant_names.get(&collaborator.user_id).cloned();
21847 Some(RemoteSelection {
21848 replica_id,
21849 selection,
21850 cursor_shape,
21851 line_mode,
21852 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
21853 user_name,
21854 color: if let Some(index) = participant_index {
21855 cx.theme().players().color_for_participant(index.0)
21856 } else {
21857 cx.theme().players().absent()
21858 },
21859 })
21860 }
21861 })
21862 }
21863
21864 pub fn hunks_for_ranges(
21865 &self,
21866 ranges: impl IntoIterator<Item = Range<Point>>,
21867 ) -> Vec<MultiBufferDiffHunk> {
21868 let mut hunks = Vec::new();
21869 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
21870 HashMap::default();
21871 for query_range in ranges {
21872 let query_rows =
21873 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
21874 for hunk in self.buffer_snapshot.diff_hunks_in_range(
21875 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
21876 ) {
21877 // Include deleted hunks that are adjacent to the query range, because
21878 // otherwise they would be missed.
21879 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
21880 if hunk.status().is_deleted() {
21881 intersects_range |= hunk.row_range.start == query_rows.end;
21882 intersects_range |= hunk.row_range.end == query_rows.start;
21883 }
21884 if intersects_range {
21885 if !processed_buffer_rows
21886 .entry(hunk.buffer_id)
21887 .or_default()
21888 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
21889 {
21890 continue;
21891 }
21892 hunks.push(hunk);
21893 }
21894 }
21895 }
21896
21897 hunks
21898 }
21899
21900 fn display_diff_hunks_for_rows<'a>(
21901 &'a self,
21902 display_rows: Range<DisplayRow>,
21903 folded_buffers: &'a HashSet<BufferId>,
21904 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
21905 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
21906 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
21907
21908 self.buffer_snapshot
21909 .diff_hunks_in_range(buffer_start..buffer_end)
21910 .filter_map(|hunk| {
21911 if folded_buffers.contains(&hunk.buffer_id) {
21912 return None;
21913 }
21914
21915 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
21916 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
21917
21918 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
21919 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
21920
21921 let display_hunk = if hunk_display_start.column() != 0 {
21922 DisplayDiffHunk::Folded {
21923 display_row: hunk_display_start.row(),
21924 }
21925 } else {
21926 let mut end_row = hunk_display_end.row();
21927 if hunk_display_end.column() > 0 {
21928 end_row.0 += 1;
21929 }
21930 let is_created_file = hunk.is_created_file();
21931 DisplayDiffHunk::Unfolded {
21932 status: hunk.status(),
21933 diff_base_byte_range: hunk.diff_base_byte_range,
21934 display_row_range: hunk_display_start.row()..end_row,
21935 multi_buffer_range: Anchor::range_in_buffer(
21936 hunk.excerpt_id,
21937 hunk.buffer_id,
21938 hunk.buffer_range,
21939 ),
21940 is_created_file,
21941 }
21942 };
21943
21944 Some(display_hunk)
21945 })
21946 }
21947
21948 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
21949 self.display_snapshot.buffer_snapshot.language_at(position)
21950 }
21951
21952 pub fn is_focused(&self) -> bool {
21953 self.is_focused
21954 }
21955
21956 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
21957 self.placeholder_text.as_ref()
21958 }
21959
21960 pub fn scroll_position(&self) -> gpui::Point<f32> {
21961 self.scroll_anchor.scroll_position(&self.display_snapshot)
21962 }
21963
21964 fn gutter_dimensions(
21965 &self,
21966 font_id: FontId,
21967 font_size: Pixels,
21968 max_line_number_width: Pixels,
21969 cx: &App,
21970 ) -> Option<GutterDimensions> {
21971 if !self.show_gutter {
21972 return None;
21973 }
21974
21975 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
21976 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
21977
21978 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
21979 matches!(
21980 ProjectSettings::get_global(cx).git.git_gutter,
21981 Some(GitGutterSetting::TrackedFiles)
21982 )
21983 });
21984 let gutter_settings = EditorSettings::get_global(cx).gutter;
21985 let show_line_numbers = self
21986 .show_line_numbers
21987 .unwrap_or(gutter_settings.line_numbers);
21988 let line_gutter_width = if show_line_numbers {
21989 // Avoid flicker-like gutter resizes when the line number gains another digit by
21990 // only resizing the gutter on files with > 10**min_line_number_digits lines.
21991 let min_width_for_number_on_gutter =
21992 ch_advance * gutter_settings.min_line_number_digits as f32;
21993 max_line_number_width.max(min_width_for_number_on_gutter)
21994 } else {
21995 0.0.into()
21996 };
21997
21998 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
21999 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22000
22001 let git_blame_entries_width =
22002 self.git_blame_gutter_max_author_length
22003 .map(|max_author_length| {
22004 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22005 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22006
22007 /// The number of characters to dedicate to gaps and margins.
22008 const SPACING_WIDTH: usize = 4;
22009
22010 let max_char_count = max_author_length.min(renderer.max_author_length())
22011 + ::git::SHORT_SHA_LENGTH
22012 + MAX_RELATIVE_TIMESTAMP.len()
22013 + SPACING_WIDTH;
22014
22015 ch_advance * max_char_count
22016 });
22017
22018 let is_singleton = self.buffer_snapshot.is_singleton();
22019
22020 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22021 left_padding += if !is_singleton {
22022 ch_width * 4.0
22023 } else if show_runnables || show_breakpoints {
22024 ch_width * 3.0
22025 } else if show_git_gutter && show_line_numbers {
22026 ch_width * 2.0
22027 } else if show_git_gutter || show_line_numbers {
22028 ch_width
22029 } else {
22030 px(0.)
22031 };
22032
22033 let shows_folds = is_singleton && gutter_settings.folds;
22034
22035 let right_padding = if shows_folds && show_line_numbers {
22036 ch_width * 4.0
22037 } else if shows_folds || (!is_singleton && show_line_numbers) {
22038 ch_width * 3.0
22039 } else if show_line_numbers {
22040 ch_width
22041 } else {
22042 px(0.)
22043 };
22044
22045 Some(GutterDimensions {
22046 left_padding,
22047 right_padding,
22048 width: line_gutter_width + left_padding + right_padding,
22049 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22050 git_blame_entries_width,
22051 })
22052 }
22053
22054 pub fn render_crease_toggle(
22055 &self,
22056 buffer_row: MultiBufferRow,
22057 row_contains_cursor: bool,
22058 editor: Entity<Editor>,
22059 window: &mut Window,
22060 cx: &mut App,
22061 ) -> Option<AnyElement> {
22062 let folded = self.is_line_folded(buffer_row);
22063 let mut is_foldable = false;
22064
22065 if let Some(crease) = self
22066 .crease_snapshot
22067 .query_row(buffer_row, &self.buffer_snapshot)
22068 {
22069 is_foldable = true;
22070 match crease {
22071 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22072 if let Some(render_toggle) = render_toggle {
22073 let toggle_callback =
22074 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22075 if folded {
22076 editor.update(cx, |editor, cx| {
22077 editor.fold_at(buffer_row, window, cx)
22078 });
22079 } else {
22080 editor.update(cx, |editor, cx| {
22081 editor.unfold_at(buffer_row, window, cx)
22082 });
22083 }
22084 });
22085 return Some((render_toggle)(
22086 buffer_row,
22087 folded,
22088 toggle_callback,
22089 window,
22090 cx,
22091 ));
22092 }
22093 }
22094 }
22095 }
22096
22097 is_foldable |= self.starts_indent(buffer_row);
22098
22099 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22100 Some(
22101 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22102 .toggle_state(folded)
22103 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22104 if folded {
22105 this.unfold_at(buffer_row, window, cx);
22106 } else {
22107 this.fold_at(buffer_row, window, cx);
22108 }
22109 }))
22110 .into_any_element(),
22111 )
22112 } else {
22113 None
22114 }
22115 }
22116
22117 pub fn render_crease_trailer(
22118 &self,
22119 buffer_row: MultiBufferRow,
22120 window: &mut Window,
22121 cx: &mut App,
22122 ) -> Option<AnyElement> {
22123 let folded = self.is_line_folded(buffer_row);
22124 if let Crease::Inline { render_trailer, .. } = self
22125 .crease_snapshot
22126 .query_row(buffer_row, &self.buffer_snapshot)?
22127 {
22128 let render_trailer = render_trailer.as_ref()?;
22129 Some(render_trailer(buffer_row, folded, window, cx))
22130 } else {
22131 None
22132 }
22133 }
22134}
22135
22136impl Deref for EditorSnapshot {
22137 type Target = DisplaySnapshot;
22138
22139 fn deref(&self) -> &Self::Target {
22140 &self.display_snapshot
22141 }
22142}
22143
22144#[derive(Clone, Debug, PartialEq, Eq)]
22145pub enum EditorEvent {
22146 InputIgnored {
22147 text: Arc<str>,
22148 },
22149 InputHandled {
22150 utf16_range_to_replace: Option<Range<isize>>,
22151 text: Arc<str>,
22152 },
22153 ExcerptsAdded {
22154 buffer: Entity<Buffer>,
22155 predecessor: ExcerptId,
22156 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22157 },
22158 ExcerptsRemoved {
22159 ids: Vec<ExcerptId>,
22160 removed_buffer_ids: Vec<BufferId>,
22161 },
22162 BufferFoldToggled {
22163 ids: Vec<ExcerptId>,
22164 folded: bool,
22165 },
22166 ExcerptsEdited {
22167 ids: Vec<ExcerptId>,
22168 },
22169 ExcerptsExpanded {
22170 ids: Vec<ExcerptId>,
22171 },
22172 BufferEdited,
22173 Edited {
22174 transaction_id: clock::Lamport,
22175 },
22176 Reparsed(BufferId),
22177 Focused,
22178 FocusedIn,
22179 Blurred,
22180 DirtyChanged,
22181 Saved,
22182 TitleChanged,
22183 DiffBaseChanged,
22184 SelectionsChanged {
22185 local: bool,
22186 },
22187 ScrollPositionChanged {
22188 local: bool,
22189 autoscroll: bool,
22190 },
22191 Closed,
22192 TransactionUndone {
22193 transaction_id: clock::Lamport,
22194 },
22195 TransactionBegun {
22196 transaction_id: clock::Lamport,
22197 },
22198 Reloaded,
22199 CursorShapeChanged,
22200 PushedToNavHistory {
22201 anchor: Anchor,
22202 is_deactivate: bool,
22203 },
22204}
22205
22206impl EventEmitter<EditorEvent> for Editor {}
22207
22208impl Focusable for Editor {
22209 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22210 self.focus_handle.clone()
22211 }
22212}
22213
22214impl Render for Editor {
22215 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22216 let settings = ThemeSettings::get_global(cx);
22217
22218 let mut text_style = match self.mode {
22219 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22220 color: cx.theme().colors().editor_foreground,
22221 font_family: settings.ui_font.family.clone(),
22222 font_features: settings.ui_font.features.clone(),
22223 font_fallbacks: settings.ui_font.fallbacks.clone(),
22224 font_size: rems(0.875).into(),
22225 font_weight: settings.ui_font.weight,
22226 line_height: relative(settings.buffer_line_height.value()),
22227 ..Default::default()
22228 },
22229 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22230 color: cx.theme().colors().editor_foreground,
22231 font_family: settings.buffer_font.family.clone(),
22232 font_features: settings.buffer_font.features.clone(),
22233 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22234 font_size: settings.buffer_font_size(cx).into(),
22235 font_weight: settings.buffer_font.weight,
22236 line_height: relative(settings.buffer_line_height.value()),
22237 ..Default::default()
22238 },
22239 };
22240 if let Some(text_style_refinement) = &self.text_style_refinement {
22241 text_style.refine(text_style_refinement)
22242 }
22243
22244 let background = match self.mode {
22245 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22246 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22247 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22248 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22249 };
22250
22251 EditorElement::new(
22252 &cx.entity(),
22253 EditorStyle {
22254 background,
22255 local_player: cx.theme().players().local(),
22256 text: text_style,
22257 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22258 syntax: cx.theme().syntax().clone(),
22259 status: cx.theme().status().clone(),
22260 inlay_hints_style: make_inlay_hints_style(cx),
22261 inline_completion_styles: make_suggestion_styles(cx),
22262 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22263 show_underlines: self.diagnostics_enabled(),
22264 },
22265 )
22266 }
22267}
22268
22269impl EntityInputHandler for Editor {
22270 fn text_for_range(
22271 &mut self,
22272 range_utf16: Range<usize>,
22273 adjusted_range: &mut Option<Range<usize>>,
22274 _: &mut Window,
22275 cx: &mut Context<Self>,
22276 ) -> Option<String> {
22277 let snapshot = self.buffer.read(cx).read(cx);
22278 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22279 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22280 if (start.0..end.0) != range_utf16 {
22281 adjusted_range.replace(start.0..end.0);
22282 }
22283 Some(snapshot.text_for_range(start..end).collect())
22284 }
22285
22286 fn selected_text_range(
22287 &mut self,
22288 ignore_disabled_input: bool,
22289 _: &mut Window,
22290 cx: &mut Context<Self>,
22291 ) -> Option<UTF16Selection> {
22292 // Prevent the IME menu from appearing when holding down an alphabetic key
22293 // while input is disabled.
22294 if !ignore_disabled_input && !self.input_enabled {
22295 return None;
22296 }
22297
22298 let selection = self.selections.newest::<OffsetUtf16>(cx);
22299 let range = selection.range();
22300
22301 Some(UTF16Selection {
22302 range: range.start.0..range.end.0,
22303 reversed: selection.reversed,
22304 })
22305 }
22306
22307 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22308 let snapshot = self.buffer.read(cx).read(cx);
22309 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22310 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22311 }
22312
22313 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22314 self.clear_highlights::<InputComposition>(cx);
22315 self.ime_transaction.take();
22316 }
22317
22318 fn replace_text_in_range(
22319 &mut self,
22320 range_utf16: Option<Range<usize>>,
22321 text: &str,
22322 window: &mut Window,
22323 cx: &mut Context<Self>,
22324 ) {
22325 if !self.input_enabled {
22326 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22327 return;
22328 }
22329
22330 self.transact(window, cx, |this, window, cx| {
22331 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22332 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22333 Some(this.selection_replacement_ranges(range_utf16, cx))
22334 } else {
22335 this.marked_text_ranges(cx)
22336 };
22337
22338 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22339 let newest_selection_id = this.selections.newest_anchor().id;
22340 this.selections
22341 .all::<OffsetUtf16>(cx)
22342 .iter()
22343 .zip(ranges_to_replace.iter())
22344 .find_map(|(selection, range)| {
22345 if selection.id == newest_selection_id {
22346 Some(
22347 (range.start.0 as isize - selection.head().0 as isize)
22348 ..(range.end.0 as isize - selection.head().0 as isize),
22349 )
22350 } else {
22351 None
22352 }
22353 })
22354 });
22355
22356 cx.emit(EditorEvent::InputHandled {
22357 utf16_range_to_replace: range_to_replace,
22358 text: text.into(),
22359 });
22360
22361 if let Some(new_selected_ranges) = new_selected_ranges {
22362 this.change_selections(None, window, cx, |selections| {
22363 selections.select_ranges(new_selected_ranges)
22364 });
22365 this.backspace(&Default::default(), window, cx);
22366 }
22367
22368 this.handle_input(text, window, cx);
22369 });
22370
22371 if let Some(transaction) = self.ime_transaction {
22372 self.buffer.update(cx, |buffer, cx| {
22373 buffer.group_until_transaction(transaction, cx);
22374 });
22375 }
22376
22377 self.unmark_text(window, cx);
22378 }
22379
22380 fn replace_and_mark_text_in_range(
22381 &mut self,
22382 range_utf16: Option<Range<usize>>,
22383 text: &str,
22384 new_selected_range_utf16: Option<Range<usize>>,
22385 window: &mut Window,
22386 cx: &mut Context<Self>,
22387 ) {
22388 if !self.input_enabled {
22389 return;
22390 }
22391
22392 let transaction = self.transact(window, cx, |this, window, cx| {
22393 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22394 let snapshot = this.buffer.read(cx).read(cx);
22395 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22396 for marked_range in &mut marked_ranges {
22397 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22398 marked_range.start.0 += relative_range_utf16.start;
22399 marked_range.start =
22400 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22401 marked_range.end =
22402 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22403 }
22404 }
22405 Some(marked_ranges)
22406 } else if let Some(range_utf16) = range_utf16 {
22407 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22408 Some(this.selection_replacement_ranges(range_utf16, cx))
22409 } else {
22410 None
22411 };
22412
22413 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22414 let newest_selection_id = this.selections.newest_anchor().id;
22415 this.selections
22416 .all::<OffsetUtf16>(cx)
22417 .iter()
22418 .zip(ranges_to_replace.iter())
22419 .find_map(|(selection, range)| {
22420 if selection.id == newest_selection_id {
22421 Some(
22422 (range.start.0 as isize - selection.head().0 as isize)
22423 ..(range.end.0 as isize - selection.head().0 as isize),
22424 )
22425 } else {
22426 None
22427 }
22428 })
22429 });
22430
22431 cx.emit(EditorEvent::InputHandled {
22432 utf16_range_to_replace: range_to_replace,
22433 text: text.into(),
22434 });
22435
22436 if let Some(ranges) = ranges_to_replace {
22437 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
22438 }
22439
22440 let marked_ranges = {
22441 let snapshot = this.buffer.read(cx).read(cx);
22442 this.selections
22443 .disjoint_anchors()
22444 .iter()
22445 .map(|selection| {
22446 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22447 })
22448 .collect::<Vec<_>>()
22449 };
22450
22451 if text.is_empty() {
22452 this.unmark_text(window, cx);
22453 } else {
22454 this.highlight_text::<InputComposition>(
22455 marked_ranges.clone(),
22456 HighlightStyle {
22457 underline: Some(UnderlineStyle {
22458 thickness: px(1.),
22459 color: None,
22460 wavy: false,
22461 }),
22462 ..Default::default()
22463 },
22464 cx,
22465 );
22466 }
22467
22468 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22469 let use_autoclose = this.use_autoclose;
22470 let use_auto_surround = this.use_auto_surround;
22471 this.set_use_autoclose(false);
22472 this.set_use_auto_surround(false);
22473 this.handle_input(text, window, cx);
22474 this.set_use_autoclose(use_autoclose);
22475 this.set_use_auto_surround(use_auto_surround);
22476
22477 if let Some(new_selected_range) = new_selected_range_utf16 {
22478 let snapshot = this.buffer.read(cx).read(cx);
22479 let new_selected_ranges = marked_ranges
22480 .into_iter()
22481 .map(|marked_range| {
22482 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22483 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22484 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22485 snapshot.clip_offset_utf16(new_start, Bias::Left)
22486 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22487 })
22488 .collect::<Vec<_>>();
22489
22490 drop(snapshot);
22491 this.change_selections(None, window, cx, |selections| {
22492 selections.select_ranges(new_selected_ranges)
22493 });
22494 }
22495 });
22496
22497 self.ime_transaction = self.ime_transaction.or(transaction);
22498 if let Some(transaction) = self.ime_transaction {
22499 self.buffer.update(cx, |buffer, cx| {
22500 buffer.group_until_transaction(transaction, cx);
22501 });
22502 }
22503
22504 if self.text_highlights::<InputComposition>(cx).is_none() {
22505 self.ime_transaction.take();
22506 }
22507 }
22508
22509 fn bounds_for_range(
22510 &mut self,
22511 range_utf16: Range<usize>,
22512 element_bounds: gpui::Bounds<Pixels>,
22513 window: &mut Window,
22514 cx: &mut Context<Self>,
22515 ) -> Option<gpui::Bounds<Pixels>> {
22516 let text_layout_details = self.text_layout_details(window);
22517 let gpui::Size {
22518 width: em_width,
22519 height: line_height,
22520 } = self.character_size(window);
22521
22522 let snapshot = self.snapshot(window, cx);
22523 let scroll_position = snapshot.scroll_position();
22524 let scroll_left = scroll_position.x * em_width;
22525
22526 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22527 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22528 + self.gutter_dimensions.width
22529 + self.gutter_dimensions.margin;
22530 let y = line_height * (start.row().as_f32() - scroll_position.y);
22531
22532 Some(Bounds {
22533 origin: element_bounds.origin + point(x, y),
22534 size: size(em_width, line_height),
22535 })
22536 }
22537
22538 fn character_index_for_point(
22539 &mut self,
22540 point: gpui::Point<Pixels>,
22541 _window: &mut Window,
22542 _cx: &mut Context<Self>,
22543 ) -> Option<usize> {
22544 let position_map = self.last_position_map.as_ref()?;
22545 if !position_map.text_hitbox.contains(&point) {
22546 return None;
22547 }
22548 let display_point = position_map.point_for_position(point).previous_valid;
22549 let anchor = position_map
22550 .snapshot
22551 .display_point_to_anchor(display_point, Bias::Left);
22552 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22553 Some(utf16_offset.0)
22554 }
22555}
22556
22557trait SelectionExt {
22558 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22559 fn spanned_rows(
22560 &self,
22561 include_end_if_at_line_start: bool,
22562 map: &DisplaySnapshot,
22563 ) -> Range<MultiBufferRow>;
22564}
22565
22566impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22567 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22568 let start = self
22569 .start
22570 .to_point(&map.buffer_snapshot)
22571 .to_display_point(map);
22572 let end = self
22573 .end
22574 .to_point(&map.buffer_snapshot)
22575 .to_display_point(map);
22576 if self.reversed {
22577 end..start
22578 } else {
22579 start..end
22580 }
22581 }
22582
22583 fn spanned_rows(
22584 &self,
22585 include_end_if_at_line_start: bool,
22586 map: &DisplaySnapshot,
22587 ) -> Range<MultiBufferRow> {
22588 let start = self.start.to_point(&map.buffer_snapshot);
22589 let mut end = self.end.to_point(&map.buffer_snapshot);
22590 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22591 end.row -= 1;
22592 }
22593
22594 let buffer_start = map.prev_line_boundary(start).0;
22595 let buffer_end = map.next_line_boundary(end).0;
22596 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22597 }
22598}
22599
22600impl<T: InvalidationRegion> InvalidationStack<T> {
22601 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22602 where
22603 S: Clone + ToOffset,
22604 {
22605 while let Some(region) = self.last() {
22606 let all_selections_inside_invalidation_ranges =
22607 if selections.len() == region.ranges().len() {
22608 selections
22609 .iter()
22610 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
22611 .all(|(selection, invalidation_range)| {
22612 let head = selection.head().to_offset(buffer);
22613 invalidation_range.start <= head && invalidation_range.end >= head
22614 })
22615 } else {
22616 false
22617 };
22618
22619 if all_selections_inside_invalidation_ranges {
22620 break;
22621 } else {
22622 self.pop();
22623 }
22624 }
22625 }
22626}
22627
22628impl<T> Default for InvalidationStack<T> {
22629 fn default() -> Self {
22630 Self(Default::default())
22631 }
22632}
22633
22634impl<T> Deref for InvalidationStack<T> {
22635 type Target = Vec<T>;
22636
22637 fn deref(&self) -> &Self::Target {
22638 &self.0
22639 }
22640}
22641
22642impl<T> DerefMut for InvalidationStack<T> {
22643 fn deref_mut(&mut self) -> &mut Self::Target {
22644 &mut self.0
22645 }
22646}
22647
22648impl InvalidationRegion for SnippetState {
22649 fn ranges(&self) -> &[Range<Anchor>] {
22650 &self.ranges[self.active_index]
22651 }
22652}
22653
22654fn inline_completion_edit_text(
22655 current_snapshot: &BufferSnapshot,
22656 edits: &[(Range<Anchor>, String)],
22657 edit_preview: &EditPreview,
22658 include_deletions: bool,
22659 cx: &App,
22660) -> HighlightedText {
22661 let edits = edits
22662 .iter()
22663 .map(|(anchor, text)| {
22664 (
22665 anchor.start.text_anchor..anchor.end.text_anchor,
22666 text.clone(),
22667 )
22668 })
22669 .collect::<Vec<_>>();
22670
22671 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
22672}
22673
22674pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
22675 match severity {
22676 lsp::DiagnosticSeverity::ERROR => colors.error,
22677 lsp::DiagnosticSeverity::WARNING => colors.warning,
22678 lsp::DiagnosticSeverity::INFORMATION => colors.info,
22679 lsp::DiagnosticSeverity::HINT => colors.info,
22680 _ => colors.ignored,
22681 }
22682}
22683
22684pub fn styled_runs_for_code_label<'a>(
22685 label: &'a CodeLabel,
22686 syntax_theme: &'a theme::SyntaxTheme,
22687) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
22688 let fade_out = HighlightStyle {
22689 fade_out: Some(0.35),
22690 ..Default::default()
22691 };
22692
22693 let mut prev_end = label.filter_range.end;
22694 label
22695 .runs
22696 .iter()
22697 .enumerate()
22698 .flat_map(move |(ix, (range, highlight_id))| {
22699 let style = if let Some(style) = highlight_id.style(syntax_theme) {
22700 style
22701 } else {
22702 return Default::default();
22703 };
22704 let mut muted_style = style;
22705 muted_style.highlight(fade_out);
22706
22707 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
22708 if range.start >= label.filter_range.end {
22709 if range.start > prev_end {
22710 runs.push((prev_end..range.start, fade_out));
22711 }
22712 runs.push((range.clone(), muted_style));
22713 } else if range.end <= label.filter_range.end {
22714 runs.push((range.clone(), style));
22715 } else {
22716 runs.push((range.start..label.filter_range.end, style));
22717 runs.push((label.filter_range.end..range.end, muted_style));
22718 }
22719 prev_end = cmp::max(prev_end, range.end);
22720
22721 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
22722 runs.push((prev_end..label.text.len(), fade_out));
22723 }
22724
22725 runs
22726 })
22727}
22728
22729pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
22730 let mut prev_index = 0;
22731 let mut prev_codepoint: Option<char> = None;
22732 text.char_indices()
22733 .chain([(text.len(), '\0')])
22734 .filter_map(move |(index, codepoint)| {
22735 let prev_codepoint = prev_codepoint.replace(codepoint)?;
22736 let is_boundary = index == text.len()
22737 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
22738 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
22739 if is_boundary {
22740 let chunk = &text[prev_index..index];
22741 prev_index = index;
22742 Some(chunk)
22743 } else {
22744 None
22745 }
22746 })
22747}
22748
22749pub trait RangeToAnchorExt: Sized {
22750 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
22751
22752 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
22753 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
22754 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
22755 }
22756}
22757
22758impl<T: ToOffset> RangeToAnchorExt for Range<T> {
22759 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
22760 let start_offset = self.start.to_offset(snapshot);
22761 let end_offset = self.end.to_offset(snapshot);
22762 if start_offset == end_offset {
22763 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
22764 } else {
22765 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
22766 }
22767 }
22768}
22769
22770pub trait RowExt {
22771 fn as_f32(&self) -> f32;
22772
22773 fn next_row(&self) -> Self;
22774
22775 fn previous_row(&self) -> Self;
22776
22777 fn minus(&self, other: Self) -> u32;
22778}
22779
22780impl RowExt for DisplayRow {
22781 fn as_f32(&self) -> f32 {
22782 self.0 as f32
22783 }
22784
22785 fn next_row(&self) -> Self {
22786 Self(self.0 + 1)
22787 }
22788
22789 fn previous_row(&self) -> Self {
22790 Self(self.0.saturating_sub(1))
22791 }
22792
22793 fn minus(&self, other: Self) -> u32 {
22794 self.0 - other.0
22795 }
22796}
22797
22798impl RowExt for MultiBufferRow {
22799 fn as_f32(&self) -> f32 {
22800 self.0 as f32
22801 }
22802
22803 fn next_row(&self) -> Self {
22804 Self(self.0 + 1)
22805 }
22806
22807 fn previous_row(&self) -> Self {
22808 Self(self.0.saturating_sub(1))
22809 }
22810
22811 fn minus(&self, other: Self) -> u32 {
22812 self.0 - other.0
22813 }
22814}
22815
22816trait RowRangeExt {
22817 type Row;
22818
22819 fn len(&self) -> usize;
22820
22821 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
22822}
22823
22824impl RowRangeExt for Range<MultiBufferRow> {
22825 type Row = MultiBufferRow;
22826
22827 fn len(&self) -> usize {
22828 (self.end.0 - self.start.0) as usize
22829 }
22830
22831 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
22832 (self.start.0..self.end.0).map(MultiBufferRow)
22833 }
22834}
22835
22836impl RowRangeExt for Range<DisplayRow> {
22837 type Row = DisplayRow;
22838
22839 fn len(&self) -> usize {
22840 (self.end.0 - self.start.0) as usize
22841 }
22842
22843 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
22844 (self.start.0..self.end.0).map(DisplayRow)
22845 }
22846}
22847
22848/// If select range has more than one line, we
22849/// just point the cursor to range.start.
22850fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
22851 if range.start.row == range.end.row {
22852 range
22853 } else {
22854 range.start..range.start
22855 }
22856}
22857pub struct KillRing(ClipboardItem);
22858impl Global for KillRing {}
22859
22860const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
22861
22862enum BreakpointPromptEditAction {
22863 Log,
22864 Condition,
22865 HitCondition,
22866}
22867
22868struct BreakpointPromptEditor {
22869 pub(crate) prompt: Entity<Editor>,
22870 editor: WeakEntity<Editor>,
22871 breakpoint_anchor: Anchor,
22872 breakpoint: Breakpoint,
22873 edit_action: BreakpointPromptEditAction,
22874 block_ids: HashSet<CustomBlockId>,
22875 editor_margins: Arc<Mutex<EditorMargins>>,
22876 _subscriptions: Vec<Subscription>,
22877}
22878
22879impl BreakpointPromptEditor {
22880 const MAX_LINES: u8 = 4;
22881
22882 fn new(
22883 editor: WeakEntity<Editor>,
22884 breakpoint_anchor: Anchor,
22885 breakpoint: Breakpoint,
22886 edit_action: BreakpointPromptEditAction,
22887 window: &mut Window,
22888 cx: &mut Context<Self>,
22889 ) -> Self {
22890 let base_text = match edit_action {
22891 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
22892 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
22893 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
22894 }
22895 .map(|msg| msg.to_string())
22896 .unwrap_or_default();
22897
22898 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
22899 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
22900
22901 let prompt = cx.new(|cx| {
22902 let mut prompt = Editor::new(
22903 EditorMode::AutoHeight {
22904 min_lines: 1,
22905 max_lines: Some(Self::MAX_LINES as usize),
22906 },
22907 buffer,
22908 None,
22909 window,
22910 cx,
22911 );
22912 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
22913 prompt.set_show_cursor_when_unfocused(false, cx);
22914 prompt.set_placeholder_text(
22915 match edit_action {
22916 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
22917 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
22918 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
22919 },
22920 cx,
22921 );
22922
22923 prompt
22924 });
22925
22926 Self {
22927 prompt,
22928 editor,
22929 breakpoint_anchor,
22930 breakpoint,
22931 edit_action,
22932 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
22933 block_ids: Default::default(),
22934 _subscriptions: vec![],
22935 }
22936 }
22937
22938 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
22939 self.block_ids.extend(block_ids)
22940 }
22941
22942 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
22943 if let Some(editor) = self.editor.upgrade() {
22944 let message = self
22945 .prompt
22946 .read(cx)
22947 .buffer
22948 .read(cx)
22949 .as_singleton()
22950 .expect("A multi buffer in breakpoint prompt isn't possible")
22951 .read(cx)
22952 .as_rope()
22953 .to_string();
22954
22955 editor.update(cx, |editor, cx| {
22956 editor.edit_breakpoint_at_anchor(
22957 self.breakpoint_anchor,
22958 self.breakpoint.clone(),
22959 match self.edit_action {
22960 BreakpointPromptEditAction::Log => {
22961 BreakpointEditAction::EditLogMessage(message.into())
22962 }
22963 BreakpointPromptEditAction::Condition => {
22964 BreakpointEditAction::EditCondition(message.into())
22965 }
22966 BreakpointPromptEditAction::HitCondition => {
22967 BreakpointEditAction::EditHitCondition(message.into())
22968 }
22969 },
22970 cx,
22971 );
22972
22973 editor.remove_blocks(self.block_ids.clone(), None, cx);
22974 cx.focus_self(window);
22975 });
22976 }
22977 }
22978
22979 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
22980 self.editor
22981 .update(cx, |editor, cx| {
22982 editor.remove_blocks(self.block_ids.clone(), None, cx);
22983 window.focus(&editor.focus_handle);
22984 })
22985 .log_err();
22986 }
22987
22988 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
22989 let settings = ThemeSettings::get_global(cx);
22990 let text_style = TextStyle {
22991 color: if self.prompt.read(cx).read_only(cx) {
22992 cx.theme().colors().text_disabled
22993 } else {
22994 cx.theme().colors().text
22995 },
22996 font_family: settings.buffer_font.family.clone(),
22997 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22998 font_size: settings.buffer_font_size(cx).into(),
22999 font_weight: settings.buffer_font.weight,
23000 line_height: relative(settings.buffer_line_height.value()),
23001 ..Default::default()
23002 };
23003 EditorElement::new(
23004 &self.prompt,
23005 EditorStyle {
23006 background: cx.theme().colors().editor_background,
23007 local_player: cx.theme().players().local(),
23008 text: text_style,
23009 ..Default::default()
23010 },
23011 )
23012 }
23013}
23014
23015impl Render for BreakpointPromptEditor {
23016 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23017 let editor_margins = *self.editor_margins.lock();
23018 let gutter_dimensions = editor_margins.gutter;
23019 h_flex()
23020 .key_context("Editor")
23021 .bg(cx.theme().colors().editor_background)
23022 .border_y_1()
23023 .border_color(cx.theme().status().info_border)
23024 .size_full()
23025 .py(window.line_height() / 2.5)
23026 .on_action(cx.listener(Self::confirm))
23027 .on_action(cx.listener(Self::cancel))
23028 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23029 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23030 }
23031}
23032
23033impl Focusable for BreakpointPromptEditor {
23034 fn focus_handle(&self, cx: &App) -> FocusHandle {
23035 self.prompt.focus_handle(cx)
23036 }
23037}
23038
23039fn all_edits_insertions_or_deletions(
23040 edits: &Vec<(Range<Anchor>, String)>,
23041 snapshot: &MultiBufferSnapshot,
23042) -> bool {
23043 let mut all_insertions = true;
23044 let mut all_deletions = true;
23045
23046 for (range, new_text) in edits.iter() {
23047 let range_is_empty = range.to_offset(&snapshot).is_empty();
23048 let text_is_empty = new_text.is_empty();
23049
23050 if range_is_empty != text_is_empty {
23051 if range_is_empty {
23052 all_deletions = false;
23053 } else {
23054 all_insertions = false;
23055 }
23056 } else {
23057 return false;
23058 }
23059
23060 if !all_insertions && !all_deletions {
23061 return false;
23062 }
23063 }
23064 all_insertions || all_deletions
23065}
23066
23067struct MissingEditPredictionKeybindingTooltip;
23068
23069impl Render for MissingEditPredictionKeybindingTooltip {
23070 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23071 ui::tooltip_container(window, cx, |container, _, cx| {
23072 container
23073 .flex_shrink_0()
23074 .max_w_80()
23075 .min_h(rems_from_px(124.))
23076 .justify_between()
23077 .child(
23078 v_flex()
23079 .flex_1()
23080 .text_ui_sm(cx)
23081 .child(Label::new("Conflict with Accept Keybinding"))
23082 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23083 )
23084 .child(
23085 h_flex()
23086 .pb_1()
23087 .gap_1()
23088 .items_end()
23089 .w_full()
23090 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23091 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23092 }))
23093 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23094 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23095 })),
23096 )
23097 })
23098 }
23099}
23100
23101#[derive(Debug, Clone, Copy, PartialEq)]
23102pub struct LineHighlight {
23103 pub background: Background,
23104 pub border: Option<gpui::Hsla>,
23105 pub include_gutter: bool,
23106 pub type_id: Option<TypeId>,
23107}
23108
23109struct LineManipulationResult {
23110 pub new_text: String,
23111 pub line_count_before: usize,
23112 pub line_count_after: usize,
23113}
23114
23115fn render_diff_hunk_controls(
23116 row: u32,
23117 status: &DiffHunkStatus,
23118 hunk_range: Range<Anchor>,
23119 is_created_file: bool,
23120 line_height: Pixels,
23121 editor: &Entity<Editor>,
23122 _window: &mut Window,
23123 cx: &mut App,
23124) -> AnyElement {
23125 h_flex()
23126 .h(line_height)
23127 .mr_1()
23128 .gap_1()
23129 .px_0p5()
23130 .pb_1()
23131 .border_x_1()
23132 .border_b_1()
23133 .border_color(cx.theme().colors().border_variant)
23134 .rounded_b_lg()
23135 .bg(cx.theme().colors().editor_background)
23136 .gap_1()
23137 .block_mouse_except_scroll()
23138 .shadow_md()
23139 .child(if status.has_secondary_hunk() {
23140 Button::new(("stage", row as u64), "Stage")
23141 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23142 .tooltip({
23143 let focus_handle = editor.focus_handle(cx);
23144 move |window, cx| {
23145 Tooltip::for_action_in(
23146 "Stage Hunk",
23147 &::git::ToggleStaged,
23148 &focus_handle,
23149 window,
23150 cx,
23151 )
23152 }
23153 })
23154 .on_click({
23155 let editor = editor.clone();
23156 move |_event, _window, cx| {
23157 editor.update(cx, |editor, cx| {
23158 editor.stage_or_unstage_diff_hunks(
23159 true,
23160 vec![hunk_range.start..hunk_range.start],
23161 cx,
23162 );
23163 });
23164 }
23165 })
23166 } else {
23167 Button::new(("unstage", row as u64), "Unstage")
23168 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23169 .tooltip({
23170 let focus_handle = editor.focus_handle(cx);
23171 move |window, cx| {
23172 Tooltip::for_action_in(
23173 "Unstage Hunk",
23174 &::git::ToggleStaged,
23175 &focus_handle,
23176 window,
23177 cx,
23178 )
23179 }
23180 })
23181 .on_click({
23182 let editor = editor.clone();
23183 move |_event, _window, cx| {
23184 editor.update(cx, |editor, cx| {
23185 editor.stage_or_unstage_diff_hunks(
23186 false,
23187 vec![hunk_range.start..hunk_range.start],
23188 cx,
23189 );
23190 });
23191 }
23192 })
23193 })
23194 .child(
23195 Button::new(("restore", row as u64), "Restore")
23196 .tooltip({
23197 let focus_handle = editor.focus_handle(cx);
23198 move |window, cx| {
23199 Tooltip::for_action_in(
23200 "Restore Hunk",
23201 &::git::Restore,
23202 &focus_handle,
23203 window,
23204 cx,
23205 )
23206 }
23207 })
23208 .on_click({
23209 let editor = editor.clone();
23210 move |_event, window, cx| {
23211 editor.update(cx, |editor, cx| {
23212 let snapshot = editor.snapshot(window, cx);
23213 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23214 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23215 });
23216 }
23217 })
23218 .disabled(is_created_file),
23219 )
23220 .when(
23221 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23222 |el| {
23223 el.child(
23224 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23225 .shape(IconButtonShape::Square)
23226 .icon_size(IconSize::Small)
23227 // .disabled(!has_multiple_hunks)
23228 .tooltip({
23229 let focus_handle = editor.focus_handle(cx);
23230 move |window, cx| {
23231 Tooltip::for_action_in(
23232 "Next Hunk",
23233 &GoToHunk,
23234 &focus_handle,
23235 window,
23236 cx,
23237 )
23238 }
23239 })
23240 .on_click({
23241 let editor = editor.clone();
23242 move |_event, window, cx| {
23243 editor.update(cx, |editor, cx| {
23244 let snapshot = editor.snapshot(window, cx);
23245 let position =
23246 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23247 editor.go_to_hunk_before_or_after_position(
23248 &snapshot,
23249 position,
23250 Direction::Next,
23251 window,
23252 cx,
23253 );
23254 editor.expand_selected_diff_hunks(cx);
23255 });
23256 }
23257 }),
23258 )
23259 .child(
23260 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23261 .shape(IconButtonShape::Square)
23262 .icon_size(IconSize::Small)
23263 // .disabled(!has_multiple_hunks)
23264 .tooltip({
23265 let focus_handle = editor.focus_handle(cx);
23266 move |window, cx| {
23267 Tooltip::for_action_in(
23268 "Previous Hunk",
23269 &GoToPreviousHunk,
23270 &focus_handle,
23271 window,
23272 cx,
23273 )
23274 }
23275 })
23276 .on_click({
23277 let editor = editor.clone();
23278 move |_event, window, cx| {
23279 editor.update(cx, |editor, cx| {
23280 let snapshot = editor.snapshot(window, cx);
23281 let point =
23282 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23283 editor.go_to_hunk_before_or_after_position(
23284 &snapshot,
23285 point,
23286 Direction::Prev,
23287 window,
23288 cx,
23289 );
23290 editor.expand_selected_diff_hunks(cx);
23291 });
23292 }
23293 }),
23294 )
23295 },
23296 )
23297 .into_any_element()
23298}