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
1218struct CharacterDimensions {
1219 em_width: Pixels,
1220 em_advance: Pixels,
1221 line_height: Pixels,
1222}
1223
1224#[derive(Debug)]
1225pub struct RemoteSelection {
1226 pub replica_id: ReplicaId,
1227 pub selection: Selection<Anchor>,
1228 pub cursor_shape: CursorShape,
1229 pub collaborator_id: CollaboratorId,
1230 pub line_mode: bool,
1231 pub user_name: Option<SharedString>,
1232 pub color: PlayerColor,
1233}
1234
1235#[derive(Clone, Debug)]
1236struct SelectionHistoryEntry {
1237 selections: Arc<[Selection<Anchor>]>,
1238 select_next_state: Option<SelectNextState>,
1239 select_prev_state: Option<SelectNextState>,
1240 add_selections_state: Option<AddSelectionsState>,
1241}
1242
1243#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1244enum SelectionHistoryMode {
1245 Normal,
1246 Undoing,
1247 Redoing,
1248 Skipping,
1249}
1250
1251#[derive(Clone, PartialEq, Eq, Hash)]
1252struct HoveredCursor {
1253 replica_id: u16,
1254 selection_id: usize,
1255}
1256
1257impl Default for SelectionHistoryMode {
1258 fn default() -> Self {
1259 Self::Normal
1260 }
1261}
1262
1263#[derive(Debug)]
1264pub struct SelectionEffects {
1265 nav_history: Option<bool>,
1266 completions: bool,
1267 scroll: Option<Autoscroll>,
1268}
1269
1270impl Default for SelectionEffects {
1271 fn default() -> Self {
1272 Self {
1273 nav_history: None,
1274 completions: true,
1275 scroll: Some(Autoscroll::fit()),
1276 }
1277 }
1278}
1279impl SelectionEffects {
1280 pub fn scroll(scroll: Autoscroll) -> Self {
1281 Self {
1282 scroll: Some(scroll),
1283 ..Default::default()
1284 }
1285 }
1286
1287 pub fn no_scroll() -> Self {
1288 Self {
1289 scroll: None,
1290 ..Default::default()
1291 }
1292 }
1293
1294 pub fn completions(self, completions: bool) -> Self {
1295 Self {
1296 completions,
1297 ..self
1298 }
1299 }
1300
1301 pub fn nav_history(self, nav_history: bool) -> Self {
1302 Self {
1303 nav_history: Some(nav_history),
1304 ..self
1305 }
1306 }
1307}
1308
1309struct DeferredSelectionEffectsState {
1310 changed: bool,
1311 effects: SelectionEffects,
1312 old_cursor_position: Anchor,
1313 history_entry: SelectionHistoryEntry,
1314}
1315
1316#[derive(Default)]
1317struct SelectionHistory {
1318 #[allow(clippy::type_complexity)]
1319 selections_by_transaction:
1320 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1321 mode: SelectionHistoryMode,
1322 undo_stack: VecDeque<SelectionHistoryEntry>,
1323 redo_stack: VecDeque<SelectionHistoryEntry>,
1324}
1325
1326impl SelectionHistory {
1327 #[track_caller]
1328 fn insert_transaction(
1329 &mut self,
1330 transaction_id: TransactionId,
1331 selections: Arc<[Selection<Anchor>]>,
1332 ) {
1333 if selections.is_empty() {
1334 log::error!(
1335 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1336 std::panic::Location::caller()
1337 );
1338 return;
1339 }
1340 self.selections_by_transaction
1341 .insert(transaction_id, (selections, None));
1342 }
1343
1344 #[allow(clippy::type_complexity)]
1345 fn transaction(
1346 &self,
1347 transaction_id: TransactionId,
1348 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1349 self.selections_by_transaction.get(&transaction_id)
1350 }
1351
1352 #[allow(clippy::type_complexity)]
1353 fn transaction_mut(
1354 &mut self,
1355 transaction_id: TransactionId,
1356 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1357 self.selections_by_transaction.get_mut(&transaction_id)
1358 }
1359
1360 fn push(&mut self, entry: SelectionHistoryEntry) {
1361 if !entry.selections.is_empty() {
1362 match self.mode {
1363 SelectionHistoryMode::Normal => {
1364 self.push_undo(entry);
1365 self.redo_stack.clear();
1366 }
1367 SelectionHistoryMode::Undoing => self.push_redo(entry),
1368 SelectionHistoryMode::Redoing => self.push_undo(entry),
1369 SelectionHistoryMode::Skipping => {}
1370 }
1371 }
1372 }
1373
1374 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1375 if self
1376 .undo_stack
1377 .back()
1378 .map_or(true, |e| e.selections != entry.selections)
1379 {
1380 self.undo_stack.push_back(entry);
1381 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1382 self.undo_stack.pop_front();
1383 }
1384 }
1385 }
1386
1387 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1388 if self
1389 .redo_stack
1390 .back()
1391 .map_or(true, |e| e.selections != entry.selections)
1392 {
1393 self.redo_stack.push_back(entry);
1394 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1395 self.redo_stack.pop_front();
1396 }
1397 }
1398 }
1399}
1400
1401#[derive(Clone, Copy)]
1402pub struct RowHighlightOptions {
1403 pub autoscroll: bool,
1404 pub include_gutter: bool,
1405}
1406
1407impl Default for RowHighlightOptions {
1408 fn default() -> Self {
1409 Self {
1410 autoscroll: Default::default(),
1411 include_gutter: true,
1412 }
1413 }
1414}
1415
1416struct RowHighlight {
1417 index: usize,
1418 range: Range<Anchor>,
1419 color: Hsla,
1420 options: RowHighlightOptions,
1421 type_id: TypeId,
1422}
1423
1424#[derive(Clone, Debug)]
1425struct AddSelectionsState {
1426 groups: Vec<AddSelectionsGroup>,
1427}
1428
1429#[derive(Clone, Debug)]
1430struct AddSelectionsGroup {
1431 above: bool,
1432 stack: Vec<usize>,
1433}
1434
1435#[derive(Clone)]
1436struct SelectNextState {
1437 query: AhoCorasick,
1438 wordwise: bool,
1439 done: bool,
1440}
1441
1442impl std::fmt::Debug for SelectNextState {
1443 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1444 f.debug_struct(std::any::type_name::<Self>())
1445 .field("wordwise", &self.wordwise)
1446 .field("done", &self.done)
1447 .finish()
1448 }
1449}
1450
1451#[derive(Debug)]
1452struct AutocloseRegion {
1453 selection_id: usize,
1454 range: Range<Anchor>,
1455 pair: BracketPair,
1456}
1457
1458#[derive(Debug)]
1459struct SnippetState {
1460 ranges: Vec<Vec<Range<Anchor>>>,
1461 active_index: usize,
1462 choices: Vec<Option<Vec<String>>>,
1463}
1464
1465#[doc(hidden)]
1466pub struct RenameState {
1467 pub range: Range<Anchor>,
1468 pub old_name: Arc<str>,
1469 pub editor: Entity<Editor>,
1470 block_id: CustomBlockId,
1471}
1472
1473struct InvalidationStack<T>(Vec<T>);
1474
1475struct RegisteredInlineCompletionProvider {
1476 provider: Arc<dyn InlineCompletionProviderHandle>,
1477 _subscription: Subscription,
1478}
1479
1480#[derive(Debug, PartialEq, Eq)]
1481pub struct ActiveDiagnosticGroup {
1482 pub active_range: Range<Anchor>,
1483 pub active_message: String,
1484 pub group_id: usize,
1485 pub blocks: HashSet<CustomBlockId>,
1486}
1487
1488#[derive(Debug, PartialEq, Eq)]
1489
1490pub(crate) enum ActiveDiagnostic {
1491 None,
1492 All,
1493 Group(ActiveDiagnosticGroup),
1494}
1495
1496#[derive(Serialize, Deserialize, Clone, Debug)]
1497pub struct ClipboardSelection {
1498 /// The number of bytes in this selection.
1499 pub len: usize,
1500 /// Whether this was a full-line selection.
1501 pub is_entire_line: bool,
1502 /// The indentation of the first line when this content was originally copied.
1503 pub first_line_indent: u32,
1504}
1505
1506// selections, scroll behavior, was newest selection reversed
1507type SelectSyntaxNodeHistoryState = (
1508 Box<[Selection<usize>]>,
1509 SelectSyntaxNodeScrollBehavior,
1510 bool,
1511);
1512
1513#[derive(Default)]
1514struct SelectSyntaxNodeHistory {
1515 stack: Vec<SelectSyntaxNodeHistoryState>,
1516 // disable temporarily to allow changing selections without losing the stack
1517 pub disable_clearing: bool,
1518}
1519
1520impl SelectSyntaxNodeHistory {
1521 pub fn try_clear(&mut self) {
1522 if !self.disable_clearing {
1523 self.stack.clear();
1524 }
1525 }
1526
1527 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1528 self.stack.push(selection);
1529 }
1530
1531 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1532 self.stack.pop()
1533 }
1534}
1535
1536enum SelectSyntaxNodeScrollBehavior {
1537 CursorTop,
1538 FitSelection,
1539 CursorBottom,
1540}
1541
1542#[derive(Debug)]
1543pub(crate) struct NavigationData {
1544 cursor_anchor: Anchor,
1545 cursor_position: Point,
1546 scroll_anchor: ScrollAnchor,
1547 scroll_top_row: u32,
1548}
1549
1550#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1551pub enum GotoDefinitionKind {
1552 Symbol,
1553 Declaration,
1554 Type,
1555 Implementation,
1556}
1557
1558#[derive(Debug, Clone)]
1559enum InlayHintRefreshReason {
1560 ModifiersChanged(bool),
1561 Toggle(bool),
1562 SettingsChange(InlayHintSettings),
1563 NewLinesShown,
1564 BufferEdited(HashSet<Arc<Language>>),
1565 RefreshRequested,
1566 ExcerptsRemoved(Vec<ExcerptId>),
1567}
1568
1569impl InlayHintRefreshReason {
1570 fn description(&self) -> &'static str {
1571 match self {
1572 Self::ModifiersChanged(_) => "modifiers changed",
1573 Self::Toggle(_) => "toggle",
1574 Self::SettingsChange(_) => "settings change",
1575 Self::NewLinesShown => "new lines shown",
1576 Self::BufferEdited(_) => "buffer edited",
1577 Self::RefreshRequested => "refresh requested",
1578 Self::ExcerptsRemoved(_) => "excerpts removed",
1579 }
1580 }
1581}
1582
1583pub enum FormatTarget {
1584 Buffers(HashSet<Entity<Buffer>>),
1585 Ranges(Vec<Range<MultiBufferPoint>>),
1586}
1587
1588pub(crate) struct FocusedBlock {
1589 id: BlockId,
1590 focus_handle: WeakFocusHandle,
1591}
1592
1593#[derive(Clone)]
1594enum JumpData {
1595 MultiBufferRow {
1596 row: MultiBufferRow,
1597 line_offset_from_top: u32,
1598 },
1599 MultiBufferPoint {
1600 excerpt_id: ExcerptId,
1601 position: Point,
1602 anchor: text::Anchor,
1603 line_offset_from_top: u32,
1604 },
1605}
1606
1607pub enum MultibufferSelectionMode {
1608 First,
1609 All,
1610}
1611
1612#[derive(Clone, Copy, Debug, Default)]
1613pub struct RewrapOptions {
1614 pub override_language_settings: bool,
1615 pub preserve_existing_whitespace: bool,
1616}
1617
1618impl Editor {
1619 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1620 let buffer = cx.new(|cx| Buffer::local("", cx));
1621 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1622 Self::new(
1623 EditorMode::SingleLine { auto_width: false },
1624 buffer,
1625 None,
1626 window,
1627 cx,
1628 )
1629 }
1630
1631 pub fn multi_line(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(EditorMode::full(), buffer, None, window, cx)
1635 }
1636
1637 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1638 let buffer = cx.new(|cx| Buffer::local("", cx));
1639 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1640 Self::new(
1641 EditorMode::SingleLine { auto_width: true },
1642 buffer,
1643 None,
1644 window,
1645 cx,
1646 )
1647 }
1648
1649 pub fn auto_height(
1650 min_lines: usize,
1651 max_lines: usize,
1652 window: &mut Window,
1653 cx: &mut Context<Self>,
1654 ) -> Self {
1655 let buffer = cx.new(|cx| Buffer::local("", cx));
1656 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1657 Self::new(
1658 EditorMode::AutoHeight {
1659 min_lines,
1660 max_lines: Some(max_lines),
1661 },
1662 buffer,
1663 None,
1664 window,
1665 cx,
1666 )
1667 }
1668
1669 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1670 /// The editor grows as tall as needed to fit its content.
1671 pub fn auto_height_unbounded(
1672 min_lines: usize,
1673 window: &mut Window,
1674 cx: &mut Context<Self>,
1675 ) -> Self {
1676 let buffer = cx.new(|cx| Buffer::local("", cx));
1677 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1678 Self::new(
1679 EditorMode::AutoHeight {
1680 min_lines,
1681 max_lines: None,
1682 },
1683 buffer,
1684 None,
1685 window,
1686 cx,
1687 )
1688 }
1689
1690 pub fn for_buffer(
1691 buffer: Entity<Buffer>,
1692 project: Option<Entity<Project>>,
1693 window: &mut Window,
1694 cx: &mut Context<Self>,
1695 ) -> Self {
1696 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1697 Self::new(EditorMode::full(), buffer, project, window, cx)
1698 }
1699
1700 pub fn for_multibuffer(
1701 buffer: Entity<MultiBuffer>,
1702 project: Option<Entity<Project>>,
1703 window: &mut Window,
1704 cx: &mut Context<Self>,
1705 ) -> Self {
1706 Self::new(EditorMode::full(), buffer, project, window, cx)
1707 }
1708
1709 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1710 let mut clone = Self::new(
1711 self.mode.clone(),
1712 self.buffer.clone(),
1713 self.project.clone(),
1714 window,
1715 cx,
1716 );
1717 self.display_map.update(cx, |display_map, cx| {
1718 let snapshot = display_map.snapshot(cx);
1719 clone.display_map.update(cx, |display_map, cx| {
1720 display_map.set_state(&snapshot, cx);
1721 });
1722 });
1723 clone.folds_did_change(cx);
1724 clone.selections.clone_state(&self.selections);
1725 clone.scroll_manager.clone_state(&self.scroll_manager);
1726 clone.searchable = self.searchable;
1727 clone.read_only = self.read_only;
1728 clone
1729 }
1730
1731 pub fn new(
1732 mode: EditorMode,
1733 buffer: Entity<MultiBuffer>,
1734 project: Option<Entity<Project>>,
1735 window: &mut Window,
1736 cx: &mut Context<Self>,
1737 ) -> Self {
1738 Editor::new_internal(mode, buffer, project, None, window, cx)
1739 }
1740
1741 fn new_internal(
1742 mode: EditorMode,
1743 buffer: Entity<MultiBuffer>,
1744 project: Option<Entity<Project>>,
1745 display_map: Option<Entity<DisplayMap>>,
1746 window: &mut Window,
1747 cx: &mut Context<Self>,
1748 ) -> Self {
1749 debug_assert!(
1750 display_map.is_none() || mode.is_minimap(),
1751 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1752 );
1753
1754 let full_mode = mode.is_full();
1755 let diagnostics_max_severity = if full_mode {
1756 EditorSettings::get_global(cx)
1757 .diagnostics_max_severity
1758 .unwrap_or(DiagnosticSeverity::Hint)
1759 } else {
1760 DiagnosticSeverity::Off
1761 };
1762 let style = window.text_style();
1763 let font_size = style.font_size.to_pixels(window.rem_size());
1764 let editor = cx.entity().downgrade();
1765 let fold_placeholder = FoldPlaceholder {
1766 constrain_width: true,
1767 render: Arc::new(move |fold_id, fold_range, cx| {
1768 let editor = editor.clone();
1769 div()
1770 .id(fold_id)
1771 .bg(cx.theme().colors().ghost_element_background)
1772 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1773 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1774 .rounded_xs()
1775 .size_full()
1776 .cursor_pointer()
1777 .child("⋯")
1778 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1779 .on_click(move |_, _window, cx| {
1780 editor
1781 .update(cx, |editor, cx| {
1782 editor.unfold_ranges(
1783 &[fold_range.start..fold_range.end],
1784 true,
1785 false,
1786 cx,
1787 );
1788 cx.stop_propagation();
1789 })
1790 .ok();
1791 })
1792 .into_any()
1793 }),
1794 merge_adjacent: true,
1795 ..FoldPlaceholder::default()
1796 };
1797 let display_map = display_map.unwrap_or_else(|| {
1798 cx.new(|cx| {
1799 DisplayMap::new(
1800 buffer.clone(),
1801 style.font(),
1802 font_size,
1803 None,
1804 FILE_HEADER_HEIGHT,
1805 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1806 fold_placeholder,
1807 diagnostics_max_severity,
1808 cx,
1809 )
1810 })
1811 });
1812
1813 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1814
1815 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1816
1817 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1818 .then(|| language_settings::SoftWrap::None);
1819
1820 let mut project_subscriptions = Vec::new();
1821 if mode.is_full() {
1822 if let Some(project) = project.as_ref() {
1823 project_subscriptions.push(cx.subscribe_in(
1824 project,
1825 window,
1826 |editor, _, event, window, cx| match event {
1827 project::Event::RefreshCodeLens => {
1828 // we always query lens with actions, without storing them, always refreshing them
1829 }
1830 project::Event::RefreshInlayHints => {
1831 editor
1832 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1833 }
1834 project::Event::LanguageServerAdded(server_id, ..)
1835 | project::Event::LanguageServerRemoved(server_id) => {
1836 if editor.tasks_update_task.is_none() {
1837 editor.tasks_update_task =
1838 Some(editor.refresh_runnables(window, cx));
1839 }
1840 editor.update_lsp_data(Some(*server_id), None, window, cx);
1841 }
1842 project::Event::SnippetEdit(id, snippet_edits) => {
1843 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1844 let focus_handle = editor.focus_handle(cx);
1845 if focus_handle.is_focused(window) {
1846 let snapshot = buffer.read(cx).snapshot();
1847 for (range, snippet) in snippet_edits {
1848 let editor_range =
1849 language::range_from_lsp(*range).to_offset(&snapshot);
1850 editor
1851 .insert_snippet(
1852 &[editor_range],
1853 snippet.clone(),
1854 window,
1855 cx,
1856 )
1857 .ok();
1858 }
1859 }
1860 }
1861 }
1862 _ => {}
1863 },
1864 ));
1865 if let Some(task_inventory) = project
1866 .read(cx)
1867 .task_store()
1868 .read(cx)
1869 .task_inventory()
1870 .cloned()
1871 {
1872 project_subscriptions.push(cx.observe_in(
1873 &task_inventory,
1874 window,
1875 |editor, _, window, cx| {
1876 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1877 },
1878 ));
1879 };
1880
1881 project_subscriptions.push(cx.subscribe_in(
1882 &project.read(cx).breakpoint_store(),
1883 window,
1884 |editor, _, event, window, cx| match event {
1885 BreakpointStoreEvent::ClearDebugLines => {
1886 editor.clear_row_highlights::<ActiveDebugLine>();
1887 editor.refresh_inline_values(cx);
1888 }
1889 BreakpointStoreEvent::SetDebugLine => {
1890 if editor.go_to_active_debug_line(window, cx) {
1891 cx.stop_propagation();
1892 }
1893
1894 editor.refresh_inline_values(cx);
1895 }
1896 _ => {}
1897 },
1898 ));
1899 let git_store = project.read(cx).git_store().clone();
1900 let project = project.clone();
1901 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1902 match event {
1903 GitStoreEvent::RepositoryUpdated(
1904 _,
1905 RepositoryEvent::Updated {
1906 new_instance: true, ..
1907 },
1908 _,
1909 ) => {
1910 this.load_diff_task = Some(
1911 update_uncommitted_diff_for_buffer(
1912 cx.entity(),
1913 &project,
1914 this.buffer.read(cx).all_buffers(),
1915 this.buffer.clone(),
1916 cx,
1917 )
1918 .shared(),
1919 );
1920 }
1921 _ => {}
1922 }
1923 }));
1924 }
1925 }
1926
1927 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1928
1929 let inlay_hint_settings =
1930 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1931 let focus_handle = cx.focus_handle();
1932 cx.on_focus(&focus_handle, window, Self::handle_focus)
1933 .detach();
1934 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1935 .detach();
1936 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1937 .detach();
1938 cx.on_blur(&focus_handle, window, Self::handle_blur)
1939 .detach();
1940 cx.observe_pending_input(window, Self::observe_pending_input)
1941 .detach();
1942
1943 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1944 Some(false)
1945 } else {
1946 None
1947 };
1948
1949 let breakpoint_store = match (&mode, project.as_ref()) {
1950 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1951 _ => None,
1952 };
1953
1954 let mut code_action_providers = Vec::new();
1955 let mut load_uncommitted_diff = None;
1956 if let Some(project) = project.clone() {
1957 load_uncommitted_diff = Some(
1958 update_uncommitted_diff_for_buffer(
1959 cx.entity(),
1960 &project,
1961 buffer.read(cx).all_buffers(),
1962 buffer.clone(),
1963 cx,
1964 )
1965 .shared(),
1966 );
1967 code_action_providers.push(Rc::new(project) as Rc<_>);
1968 }
1969
1970 let mut editor = Self {
1971 focus_handle,
1972 show_cursor_when_unfocused: false,
1973 last_focused_descendant: None,
1974 buffer: buffer.clone(),
1975 display_map: display_map.clone(),
1976 selections,
1977 scroll_manager: ScrollManager::new(cx),
1978 columnar_selection_state: None,
1979 add_selections_state: None,
1980 select_next_state: None,
1981 select_prev_state: None,
1982 selection_history: SelectionHistory::default(),
1983 defer_selection_effects: false,
1984 deferred_selection_effects_state: None,
1985 autoclose_regions: Vec::new(),
1986 snippet_stack: InvalidationStack::default(),
1987 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1988 ime_transaction: None,
1989 active_diagnostics: ActiveDiagnostic::None,
1990 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1991 inline_diagnostics_update: Task::ready(()),
1992 inline_diagnostics: Vec::new(),
1993 soft_wrap_mode_override,
1994 diagnostics_max_severity,
1995 hard_wrap: None,
1996 completion_provider: project.clone().map(|project| Rc::new(project) as _),
1997 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1998 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1999 project,
2000 blink_manager: blink_manager.clone(),
2001 show_local_selections: true,
2002 show_scrollbars: ScrollbarAxes {
2003 horizontal: full_mode,
2004 vertical: full_mode,
2005 },
2006 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2007 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
2008 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2009 show_gutter: mode.is_full(),
2010 show_line_numbers: None,
2011 use_relative_line_numbers: None,
2012 disable_expand_excerpt_buttons: false,
2013 show_git_diff_gutter: None,
2014 show_code_actions: None,
2015 show_runnables: None,
2016 show_breakpoints: None,
2017 show_wrap_guides: None,
2018 show_indent_guides,
2019 placeholder_text: None,
2020 highlight_order: 0,
2021 highlighted_rows: HashMap::default(),
2022 background_highlights: TreeMap::default(),
2023 gutter_highlights: TreeMap::default(),
2024 scrollbar_marker_state: ScrollbarMarkerState::default(),
2025 active_indent_guides_state: ActiveIndentGuidesState::default(),
2026 nav_history: None,
2027 context_menu: RefCell::new(None),
2028 context_menu_options: None,
2029 mouse_context_menu: None,
2030 completion_tasks: Vec::new(),
2031 inline_blame_popover: None,
2032 inline_blame_popover_show_task: None,
2033 signature_help_state: SignatureHelpState::default(),
2034 auto_signature_help: None,
2035 find_all_references_task_sources: Vec::new(),
2036 next_completion_id: 0,
2037 next_inlay_id: 0,
2038 code_action_providers,
2039 available_code_actions: None,
2040 code_actions_task: None,
2041 quick_selection_highlight_task: None,
2042 debounced_selection_highlight_task: None,
2043 document_highlights_task: None,
2044 linked_editing_range_task: None,
2045 pending_rename: None,
2046 searchable: true,
2047 cursor_shape: EditorSettings::get_global(cx)
2048 .cursor_shape
2049 .unwrap_or_default(),
2050 current_line_highlight: None,
2051 autoindent_mode: Some(AutoindentMode::EachLine),
2052 collapse_matches: false,
2053 workspace: None,
2054 input_enabled: true,
2055 use_modal_editing: mode.is_full(),
2056 read_only: mode.is_minimap(),
2057 use_autoclose: true,
2058 use_auto_surround: true,
2059 auto_replace_emoji_shortcode: false,
2060 jsx_tag_auto_close_enabled_in_any_buffer: false,
2061 leader_id: None,
2062 remote_id: None,
2063 hover_state: HoverState::default(),
2064 pending_mouse_down: None,
2065 hovered_link_state: None,
2066 edit_prediction_provider: None,
2067 active_inline_completion: None,
2068 stale_inline_completion_in_menu: None,
2069 edit_prediction_preview: EditPredictionPreview::Inactive {
2070 released_too_fast: false,
2071 },
2072 inline_diagnostics_enabled: mode.is_full(),
2073 diagnostics_enabled: mode.is_full(),
2074 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2075 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2076
2077 gutter_hovered: false,
2078 pixel_position_of_newest_cursor: None,
2079 last_bounds: None,
2080 last_position_map: None,
2081 expect_bounds_change: None,
2082 gutter_dimensions: GutterDimensions::default(),
2083 style: None,
2084 show_cursor_names: false,
2085 hovered_cursors: HashMap::default(),
2086 next_editor_action_id: EditorActionId::default(),
2087 editor_actions: Rc::default(),
2088 inline_completions_hidden_for_vim_mode: false,
2089 show_inline_completions_override: None,
2090 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
2091 edit_prediction_settings: EditPredictionSettings::Disabled,
2092 edit_prediction_indent_conflict: false,
2093 edit_prediction_requires_modifier_in_indent_conflict: true,
2094 custom_context_menu: None,
2095 show_git_blame_gutter: false,
2096 show_git_blame_inline: false,
2097 show_selection_menu: None,
2098 show_git_blame_inline_delay_task: None,
2099 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2100 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2101 serialize_dirty_buffers: !mode.is_minimap()
2102 && ProjectSettings::get_global(cx)
2103 .session
2104 .restore_unsaved_buffers,
2105 blame: None,
2106 blame_subscription: None,
2107 tasks: BTreeMap::default(),
2108
2109 breakpoint_store,
2110 gutter_breakpoint_indicator: (None, None),
2111 hovered_diff_hunk_row: None,
2112 _subscriptions: vec![
2113 cx.observe(&buffer, Self::on_buffer_changed),
2114 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2115 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2116 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2117 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2118 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2119 cx.observe_window_activation(window, |editor, window, cx| {
2120 let active = window.is_window_active();
2121 editor.blink_manager.update(cx, |blink_manager, cx| {
2122 if active {
2123 blink_manager.enable(cx);
2124 } else {
2125 blink_manager.disable(cx);
2126 }
2127 });
2128 if active {
2129 editor.show_mouse_cursor(cx);
2130 }
2131 }),
2132 ],
2133 tasks_update_task: None,
2134 pull_diagnostics_task: Task::ready(()),
2135 colors: None,
2136 next_color_inlay_id: 0,
2137 linked_edit_ranges: Default::default(),
2138 in_project_search: false,
2139 previous_search_ranges: None,
2140 breadcrumb_header: None,
2141 focused_block: None,
2142 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2143 addons: HashMap::default(),
2144 registered_buffers: HashMap::default(),
2145 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2146 selection_mark_mode: false,
2147 toggle_fold_multiple_buffers: Task::ready(()),
2148 serialize_selections: Task::ready(()),
2149 serialize_folds: Task::ready(()),
2150 text_style_refinement: None,
2151 load_diff_task: load_uncommitted_diff,
2152 temporary_diff_override: false,
2153 mouse_cursor_hidden: false,
2154 minimap: None,
2155 hide_mouse_mode: EditorSettings::get_global(cx)
2156 .hide_mouse
2157 .unwrap_or_default(),
2158 change_list: ChangeList::new(),
2159 mode,
2160 selection_drag_state: SelectionDragState::None,
2161 drag_and_drop_selection_enabled: EditorSettings::get_global(cx).drag_and_drop_selection,
2162 };
2163 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2164 editor
2165 ._subscriptions
2166 .push(cx.observe(breakpoints, |_, _, cx| {
2167 cx.notify();
2168 }));
2169 }
2170 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2171 editor._subscriptions.extend(project_subscriptions);
2172
2173 editor._subscriptions.push(cx.subscribe_in(
2174 &cx.entity(),
2175 window,
2176 |editor, _, e: &EditorEvent, window, cx| match e {
2177 EditorEvent::ScrollPositionChanged { local, .. } => {
2178 if *local {
2179 let new_anchor = editor.scroll_manager.anchor();
2180 let snapshot = editor.snapshot(window, cx);
2181 editor.update_restoration_data(cx, move |data| {
2182 data.scroll_position = (
2183 new_anchor.top_row(&snapshot.buffer_snapshot),
2184 new_anchor.offset,
2185 );
2186 });
2187 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2188 editor.inline_blame_popover.take();
2189 }
2190 }
2191 EditorEvent::Edited { .. } => {
2192 if !vim_enabled(cx) {
2193 let (map, selections) = editor.selections.all_adjusted_display(cx);
2194 let pop_state = editor
2195 .change_list
2196 .last()
2197 .map(|previous| {
2198 previous.len() == selections.len()
2199 && previous.iter().enumerate().all(|(ix, p)| {
2200 p.to_display_point(&map).row()
2201 == selections[ix].head().row()
2202 })
2203 })
2204 .unwrap_or(false);
2205 let new_positions = selections
2206 .into_iter()
2207 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2208 .collect();
2209 editor
2210 .change_list
2211 .push_to_change_list(pop_state, new_positions);
2212 }
2213 }
2214 _ => (),
2215 },
2216 ));
2217
2218 if let Some(dap_store) = editor
2219 .project
2220 .as_ref()
2221 .map(|project| project.read(cx).dap_store())
2222 {
2223 let weak_editor = cx.weak_entity();
2224
2225 editor
2226 ._subscriptions
2227 .push(
2228 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2229 let session_entity = cx.entity();
2230 weak_editor
2231 .update(cx, |editor, cx| {
2232 editor._subscriptions.push(
2233 cx.subscribe(&session_entity, Self::on_debug_session_event),
2234 );
2235 })
2236 .ok();
2237 }),
2238 );
2239
2240 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2241 editor
2242 ._subscriptions
2243 .push(cx.subscribe(&session, Self::on_debug_session_event));
2244 }
2245 }
2246
2247 // skip adding the initial selection to selection history
2248 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2249 editor.end_selection(window, cx);
2250 editor.selection_history.mode = SelectionHistoryMode::Normal;
2251
2252 editor.scroll_manager.show_scrollbars(window, cx);
2253 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2254
2255 if full_mode {
2256 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2257 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2258
2259 if editor.git_blame_inline_enabled {
2260 editor.start_git_blame_inline(false, window, cx);
2261 }
2262
2263 editor.go_to_active_debug_line(window, cx);
2264
2265 if let Some(buffer) = buffer.read(cx).as_singleton() {
2266 if let Some(project) = editor.project.as_ref() {
2267 let handle = project.update(cx, |project, cx| {
2268 project.register_buffer_with_language_servers(&buffer, cx)
2269 });
2270 editor
2271 .registered_buffers
2272 .insert(buffer.read(cx).remote_id(), handle);
2273 }
2274 }
2275
2276 editor.minimap =
2277 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2278 editor.colors = Some(LspColorData::new(cx));
2279 editor.update_lsp_data(None, None, window, cx);
2280 }
2281
2282 editor.report_editor_event("Editor Opened", None, cx);
2283 editor
2284 }
2285
2286 pub fn deploy_mouse_context_menu(
2287 &mut self,
2288 position: gpui::Point<Pixels>,
2289 context_menu: Entity<ContextMenu>,
2290 window: &mut Window,
2291 cx: &mut Context<Self>,
2292 ) {
2293 self.mouse_context_menu = Some(MouseContextMenu::new(
2294 self,
2295 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2296 context_menu,
2297 window,
2298 cx,
2299 ));
2300 }
2301
2302 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2303 self.mouse_context_menu
2304 .as_ref()
2305 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2306 }
2307
2308 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2309 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2310 }
2311
2312 fn key_context_internal(
2313 &self,
2314 has_active_edit_prediction: bool,
2315 window: &Window,
2316 cx: &App,
2317 ) -> KeyContext {
2318 let mut key_context = KeyContext::new_with_defaults();
2319 key_context.add("Editor");
2320 let mode = match self.mode {
2321 EditorMode::SingleLine { .. } => "single_line",
2322 EditorMode::AutoHeight { .. } => "auto_height",
2323 EditorMode::Minimap { .. } => "minimap",
2324 EditorMode::Full { .. } => "full",
2325 };
2326
2327 if EditorSettings::jupyter_enabled(cx) {
2328 key_context.add("jupyter");
2329 }
2330
2331 key_context.set("mode", mode);
2332 if self.pending_rename.is_some() {
2333 key_context.add("renaming");
2334 }
2335
2336 match self.context_menu.borrow().as_ref() {
2337 Some(CodeContextMenu::Completions(_)) => {
2338 key_context.add("menu");
2339 key_context.add("showing_completions");
2340 }
2341 Some(CodeContextMenu::CodeActions(_)) => {
2342 key_context.add("menu");
2343 key_context.add("showing_code_actions")
2344 }
2345 None => {}
2346 }
2347
2348 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2349 if !self.focus_handle(cx).contains_focused(window, cx)
2350 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2351 {
2352 for addon in self.addons.values() {
2353 addon.extend_key_context(&mut key_context, cx)
2354 }
2355 }
2356
2357 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2358 if let Some(extension) = singleton_buffer
2359 .read(cx)
2360 .file()
2361 .and_then(|file| file.path().extension()?.to_str())
2362 {
2363 key_context.set("extension", extension.to_string());
2364 }
2365 } else {
2366 key_context.add("multibuffer");
2367 }
2368
2369 if has_active_edit_prediction {
2370 if self.edit_prediction_in_conflict() {
2371 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2372 } else {
2373 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2374 key_context.add("copilot_suggestion");
2375 }
2376 }
2377
2378 if self.selection_mark_mode {
2379 key_context.add("selection_mode");
2380 }
2381
2382 key_context
2383 }
2384
2385 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2386 if self.mouse_cursor_hidden {
2387 self.mouse_cursor_hidden = false;
2388 cx.notify();
2389 }
2390 }
2391
2392 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2393 let hide_mouse_cursor = match origin {
2394 HideMouseCursorOrigin::TypingAction => {
2395 matches!(
2396 self.hide_mouse_mode,
2397 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2398 )
2399 }
2400 HideMouseCursorOrigin::MovementAction => {
2401 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2402 }
2403 };
2404 if self.mouse_cursor_hidden != hide_mouse_cursor {
2405 self.mouse_cursor_hidden = hide_mouse_cursor;
2406 cx.notify();
2407 }
2408 }
2409
2410 pub fn edit_prediction_in_conflict(&self) -> bool {
2411 if !self.show_edit_predictions_in_menu() {
2412 return false;
2413 }
2414
2415 let showing_completions = self
2416 .context_menu
2417 .borrow()
2418 .as_ref()
2419 .map_or(false, |context| {
2420 matches!(context, CodeContextMenu::Completions(_))
2421 });
2422
2423 showing_completions
2424 || self.edit_prediction_requires_modifier()
2425 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2426 // bindings to insert tab characters.
2427 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2428 }
2429
2430 pub fn accept_edit_prediction_keybind(
2431 &self,
2432 accept_partial: bool,
2433 window: &Window,
2434 cx: &App,
2435 ) -> AcceptEditPredictionBinding {
2436 let key_context = self.key_context_internal(true, window, cx);
2437 let in_conflict = self.edit_prediction_in_conflict();
2438
2439 let bindings = if accept_partial {
2440 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2441 } else {
2442 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2443 };
2444
2445 // TODO: if the binding contains multiple keystrokes, display all of them, not
2446 // just the first one.
2447 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2448 !in_conflict
2449 || binding
2450 .keystrokes()
2451 .first()
2452 .map_or(false, |keystroke| keystroke.modifiers.modified())
2453 }))
2454 }
2455
2456 pub fn new_file(
2457 workspace: &mut Workspace,
2458 _: &workspace::NewFile,
2459 window: &mut Window,
2460 cx: &mut Context<Workspace>,
2461 ) {
2462 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2463 "Failed to create buffer",
2464 window,
2465 cx,
2466 |e, _, _| match e.error_code() {
2467 ErrorCode::RemoteUpgradeRequired => Some(format!(
2468 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2469 e.error_tag("required").unwrap_or("the latest version")
2470 )),
2471 _ => None,
2472 },
2473 );
2474 }
2475
2476 pub fn new_in_workspace(
2477 workspace: &mut Workspace,
2478 window: &mut Window,
2479 cx: &mut Context<Workspace>,
2480 ) -> Task<Result<Entity<Editor>>> {
2481 let project = workspace.project().clone();
2482 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2483
2484 cx.spawn_in(window, async move |workspace, cx| {
2485 let buffer = create.await?;
2486 workspace.update_in(cx, |workspace, window, cx| {
2487 let editor =
2488 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2489 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2490 editor
2491 })
2492 })
2493 }
2494
2495 fn new_file_vertical(
2496 workspace: &mut Workspace,
2497 _: &workspace::NewFileSplitVertical,
2498 window: &mut Window,
2499 cx: &mut Context<Workspace>,
2500 ) {
2501 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2502 }
2503
2504 fn new_file_horizontal(
2505 workspace: &mut Workspace,
2506 _: &workspace::NewFileSplitHorizontal,
2507 window: &mut Window,
2508 cx: &mut Context<Workspace>,
2509 ) {
2510 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2511 }
2512
2513 fn new_file_in_direction(
2514 workspace: &mut Workspace,
2515 direction: SplitDirection,
2516 window: &mut Window,
2517 cx: &mut Context<Workspace>,
2518 ) {
2519 let project = workspace.project().clone();
2520 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2521
2522 cx.spawn_in(window, async move |workspace, cx| {
2523 let buffer = create.await?;
2524 workspace.update_in(cx, move |workspace, window, cx| {
2525 workspace.split_item(
2526 direction,
2527 Box::new(
2528 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2529 ),
2530 window,
2531 cx,
2532 )
2533 })?;
2534 anyhow::Ok(())
2535 })
2536 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2537 match e.error_code() {
2538 ErrorCode::RemoteUpgradeRequired => Some(format!(
2539 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2540 e.error_tag("required").unwrap_or("the latest version")
2541 )),
2542 _ => None,
2543 }
2544 });
2545 }
2546
2547 pub fn leader_id(&self) -> Option<CollaboratorId> {
2548 self.leader_id
2549 }
2550
2551 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2552 &self.buffer
2553 }
2554
2555 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2556 self.workspace.as_ref()?.0.upgrade()
2557 }
2558
2559 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2560 self.buffer().read(cx).title(cx)
2561 }
2562
2563 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2564 let git_blame_gutter_max_author_length = self
2565 .render_git_blame_gutter(cx)
2566 .then(|| {
2567 if let Some(blame) = self.blame.as_ref() {
2568 let max_author_length =
2569 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2570 Some(max_author_length)
2571 } else {
2572 None
2573 }
2574 })
2575 .flatten();
2576
2577 EditorSnapshot {
2578 mode: self.mode.clone(),
2579 show_gutter: self.show_gutter,
2580 show_line_numbers: self.show_line_numbers,
2581 show_git_diff_gutter: self.show_git_diff_gutter,
2582 show_code_actions: self.show_code_actions,
2583 show_runnables: self.show_runnables,
2584 show_breakpoints: self.show_breakpoints,
2585 git_blame_gutter_max_author_length,
2586 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2587 scroll_anchor: self.scroll_manager.anchor(),
2588 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2589 placeholder_text: self.placeholder_text.clone(),
2590 is_focused: self.focus_handle.is_focused(window),
2591 current_line_highlight: self
2592 .current_line_highlight
2593 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2594 gutter_hovered: self.gutter_hovered,
2595 }
2596 }
2597
2598 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2599 self.buffer.read(cx).language_at(point, cx)
2600 }
2601
2602 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2603 self.buffer.read(cx).read(cx).file_at(point).cloned()
2604 }
2605
2606 pub fn active_excerpt(
2607 &self,
2608 cx: &App,
2609 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2610 self.buffer
2611 .read(cx)
2612 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2613 }
2614
2615 pub fn mode(&self) -> &EditorMode {
2616 &self.mode
2617 }
2618
2619 pub fn set_mode(&mut self, mode: EditorMode) {
2620 self.mode = mode;
2621 }
2622
2623 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2624 self.collaboration_hub.as_deref()
2625 }
2626
2627 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2628 self.collaboration_hub = Some(hub);
2629 }
2630
2631 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2632 self.in_project_search = in_project_search;
2633 }
2634
2635 pub fn set_custom_context_menu(
2636 &mut self,
2637 f: impl 'static
2638 + Fn(
2639 &mut Self,
2640 DisplayPoint,
2641 &mut Window,
2642 &mut Context<Self>,
2643 ) -> Option<Entity<ui::ContextMenu>>,
2644 ) {
2645 self.custom_context_menu = Some(Box::new(f))
2646 }
2647
2648 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2649 self.completion_provider = provider;
2650 }
2651
2652 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2653 self.semantics_provider.clone()
2654 }
2655
2656 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2657 self.semantics_provider = provider;
2658 }
2659
2660 pub fn set_edit_prediction_provider<T>(
2661 &mut self,
2662 provider: Option<Entity<T>>,
2663 window: &mut Window,
2664 cx: &mut Context<Self>,
2665 ) where
2666 T: EditPredictionProvider,
2667 {
2668 self.edit_prediction_provider =
2669 provider.map(|provider| RegisteredInlineCompletionProvider {
2670 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2671 if this.focus_handle.is_focused(window) {
2672 this.update_visible_inline_completion(window, cx);
2673 }
2674 }),
2675 provider: Arc::new(provider),
2676 });
2677 self.update_edit_prediction_settings(cx);
2678 self.refresh_inline_completion(false, false, window, cx);
2679 }
2680
2681 pub fn placeholder_text(&self) -> Option<&str> {
2682 self.placeholder_text.as_deref()
2683 }
2684
2685 pub fn set_placeholder_text(
2686 &mut self,
2687 placeholder_text: impl Into<Arc<str>>,
2688 cx: &mut Context<Self>,
2689 ) {
2690 let placeholder_text = Some(placeholder_text.into());
2691 if self.placeholder_text != placeholder_text {
2692 self.placeholder_text = placeholder_text;
2693 cx.notify();
2694 }
2695 }
2696
2697 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2698 self.cursor_shape = cursor_shape;
2699
2700 // Disrupt blink for immediate user feedback that the cursor shape has changed
2701 self.blink_manager.update(cx, BlinkManager::show_cursor);
2702
2703 cx.notify();
2704 }
2705
2706 pub fn set_current_line_highlight(
2707 &mut self,
2708 current_line_highlight: Option<CurrentLineHighlight>,
2709 ) {
2710 self.current_line_highlight = current_line_highlight;
2711 }
2712
2713 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2714 self.collapse_matches = collapse_matches;
2715 }
2716
2717 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2718 let buffers = self.buffer.read(cx).all_buffers();
2719 let Some(project) = self.project.as_ref() else {
2720 return;
2721 };
2722 project.update(cx, |project, cx| {
2723 for buffer in buffers {
2724 self.registered_buffers
2725 .entry(buffer.read(cx).remote_id())
2726 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2727 }
2728 })
2729 }
2730
2731 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2732 if self.collapse_matches {
2733 return range.start..range.start;
2734 }
2735 range.clone()
2736 }
2737
2738 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2739 if self.display_map.read(cx).clip_at_line_ends != clip {
2740 self.display_map
2741 .update(cx, |map, _| map.clip_at_line_ends = clip);
2742 }
2743 }
2744
2745 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2746 self.input_enabled = input_enabled;
2747 }
2748
2749 pub fn set_inline_completions_hidden_for_vim_mode(
2750 &mut self,
2751 hidden: bool,
2752 window: &mut Window,
2753 cx: &mut Context<Self>,
2754 ) {
2755 if hidden != self.inline_completions_hidden_for_vim_mode {
2756 self.inline_completions_hidden_for_vim_mode = hidden;
2757 if hidden {
2758 self.update_visible_inline_completion(window, cx);
2759 } else {
2760 self.refresh_inline_completion(true, false, window, cx);
2761 }
2762 }
2763 }
2764
2765 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2766 self.menu_inline_completions_policy = value;
2767 }
2768
2769 pub fn set_autoindent(&mut self, autoindent: bool) {
2770 if autoindent {
2771 self.autoindent_mode = Some(AutoindentMode::EachLine);
2772 } else {
2773 self.autoindent_mode = None;
2774 }
2775 }
2776
2777 pub fn read_only(&self, cx: &App) -> bool {
2778 self.read_only || self.buffer.read(cx).read_only()
2779 }
2780
2781 pub fn set_read_only(&mut self, read_only: bool) {
2782 self.read_only = read_only;
2783 }
2784
2785 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2786 self.use_autoclose = autoclose;
2787 }
2788
2789 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2790 self.use_auto_surround = auto_surround;
2791 }
2792
2793 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2794 self.auto_replace_emoji_shortcode = auto_replace;
2795 }
2796
2797 pub fn toggle_edit_predictions(
2798 &mut self,
2799 _: &ToggleEditPrediction,
2800 window: &mut Window,
2801 cx: &mut Context<Self>,
2802 ) {
2803 if self.show_inline_completions_override.is_some() {
2804 self.set_show_edit_predictions(None, window, cx);
2805 } else {
2806 let show_edit_predictions = !self.edit_predictions_enabled();
2807 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2808 }
2809 }
2810
2811 pub fn set_show_edit_predictions(
2812 &mut self,
2813 show_edit_predictions: Option<bool>,
2814 window: &mut Window,
2815 cx: &mut Context<Self>,
2816 ) {
2817 self.show_inline_completions_override = show_edit_predictions;
2818 self.update_edit_prediction_settings(cx);
2819
2820 if let Some(false) = show_edit_predictions {
2821 self.discard_inline_completion(false, cx);
2822 } else {
2823 self.refresh_inline_completion(false, true, window, cx);
2824 }
2825 }
2826
2827 fn inline_completions_disabled_in_scope(
2828 &self,
2829 buffer: &Entity<Buffer>,
2830 buffer_position: language::Anchor,
2831 cx: &App,
2832 ) -> bool {
2833 let snapshot = buffer.read(cx).snapshot();
2834 let settings = snapshot.settings_at(buffer_position, cx);
2835
2836 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2837 return false;
2838 };
2839
2840 scope.override_name().map_or(false, |scope_name| {
2841 settings
2842 .edit_predictions_disabled_in
2843 .iter()
2844 .any(|s| s == scope_name)
2845 })
2846 }
2847
2848 pub fn set_use_modal_editing(&mut self, to: bool) {
2849 self.use_modal_editing = to;
2850 }
2851
2852 pub fn use_modal_editing(&self) -> bool {
2853 self.use_modal_editing
2854 }
2855
2856 fn selections_did_change(
2857 &mut self,
2858 local: bool,
2859 old_cursor_position: &Anchor,
2860 effects: SelectionEffects,
2861 window: &mut Window,
2862 cx: &mut Context<Self>,
2863 ) {
2864 window.invalidate_character_coordinates();
2865
2866 // Copy selections to primary selection buffer
2867 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2868 if local {
2869 let selections = self.selections.all::<usize>(cx);
2870 let buffer_handle = self.buffer.read(cx).read(cx);
2871
2872 let mut text = String::new();
2873 for (index, selection) in selections.iter().enumerate() {
2874 let text_for_selection = buffer_handle
2875 .text_for_range(selection.start..selection.end)
2876 .collect::<String>();
2877
2878 text.push_str(&text_for_selection);
2879 if index != selections.len() - 1 {
2880 text.push('\n');
2881 }
2882 }
2883
2884 if !text.is_empty() {
2885 cx.write_to_primary(ClipboardItem::new_string(text));
2886 }
2887 }
2888
2889 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2890 self.buffer.update(cx, |buffer, cx| {
2891 buffer.set_active_selections(
2892 &self.selections.disjoint_anchors(),
2893 self.selections.line_mode,
2894 self.cursor_shape,
2895 cx,
2896 )
2897 });
2898 }
2899 let display_map = self
2900 .display_map
2901 .update(cx, |display_map, cx| display_map.snapshot(cx));
2902 let buffer = &display_map.buffer_snapshot;
2903 if self.selections.count() == 1 {
2904 self.add_selections_state = None;
2905 }
2906 self.select_next_state = None;
2907 self.select_prev_state = None;
2908 self.select_syntax_node_history.try_clear();
2909 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2910 self.snippet_stack
2911 .invalidate(&self.selections.disjoint_anchors(), buffer);
2912 self.take_rename(false, window, cx);
2913
2914 let newest_selection = self.selections.newest_anchor();
2915 let new_cursor_position = newest_selection.head();
2916 let selection_start = newest_selection.start;
2917
2918 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
2919 self.push_to_nav_history(
2920 *old_cursor_position,
2921 Some(new_cursor_position.to_point(buffer)),
2922 false,
2923 effects.nav_history == Some(true),
2924 cx,
2925 );
2926 }
2927
2928 if local {
2929 if let Some(buffer_id) = new_cursor_position.buffer_id {
2930 if !self.registered_buffers.contains_key(&buffer_id) {
2931 if let Some(project) = self.project.as_ref() {
2932 project.update(cx, |project, cx| {
2933 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2934 return;
2935 };
2936 self.registered_buffers.insert(
2937 buffer_id,
2938 project.register_buffer_with_language_servers(&buffer, cx),
2939 );
2940 })
2941 }
2942 }
2943 }
2944
2945 let mut context_menu = self.context_menu.borrow_mut();
2946 let completion_menu = match context_menu.as_ref() {
2947 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2948 Some(CodeContextMenu::CodeActions(_)) => {
2949 *context_menu = None;
2950 None
2951 }
2952 None => None,
2953 };
2954 let completion_position = completion_menu.map(|menu| menu.initial_position);
2955 drop(context_menu);
2956
2957 if effects.completions {
2958 if let Some(completion_position) = completion_position {
2959 let start_offset = selection_start.to_offset(buffer);
2960 let position_matches = start_offset == completion_position.to_offset(buffer);
2961 let continue_showing = if position_matches {
2962 if self.snippet_stack.is_empty() {
2963 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
2964 } else {
2965 // Snippet choices can be shown even when the cursor is in whitespace.
2966 // Dismissing the menu with actions like backspace is handled by
2967 // invalidation regions.
2968 true
2969 }
2970 } else {
2971 false
2972 };
2973
2974 if continue_showing {
2975 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2976 } else {
2977 self.hide_context_menu(window, cx);
2978 }
2979 }
2980 }
2981
2982 hide_hover(self, cx);
2983
2984 if old_cursor_position.to_display_point(&display_map).row()
2985 != new_cursor_position.to_display_point(&display_map).row()
2986 {
2987 self.available_code_actions.take();
2988 }
2989 self.refresh_code_actions(window, cx);
2990 self.refresh_document_highlights(cx);
2991 self.refresh_selected_text_highlights(false, window, cx);
2992 refresh_matching_bracket_highlights(self, window, cx);
2993 self.update_visible_inline_completion(window, cx);
2994 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2995 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2996 self.inline_blame_popover.take();
2997 if self.git_blame_inline_enabled {
2998 self.start_inline_blame_timer(window, cx);
2999 }
3000 }
3001
3002 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3003 cx.emit(EditorEvent::SelectionsChanged { local });
3004
3005 let selections = &self.selections.disjoint;
3006 if selections.len() == 1 {
3007 cx.emit(SearchEvent::ActiveMatchChanged)
3008 }
3009 if local {
3010 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3011 let inmemory_selections = selections
3012 .iter()
3013 .map(|s| {
3014 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3015 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3016 })
3017 .collect();
3018 self.update_restoration_data(cx, |data| {
3019 data.selections = inmemory_selections;
3020 });
3021
3022 if WorkspaceSettings::get(None, cx).restore_on_startup
3023 != RestoreOnStartupBehavior::None
3024 {
3025 if let Some(workspace_id) =
3026 self.workspace.as_ref().and_then(|workspace| workspace.1)
3027 {
3028 let snapshot = self.buffer().read(cx).snapshot(cx);
3029 let selections = selections.clone();
3030 let background_executor = cx.background_executor().clone();
3031 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3032 self.serialize_selections = cx.background_spawn(async move {
3033 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3034 let db_selections = selections
3035 .iter()
3036 .map(|selection| {
3037 (
3038 selection.start.to_offset(&snapshot),
3039 selection.end.to_offset(&snapshot),
3040 )
3041 })
3042 .collect();
3043
3044 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3045 .await
3046 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3047 .log_err();
3048 });
3049 }
3050 }
3051 }
3052 }
3053
3054 cx.notify();
3055 }
3056
3057 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3058 use text::ToOffset as _;
3059 use text::ToPoint as _;
3060
3061 if self.mode.is_minimap()
3062 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3063 {
3064 return;
3065 }
3066
3067 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3068 return;
3069 };
3070
3071 let snapshot = singleton.read(cx).snapshot();
3072 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3073 let display_snapshot = display_map.snapshot(cx);
3074
3075 display_snapshot
3076 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3077 .map(|fold| {
3078 fold.range.start.text_anchor.to_point(&snapshot)
3079 ..fold.range.end.text_anchor.to_point(&snapshot)
3080 })
3081 .collect()
3082 });
3083 self.update_restoration_data(cx, |data| {
3084 data.folds = inmemory_folds;
3085 });
3086
3087 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3088 return;
3089 };
3090 let background_executor = cx.background_executor().clone();
3091 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3092 let db_folds = self.display_map.update(cx, |display_map, cx| {
3093 display_map
3094 .snapshot(cx)
3095 .folds_in_range(0..snapshot.len())
3096 .map(|fold| {
3097 (
3098 fold.range.start.text_anchor.to_offset(&snapshot),
3099 fold.range.end.text_anchor.to_offset(&snapshot),
3100 )
3101 })
3102 .collect()
3103 });
3104 self.serialize_folds = cx.background_spawn(async move {
3105 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3106 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3107 .await
3108 .with_context(|| {
3109 format!(
3110 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3111 )
3112 })
3113 .log_err();
3114 });
3115 }
3116
3117 pub fn sync_selections(
3118 &mut self,
3119 other: Entity<Editor>,
3120 cx: &mut Context<Self>,
3121 ) -> gpui::Subscription {
3122 let other_selections = other.read(cx).selections.disjoint.to_vec();
3123 self.selections.change_with(cx, |selections| {
3124 selections.select_anchors(other_selections);
3125 });
3126
3127 let other_subscription =
3128 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3129 EditorEvent::SelectionsChanged { local: true } => {
3130 let other_selections = other.read(cx).selections.disjoint.to_vec();
3131 if other_selections.is_empty() {
3132 return;
3133 }
3134 this.selections.change_with(cx, |selections| {
3135 selections.select_anchors(other_selections);
3136 });
3137 }
3138 _ => {}
3139 });
3140
3141 let this_subscription =
3142 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3143 EditorEvent::SelectionsChanged { local: true } => {
3144 let these_selections = this.selections.disjoint.to_vec();
3145 if these_selections.is_empty() {
3146 return;
3147 }
3148 other.update(cx, |other_editor, cx| {
3149 other_editor.selections.change_with(cx, |selections| {
3150 selections.select_anchors(these_selections);
3151 })
3152 });
3153 }
3154 _ => {}
3155 });
3156
3157 Subscription::join(other_subscription, this_subscription)
3158 }
3159
3160 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3161 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3162 /// effects of selection change occur at the end of the transaction.
3163 pub fn change_selections<R>(
3164 &mut self,
3165 effects: impl Into<SelectionEffects>,
3166 window: &mut Window,
3167 cx: &mut Context<Self>,
3168 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3169 ) -> R {
3170 let effects = effects.into();
3171 if let Some(state) = &mut self.deferred_selection_effects_state {
3172 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3173 state.effects.completions = effects.completions;
3174 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3175 let (changed, result) = self.selections.change_with(cx, change);
3176 state.changed |= changed;
3177 return result;
3178 }
3179 let mut state = DeferredSelectionEffectsState {
3180 changed: false,
3181 effects,
3182 old_cursor_position: self.selections.newest_anchor().head(),
3183 history_entry: SelectionHistoryEntry {
3184 selections: self.selections.disjoint_anchors(),
3185 select_next_state: self.select_next_state.clone(),
3186 select_prev_state: self.select_prev_state.clone(),
3187 add_selections_state: self.add_selections_state.clone(),
3188 },
3189 };
3190 let (changed, result) = self.selections.change_with(cx, change);
3191 state.changed = state.changed || changed;
3192 if self.defer_selection_effects {
3193 self.deferred_selection_effects_state = Some(state);
3194 } else {
3195 self.apply_selection_effects(state, window, cx);
3196 }
3197 result
3198 }
3199
3200 /// Defers the effects of selection change, so that the effects of multiple calls to
3201 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3202 /// to selection history and the state of popovers based on selection position aren't
3203 /// erroneously updated.
3204 pub fn with_selection_effects_deferred<R>(
3205 &mut self,
3206 window: &mut Window,
3207 cx: &mut Context<Self>,
3208 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3209 ) -> R {
3210 let already_deferred = self.defer_selection_effects;
3211 self.defer_selection_effects = true;
3212 let result = update(self, window, cx);
3213 if !already_deferred {
3214 self.defer_selection_effects = false;
3215 if let Some(state) = self.deferred_selection_effects_state.take() {
3216 self.apply_selection_effects(state, window, cx);
3217 }
3218 }
3219 result
3220 }
3221
3222 fn apply_selection_effects(
3223 &mut self,
3224 state: DeferredSelectionEffectsState,
3225 window: &mut Window,
3226 cx: &mut Context<Self>,
3227 ) {
3228 if state.changed {
3229 self.selection_history.push(state.history_entry);
3230
3231 if let Some(autoscroll) = state.effects.scroll {
3232 self.request_autoscroll(autoscroll, cx);
3233 }
3234
3235 let old_cursor_position = &state.old_cursor_position;
3236
3237 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3238
3239 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3240 self.show_signature_help(&ShowSignatureHelp, window, cx);
3241 }
3242 }
3243 }
3244
3245 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3246 where
3247 I: IntoIterator<Item = (Range<S>, T)>,
3248 S: ToOffset,
3249 T: Into<Arc<str>>,
3250 {
3251 if self.read_only(cx) {
3252 return;
3253 }
3254
3255 self.buffer
3256 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3257 }
3258
3259 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3260 where
3261 I: IntoIterator<Item = (Range<S>, T)>,
3262 S: ToOffset,
3263 T: Into<Arc<str>>,
3264 {
3265 if self.read_only(cx) {
3266 return;
3267 }
3268
3269 self.buffer.update(cx, |buffer, cx| {
3270 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3271 });
3272 }
3273
3274 pub fn edit_with_block_indent<I, S, T>(
3275 &mut self,
3276 edits: I,
3277 original_indent_columns: Vec<Option<u32>>,
3278 cx: &mut Context<Self>,
3279 ) where
3280 I: IntoIterator<Item = (Range<S>, T)>,
3281 S: ToOffset,
3282 T: Into<Arc<str>>,
3283 {
3284 if self.read_only(cx) {
3285 return;
3286 }
3287
3288 self.buffer.update(cx, |buffer, cx| {
3289 buffer.edit(
3290 edits,
3291 Some(AutoindentMode::Block {
3292 original_indent_columns,
3293 }),
3294 cx,
3295 )
3296 });
3297 }
3298
3299 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3300 self.hide_context_menu(window, cx);
3301
3302 match phase {
3303 SelectPhase::Begin {
3304 position,
3305 add,
3306 click_count,
3307 } => self.begin_selection(position, add, click_count, window, cx),
3308 SelectPhase::BeginColumnar {
3309 position,
3310 goal_column,
3311 reset,
3312 mode,
3313 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3314 SelectPhase::Extend {
3315 position,
3316 click_count,
3317 } => self.extend_selection(position, click_count, window, cx),
3318 SelectPhase::Update {
3319 position,
3320 goal_column,
3321 scroll_delta,
3322 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3323 SelectPhase::End => self.end_selection(window, cx),
3324 }
3325 }
3326
3327 fn extend_selection(
3328 &mut self,
3329 position: DisplayPoint,
3330 click_count: usize,
3331 window: &mut Window,
3332 cx: &mut Context<Self>,
3333 ) {
3334 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3335 let tail = self.selections.newest::<usize>(cx).tail();
3336 self.begin_selection(position, false, click_count, window, cx);
3337
3338 let position = position.to_offset(&display_map, Bias::Left);
3339 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3340
3341 let mut pending_selection = self
3342 .selections
3343 .pending_anchor()
3344 .expect("extend_selection not called with pending selection");
3345 if position >= tail {
3346 pending_selection.start = tail_anchor;
3347 } else {
3348 pending_selection.end = tail_anchor;
3349 pending_selection.reversed = true;
3350 }
3351
3352 let mut pending_mode = self.selections.pending_mode().unwrap();
3353 match &mut pending_mode {
3354 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3355 _ => {}
3356 }
3357
3358 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3359 SelectionEffects::scroll(Autoscroll::fit())
3360 } else {
3361 SelectionEffects::no_scroll()
3362 };
3363
3364 self.change_selections(effects, window, cx, |s| {
3365 s.set_pending(pending_selection, pending_mode)
3366 });
3367 }
3368
3369 fn begin_selection(
3370 &mut self,
3371 position: DisplayPoint,
3372 add: bool,
3373 click_count: usize,
3374 window: &mut Window,
3375 cx: &mut Context<Self>,
3376 ) {
3377 if !self.focus_handle.is_focused(window) {
3378 self.last_focused_descendant = None;
3379 window.focus(&self.focus_handle);
3380 }
3381
3382 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3383 let buffer = &display_map.buffer_snapshot;
3384 let position = display_map.clip_point(position, Bias::Left);
3385
3386 let start;
3387 let end;
3388 let mode;
3389 let mut auto_scroll;
3390 match click_count {
3391 1 => {
3392 start = buffer.anchor_before(position.to_point(&display_map));
3393 end = start;
3394 mode = SelectMode::Character;
3395 auto_scroll = true;
3396 }
3397 2 => {
3398 let position = display_map
3399 .clip_point(position, Bias::Left)
3400 .to_offset(&display_map, Bias::Left);
3401 let (range, _) = buffer.surrounding_word(position, false);
3402 start = buffer.anchor_before(range.start);
3403 end = buffer.anchor_before(range.end);
3404 mode = SelectMode::Word(start..end);
3405 auto_scroll = true;
3406 }
3407 3 => {
3408 let position = display_map
3409 .clip_point(position, Bias::Left)
3410 .to_point(&display_map);
3411 let line_start = display_map.prev_line_boundary(position).0;
3412 let next_line_start = buffer.clip_point(
3413 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3414 Bias::Left,
3415 );
3416 start = buffer.anchor_before(line_start);
3417 end = buffer.anchor_before(next_line_start);
3418 mode = SelectMode::Line(start..end);
3419 auto_scroll = true;
3420 }
3421 _ => {
3422 start = buffer.anchor_before(0);
3423 end = buffer.anchor_before(buffer.len());
3424 mode = SelectMode::All;
3425 auto_scroll = false;
3426 }
3427 }
3428 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3429
3430 let point_to_delete: Option<usize> = {
3431 let selected_points: Vec<Selection<Point>> =
3432 self.selections.disjoint_in_range(start..end, cx);
3433
3434 if !add || click_count > 1 {
3435 None
3436 } else if !selected_points.is_empty() {
3437 Some(selected_points[0].id)
3438 } else {
3439 let clicked_point_already_selected =
3440 self.selections.disjoint.iter().find(|selection| {
3441 selection.start.to_point(buffer) == start.to_point(buffer)
3442 || selection.end.to_point(buffer) == end.to_point(buffer)
3443 });
3444
3445 clicked_point_already_selected.map(|selection| selection.id)
3446 }
3447 };
3448
3449 let selections_count = self.selections.count();
3450
3451 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3452 if let Some(point_to_delete) = point_to_delete {
3453 s.delete(point_to_delete);
3454
3455 if selections_count == 1 {
3456 s.set_pending_anchor_range(start..end, mode);
3457 }
3458 } else {
3459 if !add {
3460 s.clear_disjoint();
3461 }
3462
3463 s.set_pending_anchor_range(start..end, mode);
3464 }
3465 });
3466 }
3467
3468 fn begin_columnar_selection(
3469 &mut self,
3470 position: DisplayPoint,
3471 goal_column: u32,
3472 reset: bool,
3473 mode: ColumnarMode,
3474 window: &mut Window,
3475 cx: &mut Context<Self>,
3476 ) {
3477 if !self.focus_handle.is_focused(window) {
3478 self.last_focused_descendant = None;
3479 window.focus(&self.focus_handle);
3480 }
3481
3482 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3483
3484 if reset {
3485 let pointer_position = display_map
3486 .buffer_snapshot
3487 .anchor_before(position.to_point(&display_map));
3488
3489 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3490 s.clear_disjoint();
3491 s.set_pending_anchor_range(
3492 pointer_position..pointer_position,
3493 SelectMode::Character,
3494 );
3495 });
3496 };
3497
3498 let tail = self.selections.newest::<Point>(cx).tail();
3499 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3500 self.columnar_selection_state = match mode {
3501 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3502 selection_tail: selection_anchor,
3503 display_point: if reset {
3504 if position.column() != goal_column {
3505 Some(DisplayPoint::new(position.row(), goal_column))
3506 } else {
3507 None
3508 }
3509 } else {
3510 None
3511 },
3512 }),
3513 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3514 selection_tail: selection_anchor,
3515 }),
3516 };
3517
3518 if !reset {
3519 self.select_columns(position, goal_column, &display_map, window, cx);
3520 }
3521 }
3522
3523 fn update_selection(
3524 &mut self,
3525 position: DisplayPoint,
3526 goal_column: u32,
3527 scroll_delta: gpui::Point<f32>,
3528 window: &mut Window,
3529 cx: &mut Context<Self>,
3530 ) {
3531 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3532
3533 if self.columnar_selection_state.is_some() {
3534 self.select_columns(position, goal_column, &display_map, window, cx);
3535 } else if let Some(mut pending) = self.selections.pending_anchor() {
3536 let buffer = &display_map.buffer_snapshot;
3537 let head;
3538 let tail;
3539 let mode = self.selections.pending_mode().unwrap();
3540 match &mode {
3541 SelectMode::Character => {
3542 head = position.to_point(&display_map);
3543 tail = pending.tail().to_point(buffer);
3544 }
3545 SelectMode::Word(original_range) => {
3546 let offset = display_map
3547 .clip_point(position, Bias::Left)
3548 .to_offset(&display_map, Bias::Left);
3549 let original_range = original_range.to_offset(buffer);
3550
3551 let head_offset = if buffer.is_inside_word(offset, false)
3552 || original_range.contains(&offset)
3553 {
3554 let (word_range, _) = buffer.surrounding_word(offset, false);
3555 if word_range.start < original_range.start {
3556 word_range.start
3557 } else {
3558 word_range.end
3559 }
3560 } else {
3561 offset
3562 };
3563
3564 head = head_offset.to_point(buffer);
3565 if head_offset <= original_range.start {
3566 tail = original_range.end.to_point(buffer);
3567 } else {
3568 tail = original_range.start.to_point(buffer);
3569 }
3570 }
3571 SelectMode::Line(original_range) => {
3572 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3573
3574 let position = display_map
3575 .clip_point(position, Bias::Left)
3576 .to_point(&display_map);
3577 let line_start = display_map.prev_line_boundary(position).0;
3578 let next_line_start = buffer.clip_point(
3579 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3580 Bias::Left,
3581 );
3582
3583 if line_start < original_range.start {
3584 head = line_start
3585 } else {
3586 head = next_line_start
3587 }
3588
3589 if head <= original_range.start {
3590 tail = original_range.end;
3591 } else {
3592 tail = original_range.start;
3593 }
3594 }
3595 SelectMode::All => {
3596 return;
3597 }
3598 };
3599
3600 if head < tail {
3601 pending.start = buffer.anchor_before(head);
3602 pending.end = buffer.anchor_before(tail);
3603 pending.reversed = true;
3604 } else {
3605 pending.start = buffer.anchor_before(tail);
3606 pending.end = buffer.anchor_before(head);
3607 pending.reversed = false;
3608 }
3609
3610 self.change_selections(None, window, cx, |s| {
3611 s.set_pending(pending, mode);
3612 });
3613 } else {
3614 log::error!("update_selection dispatched with no pending selection");
3615 return;
3616 }
3617
3618 self.apply_scroll_delta(scroll_delta, window, cx);
3619 cx.notify();
3620 }
3621
3622 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3623 self.columnar_selection_state.take();
3624 if self.selections.pending_anchor().is_some() {
3625 let selections = self.selections.all::<usize>(cx);
3626 self.change_selections(None, window, cx, |s| {
3627 s.select(selections);
3628 s.clear_pending();
3629 });
3630 }
3631 }
3632
3633 fn select_columns(
3634 &mut self,
3635 head: DisplayPoint,
3636 goal_column: u32,
3637 display_map: &DisplaySnapshot,
3638 window: &mut Window,
3639 cx: &mut Context<Self>,
3640 ) {
3641 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3642 return;
3643 };
3644
3645 let tail = match columnar_state {
3646 ColumnarSelectionState::FromMouse {
3647 selection_tail,
3648 display_point,
3649 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(&display_map)),
3650 ColumnarSelectionState::FromSelection { selection_tail } => {
3651 selection_tail.to_display_point(&display_map)
3652 }
3653 };
3654
3655 let start_row = cmp::min(tail.row(), head.row());
3656 let end_row = cmp::max(tail.row(), head.row());
3657 let start_column = cmp::min(tail.column(), goal_column);
3658 let end_column = cmp::max(tail.column(), goal_column);
3659 let reversed = start_column < tail.column();
3660
3661 let selection_ranges = (start_row.0..=end_row.0)
3662 .map(DisplayRow)
3663 .filter_map(|row| {
3664 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3665 || start_column <= display_map.line_len(row))
3666 && !display_map.is_block_line(row)
3667 {
3668 let start = display_map
3669 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3670 .to_point(display_map);
3671 let end = display_map
3672 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3673 .to_point(display_map);
3674 if reversed {
3675 Some(end..start)
3676 } else {
3677 Some(start..end)
3678 }
3679 } else {
3680 None
3681 }
3682 })
3683 .collect::<Vec<_>>();
3684
3685 let ranges = match columnar_state {
3686 ColumnarSelectionState::FromMouse { .. } => {
3687 let mut non_empty_ranges = selection_ranges
3688 .iter()
3689 .filter(|selection_range| selection_range.start != selection_range.end)
3690 .peekable();
3691 if non_empty_ranges.peek().is_some() {
3692 non_empty_ranges.cloned().collect()
3693 } else {
3694 selection_ranges
3695 }
3696 }
3697 _ => selection_ranges,
3698 };
3699
3700 self.change_selections(None, window, cx, |s| {
3701 s.select_ranges(ranges);
3702 });
3703 cx.notify();
3704 }
3705
3706 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3707 self.selections
3708 .all_adjusted(cx)
3709 .iter()
3710 .any(|selection| !selection.is_empty())
3711 }
3712
3713 pub fn has_pending_nonempty_selection(&self) -> bool {
3714 let pending_nonempty_selection = match self.selections.pending_anchor() {
3715 Some(Selection { start, end, .. }) => start != end,
3716 None => false,
3717 };
3718
3719 pending_nonempty_selection
3720 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3721 }
3722
3723 pub fn has_pending_selection(&self) -> bool {
3724 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3725 }
3726
3727 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3728 self.selection_mark_mode = false;
3729 self.selection_drag_state = SelectionDragState::None;
3730
3731 if self.clear_expanded_diff_hunks(cx) {
3732 cx.notify();
3733 return;
3734 }
3735 if self.dismiss_menus_and_popups(true, window, cx) {
3736 return;
3737 }
3738
3739 if self.mode.is_full()
3740 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3741 {
3742 return;
3743 }
3744
3745 cx.propagate();
3746 }
3747
3748 pub fn dismiss_menus_and_popups(
3749 &mut self,
3750 is_user_requested: bool,
3751 window: &mut Window,
3752 cx: &mut Context<Self>,
3753 ) -> bool {
3754 if self.take_rename(false, window, cx).is_some() {
3755 return true;
3756 }
3757
3758 if hide_hover(self, cx) {
3759 return true;
3760 }
3761
3762 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3763 return true;
3764 }
3765
3766 if self.hide_context_menu(window, cx).is_some() {
3767 return true;
3768 }
3769
3770 if self.mouse_context_menu.take().is_some() {
3771 return true;
3772 }
3773
3774 if is_user_requested && self.discard_inline_completion(true, cx) {
3775 return true;
3776 }
3777
3778 if self.snippet_stack.pop().is_some() {
3779 return true;
3780 }
3781
3782 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3783 self.dismiss_diagnostics(cx);
3784 return true;
3785 }
3786
3787 false
3788 }
3789
3790 fn linked_editing_ranges_for(
3791 &self,
3792 selection: Range<text::Anchor>,
3793 cx: &App,
3794 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3795 if self.linked_edit_ranges.is_empty() {
3796 return None;
3797 }
3798 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3799 selection.end.buffer_id.and_then(|end_buffer_id| {
3800 if selection.start.buffer_id != Some(end_buffer_id) {
3801 return None;
3802 }
3803 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3804 let snapshot = buffer.read(cx).snapshot();
3805 self.linked_edit_ranges
3806 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3807 .map(|ranges| (ranges, snapshot, buffer))
3808 })?;
3809 use text::ToOffset as TO;
3810 // find offset from the start of current range to current cursor position
3811 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3812
3813 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3814 let start_difference = start_offset - start_byte_offset;
3815 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3816 let end_difference = end_offset - start_byte_offset;
3817 // Current range has associated linked ranges.
3818 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3819 for range in linked_ranges.iter() {
3820 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3821 let end_offset = start_offset + end_difference;
3822 let start_offset = start_offset + start_difference;
3823 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3824 continue;
3825 }
3826 if self.selections.disjoint_anchor_ranges().any(|s| {
3827 if s.start.buffer_id != selection.start.buffer_id
3828 || s.end.buffer_id != selection.end.buffer_id
3829 {
3830 return false;
3831 }
3832 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3833 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3834 }) {
3835 continue;
3836 }
3837 let start = buffer_snapshot.anchor_after(start_offset);
3838 let end = buffer_snapshot.anchor_after(end_offset);
3839 linked_edits
3840 .entry(buffer.clone())
3841 .or_default()
3842 .push(start..end);
3843 }
3844 Some(linked_edits)
3845 }
3846
3847 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3848 let text: Arc<str> = text.into();
3849
3850 if self.read_only(cx) {
3851 return;
3852 }
3853
3854 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3855
3856 let selections = self.selections.all_adjusted(cx);
3857 let mut bracket_inserted = false;
3858 let mut edits = Vec::new();
3859 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3860 let mut new_selections = Vec::with_capacity(selections.len());
3861 let mut new_autoclose_regions = Vec::new();
3862 let snapshot = self.buffer.read(cx).read(cx);
3863 let mut clear_linked_edit_ranges = false;
3864
3865 for (selection, autoclose_region) in
3866 self.selections_with_autoclose_regions(selections, &snapshot)
3867 {
3868 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3869 // Determine if the inserted text matches the opening or closing
3870 // bracket of any of this language's bracket pairs.
3871 let mut bracket_pair = None;
3872 let mut is_bracket_pair_start = false;
3873 let mut is_bracket_pair_end = false;
3874 if !text.is_empty() {
3875 let mut bracket_pair_matching_end = None;
3876 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3877 // and they are removing the character that triggered IME popup.
3878 for (pair, enabled) in scope.brackets() {
3879 if !pair.close && !pair.surround {
3880 continue;
3881 }
3882
3883 if enabled && pair.start.ends_with(text.as_ref()) {
3884 let prefix_len = pair.start.len() - text.len();
3885 let preceding_text_matches_prefix = prefix_len == 0
3886 || (selection.start.column >= (prefix_len as u32)
3887 && snapshot.contains_str_at(
3888 Point::new(
3889 selection.start.row,
3890 selection.start.column - (prefix_len as u32),
3891 ),
3892 &pair.start[..prefix_len],
3893 ));
3894 if preceding_text_matches_prefix {
3895 bracket_pair = Some(pair.clone());
3896 is_bracket_pair_start = true;
3897 break;
3898 }
3899 }
3900 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3901 {
3902 // take first bracket pair matching end, but don't break in case a later bracket
3903 // pair matches start
3904 bracket_pair_matching_end = Some(pair.clone());
3905 }
3906 }
3907 if let Some(end) = bracket_pair_matching_end
3908 && bracket_pair.is_none()
3909 {
3910 bracket_pair = Some(end);
3911 is_bracket_pair_end = true;
3912 }
3913 }
3914
3915 if let Some(bracket_pair) = bracket_pair {
3916 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3917 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3918 let auto_surround =
3919 self.use_auto_surround && snapshot_settings.use_auto_surround;
3920 if selection.is_empty() {
3921 if is_bracket_pair_start {
3922 // If the inserted text is a suffix of an opening bracket and the
3923 // selection is preceded by the rest of the opening bracket, then
3924 // insert the closing bracket.
3925 let following_text_allows_autoclose = snapshot
3926 .chars_at(selection.start)
3927 .next()
3928 .map_or(true, |c| scope.should_autoclose_before(c));
3929
3930 let preceding_text_allows_autoclose = selection.start.column == 0
3931 || snapshot.reversed_chars_at(selection.start).next().map_or(
3932 true,
3933 |c| {
3934 bracket_pair.start != bracket_pair.end
3935 || !snapshot
3936 .char_classifier_at(selection.start)
3937 .is_word(c)
3938 },
3939 );
3940
3941 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3942 && bracket_pair.start.len() == 1
3943 {
3944 let target = bracket_pair.start.chars().next().unwrap();
3945 let current_line_count = snapshot
3946 .reversed_chars_at(selection.start)
3947 .take_while(|&c| c != '\n')
3948 .filter(|&c| c == target)
3949 .count();
3950 current_line_count % 2 == 1
3951 } else {
3952 false
3953 };
3954
3955 if autoclose
3956 && bracket_pair.close
3957 && following_text_allows_autoclose
3958 && preceding_text_allows_autoclose
3959 && !is_closing_quote
3960 {
3961 let anchor = snapshot.anchor_before(selection.end);
3962 new_selections.push((selection.map(|_| anchor), text.len()));
3963 new_autoclose_regions.push((
3964 anchor,
3965 text.len(),
3966 selection.id,
3967 bracket_pair.clone(),
3968 ));
3969 edits.push((
3970 selection.range(),
3971 format!("{}{}", text, bracket_pair.end).into(),
3972 ));
3973 bracket_inserted = true;
3974 continue;
3975 }
3976 }
3977
3978 if let Some(region) = autoclose_region {
3979 // If the selection is followed by an auto-inserted closing bracket,
3980 // then don't insert that closing bracket again; just move the selection
3981 // past the closing bracket.
3982 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3983 && text.as_ref() == region.pair.end.as_str();
3984 if should_skip {
3985 let anchor = snapshot.anchor_after(selection.end);
3986 new_selections
3987 .push((selection.map(|_| anchor), region.pair.end.len()));
3988 continue;
3989 }
3990 }
3991
3992 let always_treat_brackets_as_autoclosed = snapshot
3993 .language_settings_at(selection.start, cx)
3994 .always_treat_brackets_as_autoclosed;
3995 if always_treat_brackets_as_autoclosed
3996 && is_bracket_pair_end
3997 && snapshot.contains_str_at(selection.end, text.as_ref())
3998 {
3999 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4000 // and the inserted text is a closing bracket and the selection is followed
4001 // by the closing bracket then move the selection past the closing bracket.
4002 let anchor = snapshot.anchor_after(selection.end);
4003 new_selections.push((selection.map(|_| anchor), text.len()));
4004 continue;
4005 }
4006 }
4007 // If an opening bracket is 1 character long and is typed while
4008 // text is selected, then surround that text with the bracket pair.
4009 else if auto_surround
4010 && bracket_pair.surround
4011 && is_bracket_pair_start
4012 && bracket_pair.start.chars().count() == 1
4013 {
4014 edits.push((selection.start..selection.start, text.clone()));
4015 edits.push((
4016 selection.end..selection.end,
4017 bracket_pair.end.as_str().into(),
4018 ));
4019 bracket_inserted = true;
4020 new_selections.push((
4021 Selection {
4022 id: selection.id,
4023 start: snapshot.anchor_after(selection.start),
4024 end: snapshot.anchor_before(selection.end),
4025 reversed: selection.reversed,
4026 goal: selection.goal,
4027 },
4028 0,
4029 ));
4030 continue;
4031 }
4032 }
4033 }
4034
4035 if self.auto_replace_emoji_shortcode
4036 && selection.is_empty()
4037 && text.as_ref().ends_with(':')
4038 {
4039 if let Some(possible_emoji_short_code) =
4040 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4041 {
4042 if !possible_emoji_short_code.is_empty() {
4043 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
4044 let emoji_shortcode_start = Point::new(
4045 selection.start.row,
4046 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4047 );
4048
4049 // Remove shortcode from buffer
4050 edits.push((
4051 emoji_shortcode_start..selection.start,
4052 "".to_string().into(),
4053 ));
4054 new_selections.push((
4055 Selection {
4056 id: selection.id,
4057 start: snapshot.anchor_after(emoji_shortcode_start),
4058 end: snapshot.anchor_before(selection.start),
4059 reversed: selection.reversed,
4060 goal: selection.goal,
4061 },
4062 0,
4063 ));
4064
4065 // Insert emoji
4066 let selection_start_anchor = snapshot.anchor_after(selection.start);
4067 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4068 edits.push((selection.start..selection.end, emoji.to_string().into()));
4069
4070 continue;
4071 }
4072 }
4073 }
4074 }
4075
4076 // If not handling any auto-close operation, then just replace the selected
4077 // text with the given input and move the selection to the end of the
4078 // newly inserted text.
4079 let anchor = snapshot.anchor_after(selection.end);
4080 if !self.linked_edit_ranges.is_empty() {
4081 let start_anchor = snapshot.anchor_before(selection.start);
4082
4083 let is_word_char = text.chars().next().map_or(true, |char| {
4084 let classifier = snapshot
4085 .char_classifier_at(start_anchor.to_offset(&snapshot))
4086 .ignore_punctuation(true);
4087 classifier.is_word(char)
4088 });
4089
4090 if is_word_char {
4091 if let Some(ranges) = self
4092 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4093 {
4094 for (buffer, edits) in ranges {
4095 linked_edits
4096 .entry(buffer.clone())
4097 .or_default()
4098 .extend(edits.into_iter().map(|range| (range, text.clone())));
4099 }
4100 }
4101 } else {
4102 clear_linked_edit_ranges = true;
4103 }
4104 }
4105
4106 new_selections.push((selection.map(|_| anchor), 0));
4107 edits.push((selection.start..selection.end, text.clone()));
4108 }
4109
4110 drop(snapshot);
4111
4112 self.transact(window, cx, |this, window, cx| {
4113 if clear_linked_edit_ranges {
4114 this.linked_edit_ranges.clear();
4115 }
4116 let initial_buffer_versions =
4117 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4118
4119 this.buffer.update(cx, |buffer, cx| {
4120 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4121 });
4122 for (buffer, edits) in linked_edits {
4123 buffer.update(cx, |buffer, cx| {
4124 let snapshot = buffer.snapshot();
4125 let edits = edits
4126 .into_iter()
4127 .map(|(range, text)| {
4128 use text::ToPoint as TP;
4129 let end_point = TP::to_point(&range.end, &snapshot);
4130 let start_point = TP::to_point(&range.start, &snapshot);
4131 (start_point..end_point, text)
4132 })
4133 .sorted_by_key(|(range, _)| range.start);
4134 buffer.edit(edits, None, cx);
4135 })
4136 }
4137 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4138 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4139 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4140 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4141 .zip(new_selection_deltas)
4142 .map(|(selection, delta)| Selection {
4143 id: selection.id,
4144 start: selection.start + delta,
4145 end: selection.end + delta,
4146 reversed: selection.reversed,
4147 goal: SelectionGoal::None,
4148 })
4149 .collect::<Vec<_>>();
4150
4151 let mut i = 0;
4152 for (position, delta, selection_id, pair) in new_autoclose_regions {
4153 let position = position.to_offset(&map.buffer_snapshot) + delta;
4154 let start = map.buffer_snapshot.anchor_before(position);
4155 let end = map.buffer_snapshot.anchor_after(position);
4156 while let Some(existing_state) = this.autoclose_regions.get(i) {
4157 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4158 Ordering::Less => i += 1,
4159 Ordering::Greater => break,
4160 Ordering::Equal => {
4161 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4162 Ordering::Less => i += 1,
4163 Ordering::Equal => break,
4164 Ordering::Greater => break,
4165 }
4166 }
4167 }
4168 }
4169 this.autoclose_regions.insert(
4170 i,
4171 AutocloseRegion {
4172 selection_id,
4173 range: start..end,
4174 pair,
4175 },
4176 );
4177 }
4178
4179 let had_active_inline_completion = this.has_active_inline_completion();
4180 this.change_selections(
4181 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4182 window,
4183 cx,
4184 |s| s.select(new_selections),
4185 );
4186
4187 if !bracket_inserted {
4188 if let Some(on_type_format_task) =
4189 this.trigger_on_type_formatting(text.to_string(), window, cx)
4190 {
4191 on_type_format_task.detach_and_log_err(cx);
4192 }
4193 }
4194
4195 let editor_settings = EditorSettings::get_global(cx);
4196 if bracket_inserted
4197 && (editor_settings.auto_signature_help
4198 || editor_settings.show_signature_help_after_edits)
4199 {
4200 this.show_signature_help(&ShowSignatureHelp, window, cx);
4201 }
4202
4203 let trigger_in_words =
4204 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4205 if this.hard_wrap.is_some() {
4206 let latest: Range<Point> = this.selections.newest(cx).range();
4207 if latest.is_empty()
4208 && this
4209 .buffer()
4210 .read(cx)
4211 .snapshot(cx)
4212 .line_len(MultiBufferRow(latest.start.row))
4213 == latest.start.column
4214 {
4215 this.rewrap_impl(
4216 RewrapOptions {
4217 override_language_settings: true,
4218 preserve_existing_whitespace: true,
4219 },
4220 cx,
4221 )
4222 }
4223 }
4224 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4225 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4226 this.refresh_inline_completion(true, false, window, cx);
4227 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4228 });
4229 }
4230
4231 fn find_possible_emoji_shortcode_at_position(
4232 snapshot: &MultiBufferSnapshot,
4233 position: Point,
4234 ) -> Option<String> {
4235 let mut chars = Vec::new();
4236 let mut found_colon = false;
4237 for char in snapshot.reversed_chars_at(position).take(100) {
4238 // Found a possible emoji shortcode in the middle of the buffer
4239 if found_colon {
4240 if char.is_whitespace() {
4241 chars.reverse();
4242 return Some(chars.iter().collect());
4243 }
4244 // If the previous character is not a whitespace, we are in the middle of a word
4245 // and we only want to complete the shortcode if the word is made up of other emojis
4246 let mut containing_word = String::new();
4247 for ch in snapshot
4248 .reversed_chars_at(position)
4249 .skip(chars.len() + 1)
4250 .take(100)
4251 {
4252 if ch.is_whitespace() {
4253 break;
4254 }
4255 containing_word.push(ch);
4256 }
4257 let containing_word = containing_word.chars().rev().collect::<String>();
4258 if util::word_consists_of_emojis(containing_word.as_str()) {
4259 chars.reverse();
4260 return Some(chars.iter().collect());
4261 }
4262 }
4263
4264 if char.is_whitespace() || !char.is_ascii() {
4265 return None;
4266 }
4267 if char == ':' {
4268 found_colon = true;
4269 } else {
4270 chars.push(char);
4271 }
4272 }
4273 // Found a possible emoji shortcode at the beginning of the buffer
4274 chars.reverse();
4275 Some(chars.iter().collect())
4276 }
4277
4278 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4279 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4280 self.transact(window, cx, |this, window, cx| {
4281 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4282 let selections = this.selections.all::<usize>(cx);
4283 let multi_buffer = this.buffer.read(cx);
4284 let buffer = multi_buffer.snapshot(cx);
4285 selections
4286 .iter()
4287 .map(|selection| {
4288 let start_point = selection.start.to_point(&buffer);
4289 let mut existing_indent =
4290 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4291 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4292 let start = selection.start;
4293 let end = selection.end;
4294 let selection_is_empty = start == end;
4295 let language_scope = buffer.language_scope_at(start);
4296 let (
4297 comment_delimiter,
4298 doc_delimiter,
4299 insert_extra_newline,
4300 indent_on_newline,
4301 indent_on_extra_newline,
4302 ) = if let Some(language) = &language_scope {
4303 let mut insert_extra_newline =
4304 insert_extra_newline_brackets(&buffer, start..end, language)
4305 || insert_extra_newline_tree_sitter(&buffer, start..end);
4306
4307 // Comment extension on newline is allowed only for cursor selections
4308 let comment_delimiter = maybe!({
4309 if !selection_is_empty {
4310 return None;
4311 }
4312
4313 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4314 return None;
4315 }
4316
4317 let delimiters = language.line_comment_prefixes();
4318 let max_len_of_delimiter =
4319 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4320 let (snapshot, range) =
4321 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4322
4323 let num_of_whitespaces = snapshot
4324 .chars_for_range(range.clone())
4325 .take_while(|c| c.is_whitespace())
4326 .count();
4327 let comment_candidate = snapshot
4328 .chars_for_range(range)
4329 .skip(num_of_whitespaces)
4330 .take(max_len_of_delimiter)
4331 .collect::<String>();
4332 let (delimiter, trimmed_len) = delimiters
4333 .iter()
4334 .filter_map(|delimiter| {
4335 let prefix = delimiter.trim_end();
4336 if comment_candidate.starts_with(prefix) {
4337 Some((delimiter, prefix.len()))
4338 } else {
4339 None
4340 }
4341 })
4342 .max_by_key(|(_, len)| *len)?;
4343
4344 let cursor_is_placed_after_comment_marker =
4345 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4346 if cursor_is_placed_after_comment_marker {
4347 Some(delimiter.clone())
4348 } else {
4349 None
4350 }
4351 });
4352
4353 let mut indent_on_newline = IndentSize::spaces(0);
4354 let mut indent_on_extra_newline = IndentSize::spaces(0);
4355
4356 let doc_delimiter = maybe!({
4357 if !selection_is_empty {
4358 return None;
4359 }
4360
4361 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4362 return None;
4363 }
4364
4365 let DocumentationConfig {
4366 start: start_tag,
4367 end: end_tag,
4368 prefix: delimiter,
4369 tab_size: len,
4370 } = language.documentation()?;
4371
4372 let is_within_block_comment = buffer
4373 .language_scope_at(start_point)
4374 .is_some_and(|scope| scope.override_name() == Some("comment"));
4375 if !is_within_block_comment {
4376 return None;
4377 }
4378
4379 let (snapshot, range) =
4380 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4381
4382 let num_of_whitespaces = snapshot
4383 .chars_for_range(range.clone())
4384 .take_while(|c| c.is_whitespace())
4385 .count();
4386
4387 // 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.
4388 let column = start_point.column;
4389 let cursor_is_after_start_tag = {
4390 let start_tag_len = start_tag.len();
4391 let start_tag_line = snapshot
4392 .chars_for_range(range.clone())
4393 .skip(num_of_whitespaces)
4394 .take(start_tag_len)
4395 .collect::<String>();
4396 if start_tag_line.starts_with(start_tag.as_ref()) {
4397 num_of_whitespaces + start_tag_len <= column as usize
4398 } else {
4399 false
4400 }
4401 };
4402
4403 let cursor_is_after_delimiter = {
4404 let delimiter_trim = delimiter.trim_end();
4405 let delimiter_line = snapshot
4406 .chars_for_range(range.clone())
4407 .skip(num_of_whitespaces)
4408 .take(delimiter_trim.len())
4409 .collect::<String>();
4410 if delimiter_line.starts_with(delimiter_trim) {
4411 num_of_whitespaces + delimiter_trim.len() <= column as usize
4412 } else {
4413 false
4414 }
4415 };
4416
4417 let cursor_is_before_end_tag_if_exists = {
4418 let mut char_position = 0u32;
4419 let mut end_tag_offset = None;
4420
4421 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4422 if let Some(byte_pos) = chunk.find(&**end_tag) {
4423 let chars_before_match =
4424 chunk[..byte_pos].chars().count() as u32;
4425 end_tag_offset =
4426 Some(char_position + chars_before_match);
4427 break 'outer;
4428 }
4429 char_position += chunk.chars().count() as u32;
4430 }
4431
4432 if let Some(end_tag_offset) = end_tag_offset {
4433 let cursor_is_before_end_tag = column <= end_tag_offset;
4434 if cursor_is_after_start_tag {
4435 if cursor_is_before_end_tag {
4436 insert_extra_newline = true;
4437 }
4438 let cursor_is_at_start_of_end_tag =
4439 column == end_tag_offset;
4440 if cursor_is_at_start_of_end_tag {
4441 indent_on_extra_newline.len = (*len).into();
4442 }
4443 }
4444 cursor_is_before_end_tag
4445 } else {
4446 true
4447 }
4448 };
4449
4450 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4451 && cursor_is_before_end_tag_if_exists
4452 {
4453 if cursor_is_after_start_tag {
4454 indent_on_newline.len = (*len).into();
4455 }
4456 Some(delimiter.clone())
4457 } else {
4458 None
4459 }
4460 });
4461
4462 (
4463 comment_delimiter,
4464 doc_delimiter,
4465 insert_extra_newline,
4466 indent_on_newline,
4467 indent_on_extra_newline,
4468 )
4469 } else {
4470 (
4471 None,
4472 None,
4473 false,
4474 IndentSize::default(),
4475 IndentSize::default(),
4476 )
4477 };
4478
4479 let prevent_auto_indent = doc_delimiter.is_some();
4480 let delimiter = comment_delimiter.or(doc_delimiter);
4481
4482 let capacity_for_delimiter =
4483 delimiter.as_deref().map(str::len).unwrap_or_default();
4484 let mut new_text = String::with_capacity(
4485 1 + capacity_for_delimiter
4486 + existing_indent.len as usize
4487 + indent_on_newline.len as usize
4488 + indent_on_extra_newline.len as usize,
4489 );
4490 new_text.push('\n');
4491 new_text.extend(existing_indent.chars());
4492 new_text.extend(indent_on_newline.chars());
4493
4494 if let Some(delimiter) = &delimiter {
4495 new_text.push_str(delimiter);
4496 }
4497
4498 if insert_extra_newline {
4499 new_text.push('\n');
4500 new_text.extend(existing_indent.chars());
4501 new_text.extend(indent_on_extra_newline.chars());
4502 }
4503
4504 let anchor = buffer.anchor_after(end);
4505 let new_selection = selection.map(|_| anchor);
4506 (
4507 ((start..end, new_text), prevent_auto_indent),
4508 (insert_extra_newline, new_selection),
4509 )
4510 })
4511 .unzip()
4512 };
4513
4514 let mut auto_indent_edits = Vec::new();
4515 let mut edits = Vec::new();
4516 for (edit, prevent_auto_indent) in edits_with_flags {
4517 if prevent_auto_indent {
4518 edits.push(edit);
4519 } else {
4520 auto_indent_edits.push(edit);
4521 }
4522 }
4523 if !edits.is_empty() {
4524 this.edit(edits, cx);
4525 }
4526 if !auto_indent_edits.is_empty() {
4527 this.edit_with_autoindent(auto_indent_edits, cx);
4528 }
4529
4530 let buffer = this.buffer.read(cx).snapshot(cx);
4531 let new_selections = selection_info
4532 .into_iter()
4533 .map(|(extra_newline_inserted, new_selection)| {
4534 let mut cursor = new_selection.end.to_point(&buffer);
4535 if extra_newline_inserted {
4536 cursor.row -= 1;
4537 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4538 }
4539 new_selection.map(|_| cursor)
4540 })
4541 .collect();
4542
4543 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4544 s.select(new_selections)
4545 });
4546 this.refresh_inline_completion(true, false, window, cx);
4547 });
4548 }
4549
4550 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4551 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4552
4553 let buffer = self.buffer.read(cx);
4554 let snapshot = buffer.snapshot(cx);
4555
4556 let mut edits = Vec::new();
4557 let mut rows = Vec::new();
4558
4559 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4560 let cursor = selection.head();
4561 let row = cursor.row;
4562
4563 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4564
4565 let newline = "\n".to_string();
4566 edits.push((start_of_line..start_of_line, newline));
4567
4568 rows.push(row + rows_inserted as u32);
4569 }
4570
4571 self.transact(window, cx, |editor, window, cx| {
4572 editor.edit(edits, cx);
4573
4574 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4575 let mut index = 0;
4576 s.move_cursors_with(|map, _, _| {
4577 let row = rows[index];
4578 index += 1;
4579
4580 let point = Point::new(row, 0);
4581 let boundary = map.next_line_boundary(point).1;
4582 let clipped = map.clip_point(boundary, Bias::Left);
4583
4584 (clipped, SelectionGoal::None)
4585 });
4586 });
4587
4588 let mut indent_edits = Vec::new();
4589 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4590 for row in rows {
4591 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4592 for (row, indent) in indents {
4593 if indent.len == 0 {
4594 continue;
4595 }
4596
4597 let text = match indent.kind {
4598 IndentKind::Space => " ".repeat(indent.len as usize),
4599 IndentKind::Tab => "\t".repeat(indent.len as usize),
4600 };
4601 let point = Point::new(row.0, 0);
4602 indent_edits.push((point..point, text));
4603 }
4604 }
4605 editor.edit(indent_edits, cx);
4606 });
4607 }
4608
4609 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4610 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4611
4612 let buffer = self.buffer.read(cx);
4613 let snapshot = buffer.snapshot(cx);
4614
4615 let mut edits = Vec::new();
4616 let mut rows = Vec::new();
4617 let mut rows_inserted = 0;
4618
4619 for selection in self.selections.all_adjusted(cx) {
4620 let cursor = selection.head();
4621 let row = cursor.row;
4622
4623 let point = Point::new(row + 1, 0);
4624 let start_of_line = snapshot.clip_point(point, Bias::Left);
4625
4626 let newline = "\n".to_string();
4627 edits.push((start_of_line..start_of_line, newline));
4628
4629 rows_inserted += 1;
4630 rows.push(row + rows_inserted);
4631 }
4632
4633 self.transact(window, cx, |editor, window, cx| {
4634 editor.edit(edits, cx);
4635
4636 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4637 let mut index = 0;
4638 s.move_cursors_with(|map, _, _| {
4639 let row = rows[index];
4640 index += 1;
4641
4642 let point = Point::new(row, 0);
4643 let boundary = map.next_line_boundary(point).1;
4644 let clipped = map.clip_point(boundary, Bias::Left);
4645
4646 (clipped, SelectionGoal::None)
4647 });
4648 });
4649
4650 let mut indent_edits = Vec::new();
4651 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4652 for row in rows {
4653 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4654 for (row, indent) in indents {
4655 if indent.len == 0 {
4656 continue;
4657 }
4658
4659 let text = match indent.kind {
4660 IndentKind::Space => " ".repeat(indent.len as usize),
4661 IndentKind::Tab => "\t".repeat(indent.len as usize),
4662 };
4663 let point = Point::new(row.0, 0);
4664 indent_edits.push((point..point, text));
4665 }
4666 }
4667 editor.edit(indent_edits, cx);
4668 });
4669 }
4670
4671 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4672 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4673 original_indent_columns: Vec::new(),
4674 });
4675 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4676 }
4677
4678 fn insert_with_autoindent_mode(
4679 &mut self,
4680 text: &str,
4681 autoindent_mode: Option<AutoindentMode>,
4682 window: &mut Window,
4683 cx: &mut Context<Self>,
4684 ) {
4685 if self.read_only(cx) {
4686 return;
4687 }
4688
4689 let text: Arc<str> = text.into();
4690 self.transact(window, cx, |this, window, cx| {
4691 let old_selections = this.selections.all_adjusted(cx);
4692 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4693 let anchors = {
4694 let snapshot = buffer.read(cx);
4695 old_selections
4696 .iter()
4697 .map(|s| {
4698 let anchor = snapshot.anchor_after(s.head());
4699 s.map(|_| anchor)
4700 })
4701 .collect::<Vec<_>>()
4702 };
4703 buffer.edit(
4704 old_selections
4705 .iter()
4706 .map(|s| (s.start..s.end, text.clone())),
4707 autoindent_mode,
4708 cx,
4709 );
4710 anchors
4711 });
4712
4713 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4714 s.select_anchors(selection_anchors);
4715 });
4716
4717 cx.notify();
4718 });
4719 }
4720
4721 fn trigger_completion_on_input(
4722 &mut self,
4723 text: &str,
4724 trigger_in_words: bool,
4725 window: &mut Window,
4726 cx: &mut Context<Self>,
4727 ) {
4728 let completions_source = self
4729 .context_menu
4730 .borrow()
4731 .as_ref()
4732 .and_then(|menu| match menu {
4733 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4734 CodeContextMenu::CodeActions(_) => None,
4735 });
4736
4737 match completions_source {
4738 Some(CompletionsMenuSource::Words) => {
4739 self.show_word_completions(&ShowWordCompletions, window, cx)
4740 }
4741 Some(CompletionsMenuSource::Normal)
4742 | Some(CompletionsMenuSource::SnippetChoices)
4743 | None
4744 if self.is_completion_trigger(
4745 text,
4746 trigger_in_words,
4747 completions_source.is_some(),
4748 cx,
4749 ) =>
4750 {
4751 self.show_completions(
4752 &ShowCompletions {
4753 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4754 },
4755 window,
4756 cx,
4757 )
4758 }
4759 _ => {
4760 self.hide_context_menu(window, cx);
4761 }
4762 }
4763 }
4764
4765 fn is_completion_trigger(
4766 &self,
4767 text: &str,
4768 trigger_in_words: bool,
4769 menu_is_open: bool,
4770 cx: &mut Context<Self>,
4771 ) -> bool {
4772 let position = self.selections.newest_anchor().head();
4773 let multibuffer = self.buffer.read(cx);
4774 let Some(buffer) = position
4775 .buffer_id
4776 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4777 else {
4778 return false;
4779 };
4780
4781 if let Some(completion_provider) = &self.completion_provider {
4782 completion_provider.is_completion_trigger(
4783 &buffer,
4784 position.text_anchor,
4785 text,
4786 trigger_in_words,
4787 menu_is_open,
4788 cx,
4789 )
4790 } else {
4791 false
4792 }
4793 }
4794
4795 /// If any empty selections is touching the start of its innermost containing autoclose
4796 /// region, expand it to select the brackets.
4797 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4798 let selections = self.selections.all::<usize>(cx);
4799 let buffer = self.buffer.read(cx).read(cx);
4800 let new_selections = self
4801 .selections_with_autoclose_regions(selections, &buffer)
4802 .map(|(mut selection, region)| {
4803 if !selection.is_empty() {
4804 return selection;
4805 }
4806
4807 if let Some(region) = region {
4808 let mut range = region.range.to_offset(&buffer);
4809 if selection.start == range.start && range.start >= region.pair.start.len() {
4810 range.start -= region.pair.start.len();
4811 if buffer.contains_str_at(range.start, ®ion.pair.start)
4812 && buffer.contains_str_at(range.end, ®ion.pair.end)
4813 {
4814 range.end += region.pair.end.len();
4815 selection.start = range.start;
4816 selection.end = range.end;
4817
4818 return selection;
4819 }
4820 }
4821 }
4822
4823 let always_treat_brackets_as_autoclosed = buffer
4824 .language_settings_at(selection.start, cx)
4825 .always_treat_brackets_as_autoclosed;
4826
4827 if !always_treat_brackets_as_autoclosed {
4828 return selection;
4829 }
4830
4831 if let Some(scope) = buffer.language_scope_at(selection.start) {
4832 for (pair, enabled) in scope.brackets() {
4833 if !enabled || !pair.close {
4834 continue;
4835 }
4836
4837 if buffer.contains_str_at(selection.start, &pair.end) {
4838 let pair_start_len = pair.start.len();
4839 if buffer.contains_str_at(
4840 selection.start.saturating_sub(pair_start_len),
4841 &pair.start,
4842 ) {
4843 selection.start -= pair_start_len;
4844 selection.end += pair.end.len();
4845
4846 return selection;
4847 }
4848 }
4849 }
4850 }
4851
4852 selection
4853 })
4854 .collect();
4855
4856 drop(buffer);
4857 self.change_selections(None, window, cx, |selections| {
4858 selections.select(new_selections)
4859 });
4860 }
4861
4862 /// Iterate the given selections, and for each one, find the smallest surrounding
4863 /// autoclose region. This uses the ordering of the selections and the autoclose
4864 /// regions to avoid repeated comparisons.
4865 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4866 &'a self,
4867 selections: impl IntoIterator<Item = Selection<D>>,
4868 buffer: &'a MultiBufferSnapshot,
4869 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4870 let mut i = 0;
4871 let mut regions = self.autoclose_regions.as_slice();
4872 selections.into_iter().map(move |selection| {
4873 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4874
4875 let mut enclosing = None;
4876 while let Some(pair_state) = regions.get(i) {
4877 if pair_state.range.end.to_offset(buffer) < range.start {
4878 regions = ®ions[i + 1..];
4879 i = 0;
4880 } else if pair_state.range.start.to_offset(buffer) > range.end {
4881 break;
4882 } else {
4883 if pair_state.selection_id == selection.id {
4884 enclosing = Some(pair_state);
4885 }
4886 i += 1;
4887 }
4888 }
4889
4890 (selection, enclosing)
4891 })
4892 }
4893
4894 /// Remove any autoclose regions that no longer contain their selection.
4895 fn invalidate_autoclose_regions(
4896 &mut self,
4897 mut selections: &[Selection<Anchor>],
4898 buffer: &MultiBufferSnapshot,
4899 ) {
4900 self.autoclose_regions.retain(|state| {
4901 let mut i = 0;
4902 while let Some(selection) = selections.get(i) {
4903 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4904 selections = &selections[1..];
4905 continue;
4906 }
4907 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4908 break;
4909 }
4910 if selection.id == state.selection_id {
4911 return true;
4912 } else {
4913 i += 1;
4914 }
4915 }
4916 false
4917 });
4918 }
4919
4920 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4921 let offset = position.to_offset(buffer);
4922 let (word_range, kind) = buffer.surrounding_word(offset, true);
4923 if offset > word_range.start && kind == Some(CharKind::Word) {
4924 Some(
4925 buffer
4926 .text_for_range(word_range.start..offset)
4927 .collect::<String>(),
4928 )
4929 } else {
4930 None
4931 }
4932 }
4933
4934 pub fn toggle_inline_values(
4935 &mut self,
4936 _: &ToggleInlineValues,
4937 _: &mut Window,
4938 cx: &mut Context<Self>,
4939 ) {
4940 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4941
4942 self.refresh_inline_values(cx);
4943 }
4944
4945 pub fn toggle_inlay_hints(
4946 &mut self,
4947 _: &ToggleInlayHints,
4948 _: &mut Window,
4949 cx: &mut Context<Self>,
4950 ) {
4951 self.refresh_inlay_hints(
4952 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4953 cx,
4954 );
4955 }
4956
4957 pub fn inlay_hints_enabled(&self) -> bool {
4958 self.inlay_hint_cache.enabled
4959 }
4960
4961 pub fn inline_values_enabled(&self) -> bool {
4962 self.inline_value_cache.enabled
4963 }
4964
4965 #[cfg(any(test, feature = "test-support"))]
4966 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4967 self.display_map
4968 .read(cx)
4969 .current_inlays()
4970 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4971 .cloned()
4972 .collect()
4973 }
4974
4975 #[cfg(any(test, feature = "test-support"))]
4976 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
4977 self.display_map
4978 .read(cx)
4979 .current_inlays()
4980 .cloned()
4981 .collect()
4982 }
4983
4984 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4985 if self.semantics_provider.is_none() || !self.mode.is_full() {
4986 return;
4987 }
4988
4989 let reason_description = reason.description();
4990 let ignore_debounce = matches!(
4991 reason,
4992 InlayHintRefreshReason::SettingsChange(_)
4993 | InlayHintRefreshReason::Toggle(_)
4994 | InlayHintRefreshReason::ExcerptsRemoved(_)
4995 | InlayHintRefreshReason::ModifiersChanged(_)
4996 );
4997 let (invalidate_cache, required_languages) = match reason {
4998 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4999 match self.inlay_hint_cache.modifiers_override(enabled) {
5000 Some(enabled) => {
5001 if enabled {
5002 (InvalidationStrategy::RefreshRequested, None)
5003 } else {
5004 self.splice_inlays(
5005 &self
5006 .visible_inlay_hints(cx)
5007 .iter()
5008 .map(|inlay| inlay.id)
5009 .collect::<Vec<InlayId>>(),
5010 Vec::new(),
5011 cx,
5012 );
5013 return;
5014 }
5015 }
5016 None => return,
5017 }
5018 }
5019 InlayHintRefreshReason::Toggle(enabled) => {
5020 if self.inlay_hint_cache.toggle(enabled) {
5021 if enabled {
5022 (InvalidationStrategy::RefreshRequested, None)
5023 } else {
5024 self.splice_inlays(
5025 &self
5026 .visible_inlay_hints(cx)
5027 .iter()
5028 .map(|inlay| inlay.id)
5029 .collect::<Vec<InlayId>>(),
5030 Vec::new(),
5031 cx,
5032 );
5033 return;
5034 }
5035 } else {
5036 return;
5037 }
5038 }
5039 InlayHintRefreshReason::SettingsChange(new_settings) => {
5040 match self.inlay_hint_cache.update_settings(
5041 &self.buffer,
5042 new_settings,
5043 self.visible_inlay_hints(cx),
5044 cx,
5045 ) {
5046 ControlFlow::Break(Some(InlaySplice {
5047 to_remove,
5048 to_insert,
5049 })) => {
5050 self.splice_inlays(&to_remove, to_insert, cx);
5051 return;
5052 }
5053 ControlFlow::Break(None) => return,
5054 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5055 }
5056 }
5057 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5058 if let Some(InlaySplice {
5059 to_remove,
5060 to_insert,
5061 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5062 {
5063 self.splice_inlays(&to_remove, to_insert, cx);
5064 }
5065 self.display_map.update(cx, |display_map, _| {
5066 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5067 });
5068 return;
5069 }
5070 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5071 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5072 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5073 }
5074 InlayHintRefreshReason::RefreshRequested => {
5075 (InvalidationStrategy::RefreshRequested, None)
5076 }
5077 };
5078
5079 if let Some(InlaySplice {
5080 to_remove,
5081 to_insert,
5082 }) = self.inlay_hint_cache.spawn_hint_refresh(
5083 reason_description,
5084 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
5085 invalidate_cache,
5086 ignore_debounce,
5087 cx,
5088 ) {
5089 self.splice_inlays(&to_remove, to_insert, cx);
5090 }
5091 }
5092
5093 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5094 self.display_map
5095 .read(cx)
5096 .current_inlays()
5097 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5098 .cloned()
5099 .collect()
5100 }
5101
5102 pub fn excerpts_for_inlay_hints_query(
5103 &self,
5104 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5105 cx: &mut Context<Editor>,
5106 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5107 let Some(project) = self.project.as_ref() else {
5108 return HashMap::default();
5109 };
5110 let project = project.read(cx);
5111 let multi_buffer = self.buffer().read(cx);
5112 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5113 let multi_buffer_visible_start = self
5114 .scroll_manager
5115 .anchor()
5116 .anchor
5117 .to_point(&multi_buffer_snapshot);
5118 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5119 multi_buffer_visible_start
5120 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5121 Bias::Left,
5122 );
5123 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5124 multi_buffer_snapshot
5125 .range_to_buffer_ranges(multi_buffer_visible_range)
5126 .into_iter()
5127 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5128 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5129 let buffer_file = project::File::from_dyn(buffer.file())?;
5130 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5131 let worktree_entry = buffer_worktree
5132 .read(cx)
5133 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5134 if worktree_entry.is_ignored {
5135 return None;
5136 }
5137
5138 let language = buffer.language()?;
5139 if let Some(restrict_to_languages) = restrict_to_languages {
5140 if !restrict_to_languages.contains(language) {
5141 return None;
5142 }
5143 }
5144 Some((
5145 excerpt_id,
5146 (
5147 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5148 buffer.version().clone(),
5149 excerpt_visible_range,
5150 ),
5151 ))
5152 })
5153 .collect()
5154 }
5155
5156 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5157 TextLayoutDetails {
5158 text_system: window.text_system().clone(),
5159 editor_style: self.style.clone().unwrap(),
5160 rem_size: window.rem_size(),
5161 scroll_anchor: self.scroll_manager.anchor(),
5162 visible_rows: self.visible_line_count(),
5163 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5164 }
5165 }
5166
5167 pub fn splice_inlays(
5168 &self,
5169 to_remove: &[InlayId],
5170 to_insert: Vec<Inlay>,
5171 cx: &mut Context<Self>,
5172 ) {
5173 self.display_map.update(cx, |display_map, cx| {
5174 display_map.splice_inlays(to_remove, to_insert, cx)
5175 });
5176 cx.notify();
5177 }
5178
5179 fn trigger_on_type_formatting(
5180 &self,
5181 input: String,
5182 window: &mut Window,
5183 cx: &mut Context<Self>,
5184 ) -> Option<Task<Result<()>>> {
5185 if input.len() != 1 {
5186 return None;
5187 }
5188
5189 let project = self.project.as_ref()?;
5190 let position = self.selections.newest_anchor().head();
5191 let (buffer, buffer_position) = self
5192 .buffer
5193 .read(cx)
5194 .text_anchor_for_position(position, cx)?;
5195
5196 let settings = language_settings::language_settings(
5197 buffer
5198 .read(cx)
5199 .language_at(buffer_position)
5200 .map(|l| l.name()),
5201 buffer.read(cx).file(),
5202 cx,
5203 );
5204 if !settings.use_on_type_format {
5205 return None;
5206 }
5207
5208 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5209 // hence we do LSP request & edit on host side only — add formats to host's history.
5210 let push_to_lsp_host_history = true;
5211 // If this is not the host, append its history with new edits.
5212 let push_to_client_history = project.read(cx).is_via_collab();
5213
5214 let on_type_formatting = project.update(cx, |project, cx| {
5215 project.on_type_format(
5216 buffer.clone(),
5217 buffer_position,
5218 input,
5219 push_to_lsp_host_history,
5220 cx,
5221 )
5222 });
5223 Some(cx.spawn_in(window, async move |editor, cx| {
5224 if let Some(transaction) = on_type_formatting.await? {
5225 if push_to_client_history {
5226 buffer
5227 .update(cx, |buffer, _| {
5228 buffer.push_transaction(transaction, Instant::now());
5229 buffer.finalize_last_transaction();
5230 })
5231 .ok();
5232 }
5233 editor.update(cx, |editor, cx| {
5234 editor.refresh_document_highlights(cx);
5235 })?;
5236 }
5237 Ok(())
5238 }))
5239 }
5240
5241 pub fn show_word_completions(
5242 &mut self,
5243 _: &ShowWordCompletions,
5244 window: &mut Window,
5245 cx: &mut Context<Self>,
5246 ) {
5247 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5248 }
5249
5250 pub fn show_completions(
5251 &mut self,
5252 options: &ShowCompletions,
5253 window: &mut Window,
5254 cx: &mut Context<Self>,
5255 ) {
5256 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5257 }
5258
5259 fn open_or_update_completions_menu(
5260 &mut self,
5261 requested_source: Option<CompletionsMenuSource>,
5262 trigger: Option<&str>,
5263 window: &mut Window,
5264 cx: &mut Context<Self>,
5265 ) {
5266 if self.pending_rename.is_some() {
5267 return;
5268 }
5269
5270 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5271
5272 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5273 // inserted and selected. To handle that case, the start of the selection is used so that
5274 // the menu starts with all choices.
5275 let position = self
5276 .selections
5277 .newest_anchor()
5278 .start
5279 .bias_right(&multibuffer_snapshot);
5280 if position.diff_base_anchor.is_some() {
5281 return;
5282 }
5283 let (buffer, buffer_position) =
5284 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5285 output
5286 } else {
5287 return;
5288 };
5289 let buffer_snapshot = buffer.read(cx).snapshot();
5290
5291 let query: Option<Arc<String>> =
5292 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5293
5294 drop(multibuffer_snapshot);
5295
5296 let provider = match requested_source {
5297 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5298 Some(CompletionsMenuSource::Words) => None,
5299 Some(CompletionsMenuSource::SnippetChoices) => {
5300 log::error!("bug: SnippetChoices requested_source is not handled");
5301 None
5302 }
5303 };
5304
5305 let sort_completions = provider
5306 .as_ref()
5307 .map_or(false, |provider| provider.sort_completions());
5308
5309 let filter_completions = provider
5310 .as_ref()
5311 .map_or(true, |provider| provider.filter_completions());
5312
5313 let trigger_kind = match trigger {
5314 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5315 CompletionTriggerKind::TRIGGER_CHARACTER
5316 }
5317 _ => CompletionTriggerKind::INVOKED,
5318 };
5319 let completion_context = CompletionContext {
5320 trigger_character: trigger.and_then(|trigger| {
5321 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5322 Some(String::from(trigger))
5323 } else {
5324 None
5325 }
5326 }),
5327 trigger_kind,
5328 };
5329
5330 // Hide the current completions menu when a trigger char is typed. Without this, cached
5331 // completions from before the trigger char may be reused (#32774). Snippet choices could
5332 // involve trigger chars, so this is skipped in that case.
5333 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5334 {
5335 let menu_is_open = matches!(
5336 self.context_menu.borrow().as_ref(),
5337 Some(CodeContextMenu::Completions(_))
5338 );
5339 if menu_is_open {
5340 self.hide_context_menu(window, cx);
5341 }
5342 }
5343
5344 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5345 if filter_completions {
5346 menu.filter(query.clone(), provider.clone(), window, cx);
5347 }
5348 // When `is_incomplete` is false, no need to re-query completions when the current query
5349 // is a suffix of the initial query.
5350 if !menu.is_incomplete {
5351 // If the new query is a suffix of the old query (typing more characters) and
5352 // the previous result was complete, the existing completions can be filtered.
5353 //
5354 // Note that this is always true for snippet completions.
5355 let query_matches = match (&menu.initial_query, &query) {
5356 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5357 (None, _) => true,
5358 _ => false,
5359 };
5360 if query_matches {
5361 let position_matches = if menu.initial_position == position {
5362 true
5363 } else {
5364 let snapshot = self.buffer.read(cx).read(cx);
5365 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5366 };
5367 if position_matches {
5368 return;
5369 }
5370 }
5371 }
5372 };
5373
5374 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5375 buffer_snapshot.surrounding_word(buffer_position)
5376 {
5377 let word_to_exclude = buffer_snapshot
5378 .text_for_range(word_range.clone())
5379 .collect::<String>();
5380 (
5381 buffer_snapshot.anchor_before(word_range.start)
5382 ..buffer_snapshot.anchor_after(buffer_position),
5383 Some(word_to_exclude),
5384 )
5385 } else {
5386 (buffer_position..buffer_position, None)
5387 };
5388
5389 let language = buffer_snapshot
5390 .language_at(buffer_position)
5391 .map(|language| language.name());
5392
5393 let completion_settings =
5394 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5395
5396 let show_completion_documentation = buffer_snapshot
5397 .settings_at(buffer_position, cx)
5398 .show_completion_documentation;
5399
5400 // The document can be large, so stay in reasonable bounds when searching for words,
5401 // otherwise completion pop-up might be slow to appear.
5402 const WORD_LOOKUP_ROWS: u32 = 5_000;
5403 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5404 let min_word_search = buffer_snapshot.clip_point(
5405 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5406 Bias::Left,
5407 );
5408 let max_word_search = buffer_snapshot.clip_point(
5409 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5410 Bias::Right,
5411 );
5412 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5413 ..buffer_snapshot.point_to_offset(max_word_search);
5414
5415 let skip_digits = query
5416 .as_ref()
5417 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5418
5419 let (mut words, provider_responses) = match &provider {
5420 Some(provider) => {
5421 let provider_responses = provider.completions(
5422 position.excerpt_id,
5423 &buffer,
5424 buffer_position,
5425 completion_context,
5426 window,
5427 cx,
5428 );
5429
5430 let words = match completion_settings.words {
5431 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5432 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5433 .background_spawn(async move {
5434 buffer_snapshot.words_in_range(WordsQuery {
5435 fuzzy_contents: None,
5436 range: word_search_range,
5437 skip_digits,
5438 })
5439 }),
5440 };
5441
5442 (words, provider_responses)
5443 }
5444 None => (
5445 cx.background_spawn(async move {
5446 buffer_snapshot.words_in_range(WordsQuery {
5447 fuzzy_contents: None,
5448 range: word_search_range,
5449 skip_digits,
5450 })
5451 }),
5452 Task::ready(Ok(Vec::new())),
5453 ),
5454 };
5455
5456 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5457
5458 let id = post_inc(&mut self.next_completion_id);
5459 let task = cx.spawn_in(window, async move |editor, cx| {
5460 let Ok(()) = editor.update(cx, |this, _| {
5461 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5462 }) else {
5463 return;
5464 };
5465
5466 // TODO: Ideally completions from different sources would be selectively re-queried, so
5467 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5468 let mut completions = Vec::new();
5469 let mut is_incomplete = false;
5470 if let Some(provider_responses) = provider_responses.await.log_err() {
5471 if !provider_responses.is_empty() {
5472 for response in provider_responses {
5473 completions.extend(response.completions);
5474 is_incomplete = is_incomplete || response.is_incomplete;
5475 }
5476 if completion_settings.words == WordsCompletionMode::Fallback {
5477 words = Task::ready(BTreeMap::default());
5478 }
5479 }
5480 }
5481
5482 let mut words = words.await;
5483 if let Some(word_to_exclude) = &word_to_exclude {
5484 words.remove(word_to_exclude);
5485 }
5486 for lsp_completion in &completions {
5487 words.remove(&lsp_completion.new_text);
5488 }
5489 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5490 replace_range: word_replace_range.clone(),
5491 new_text: word.clone(),
5492 label: CodeLabel::plain(word, None),
5493 icon_path: None,
5494 documentation: None,
5495 source: CompletionSource::BufferWord {
5496 word_range,
5497 resolved: false,
5498 },
5499 insert_text_mode: Some(InsertTextMode::AS_IS),
5500 confirm: None,
5501 }));
5502
5503 let menu = if completions.is_empty() {
5504 None
5505 } else {
5506 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5507 let languages = editor
5508 .workspace
5509 .as_ref()
5510 .and_then(|(workspace, _)| workspace.upgrade())
5511 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5512 let menu = CompletionsMenu::new(
5513 id,
5514 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5515 sort_completions,
5516 show_completion_documentation,
5517 position,
5518 query.clone(),
5519 is_incomplete,
5520 buffer.clone(),
5521 completions.into(),
5522 snippet_sort_order,
5523 languages,
5524 language,
5525 cx,
5526 );
5527
5528 let query = if filter_completions { query } else { None };
5529 let matches_task = if let Some(query) = query {
5530 menu.do_async_filtering(query, cx)
5531 } else {
5532 Task::ready(menu.unfiltered_matches())
5533 };
5534 (menu, matches_task)
5535 }) else {
5536 return;
5537 };
5538
5539 let matches = matches_task.await;
5540
5541 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5542 // Newer menu already set, so exit.
5543 match editor.context_menu.borrow().as_ref() {
5544 Some(CodeContextMenu::Completions(prev_menu)) => {
5545 if prev_menu.id > id {
5546 return;
5547 }
5548 }
5549 _ => {}
5550 };
5551
5552 // Only valid to take prev_menu because it the new menu is immediately set
5553 // below, or the menu is hidden.
5554 match editor.context_menu.borrow_mut().take() {
5555 Some(CodeContextMenu::Completions(prev_menu)) => {
5556 let position_matches =
5557 if prev_menu.initial_position == menu.initial_position {
5558 true
5559 } else {
5560 let snapshot = editor.buffer.read(cx).read(cx);
5561 prev_menu.initial_position.to_offset(&snapshot)
5562 == menu.initial_position.to_offset(&snapshot)
5563 };
5564 if position_matches {
5565 // Preserve markdown cache before `set_filter_results` because it will
5566 // try to populate the documentation cache.
5567 menu.preserve_markdown_cache(prev_menu);
5568 }
5569 }
5570 _ => {}
5571 };
5572
5573 menu.set_filter_results(matches, provider, window, cx);
5574 }) else {
5575 return;
5576 };
5577
5578 menu.visible().then_some(menu)
5579 };
5580
5581 editor
5582 .update_in(cx, |editor, window, cx| {
5583 if editor.focus_handle.is_focused(window) {
5584 if let Some(menu) = menu {
5585 *editor.context_menu.borrow_mut() =
5586 Some(CodeContextMenu::Completions(menu));
5587
5588 crate::hover_popover::hide_hover(editor, cx);
5589 if editor.show_edit_predictions_in_menu() {
5590 editor.update_visible_inline_completion(window, cx);
5591 } else {
5592 editor.discard_inline_completion(false, cx);
5593 }
5594
5595 cx.notify();
5596 return;
5597 }
5598 }
5599
5600 if editor.completion_tasks.len() <= 1 {
5601 // If there are no more completion tasks and the last menu was empty, we should hide it.
5602 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5603 // If it was already hidden and we don't show inline completions in the menu, we should
5604 // also show the inline-completion when available.
5605 if was_hidden && editor.show_edit_predictions_in_menu() {
5606 editor.update_visible_inline_completion(window, cx);
5607 }
5608 }
5609 })
5610 .ok();
5611 });
5612
5613 self.completion_tasks.push((id, task));
5614 }
5615
5616 #[cfg(feature = "test-support")]
5617 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5618 let menu = self.context_menu.borrow();
5619 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5620 let completions = menu.completions.borrow();
5621 Some(completions.to_vec())
5622 } else {
5623 None
5624 }
5625 }
5626
5627 pub fn with_completions_menu_matching_id<R>(
5628 &self,
5629 id: CompletionId,
5630 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5631 ) -> R {
5632 let mut context_menu = self.context_menu.borrow_mut();
5633 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5634 return f(None);
5635 };
5636 if completions_menu.id != id {
5637 return f(None);
5638 }
5639 f(Some(completions_menu))
5640 }
5641
5642 pub fn confirm_completion(
5643 &mut self,
5644 action: &ConfirmCompletion,
5645 window: &mut Window,
5646 cx: &mut Context<Self>,
5647 ) -> Option<Task<Result<()>>> {
5648 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5649 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5650 }
5651
5652 pub fn confirm_completion_insert(
5653 &mut self,
5654 _: &ConfirmCompletionInsert,
5655 window: &mut Window,
5656 cx: &mut Context<Self>,
5657 ) -> Option<Task<Result<()>>> {
5658 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5659 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5660 }
5661
5662 pub fn confirm_completion_replace(
5663 &mut self,
5664 _: &ConfirmCompletionReplace,
5665 window: &mut Window,
5666 cx: &mut Context<Self>,
5667 ) -> Option<Task<Result<()>>> {
5668 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5669 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5670 }
5671
5672 pub fn compose_completion(
5673 &mut self,
5674 action: &ComposeCompletion,
5675 window: &mut Window,
5676 cx: &mut Context<Self>,
5677 ) -> Option<Task<Result<()>>> {
5678 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5679 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5680 }
5681
5682 fn do_completion(
5683 &mut self,
5684 item_ix: Option<usize>,
5685 intent: CompletionIntent,
5686 window: &mut Window,
5687 cx: &mut Context<Editor>,
5688 ) -> Option<Task<Result<()>>> {
5689 use language::ToOffset as _;
5690
5691 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5692 else {
5693 return None;
5694 };
5695
5696 let candidate_id = {
5697 let entries = completions_menu.entries.borrow();
5698 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5699 if self.show_edit_predictions_in_menu() {
5700 self.discard_inline_completion(true, cx);
5701 }
5702 mat.candidate_id
5703 };
5704
5705 let completion = completions_menu
5706 .completions
5707 .borrow()
5708 .get(candidate_id)?
5709 .clone();
5710 cx.stop_propagation();
5711
5712 let buffer_handle = completions_menu.buffer.clone();
5713
5714 let CompletionEdit {
5715 new_text,
5716 snippet,
5717 replace_range,
5718 } = process_completion_for_edit(
5719 &completion,
5720 intent,
5721 &buffer_handle,
5722 &completions_menu.initial_position.text_anchor,
5723 cx,
5724 );
5725
5726 let buffer = buffer_handle.read(cx);
5727 let snapshot = self.buffer.read(cx).snapshot(cx);
5728 let newest_anchor = self.selections.newest_anchor();
5729 let replace_range_multibuffer = {
5730 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5731 let multibuffer_anchor = snapshot
5732 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5733 .unwrap()
5734 ..snapshot
5735 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5736 .unwrap();
5737 multibuffer_anchor.start.to_offset(&snapshot)
5738 ..multibuffer_anchor.end.to_offset(&snapshot)
5739 };
5740 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5741 return None;
5742 }
5743
5744 let old_text = buffer
5745 .text_for_range(replace_range.clone())
5746 .collect::<String>();
5747 let lookbehind = newest_anchor
5748 .start
5749 .text_anchor
5750 .to_offset(buffer)
5751 .saturating_sub(replace_range.start);
5752 let lookahead = replace_range
5753 .end
5754 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5755 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5756 let suffix = &old_text[lookbehind.min(old_text.len())..];
5757
5758 let selections = self.selections.all::<usize>(cx);
5759 let mut ranges = Vec::new();
5760 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5761
5762 for selection in &selections {
5763 let range = if selection.id == newest_anchor.id {
5764 replace_range_multibuffer.clone()
5765 } else {
5766 let mut range = selection.range();
5767
5768 // if prefix is present, don't duplicate it
5769 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5770 range.start = range.start.saturating_sub(lookbehind);
5771
5772 // if suffix is also present, mimic the newest cursor and replace it
5773 if selection.id != newest_anchor.id
5774 && snapshot.contains_str_at(range.end, suffix)
5775 {
5776 range.end += lookahead;
5777 }
5778 }
5779 range
5780 };
5781
5782 ranges.push(range.clone());
5783
5784 if !self.linked_edit_ranges.is_empty() {
5785 let start_anchor = snapshot.anchor_before(range.start);
5786 let end_anchor = snapshot.anchor_after(range.end);
5787 if let Some(ranges) = self
5788 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5789 {
5790 for (buffer, edits) in ranges {
5791 linked_edits
5792 .entry(buffer.clone())
5793 .or_default()
5794 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5795 }
5796 }
5797 }
5798 }
5799
5800 let common_prefix_len = old_text
5801 .chars()
5802 .zip(new_text.chars())
5803 .take_while(|(a, b)| a == b)
5804 .map(|(a, _)| a.len_utf8())
5805 .sum::<usize>();
5806
5807 cx.emit(EditorEvent::InputHandled {
5808 utf16_range_to_replace: None,
5809 text: new_text[common_prefix_len..].into(),
5810 });
5811
5812 self.transact(window, cx, |this, window, cx| {
5813 if let Some(mut snippet) = snippet {
5814 snippet.text = new_text.to_string();
5815 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5816 } else {
5817 this.buffer.update(cx, |buffer, cx| {
5818 let auto_indent = match completion.insert_text_mode {
5819 Some(InsertTextMode::AS_IS) => None,
5820 _ => this.autoindent_mode.clone(),
5821 };
5822 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5823 buffer.edit(edits, auto_indent, cx);
5824 });
5825 }
5826 for (buffer, edits) in linked_edits {
5827 buffer.update(cx, |buffer, cx| {
5828 let snapshot = buffer.snapshot();
5829 let edits = edits
5830 .into_iter()
5831 .map(|(range, text)| {
5832 use text::ToPoint as TP;
5833 let end_point = TP::to_point(&range.end, &snapshot);
5834 let start_point = TP::to_point(&range.start, &snapshot);
5835 (start_point..end_point, text)
5836 })
5837 .sorted_by_key(|(range, _)| range.start);
5838 buffer.edit(edits, None, cx);
5839 })
5840 }
5841
5842 this.refresh_inline_completion(true, false, window, cx);
5843 });
5844
5845 let show_new_completions_on_confirm = completion
5846 .confirm
5847 .as_ref()
5848 .map_or(false, |confirm| confirm(intent, window, cx));
5849 if show_new_completions_on_confirm {
5850 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5851 }
5852
5853 let provider = self.completion_provider.as_ref()?;
5854 drop(completion);
5855 let apply_edits = provider.apply_additional_edits_for_completion(
5856 buffer_handle,
5857 completions_menu.completions.clone(),
5858 candidate_id,
5859 true,
5860 cx,
5861 );
5862
5863 let editor_settings = EditorSettings::get_global(cx);
5864 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5865 // After the code completion is finished, users often want to know what signatures are needed.
5866 // so we should automatically call signature_help
5867 self.show_signature_help(&ShowSignatureHelp, window, cx);
5868 }
5869
5870 Some(cx.foreground_executor().spawn(async move {
5871 apply_edits.await?;
5872 Ok(())
5873 }))
5874 }
5875
5876 pub fn toggle_code_actions(
5877 &mut self,
5878 action: &ToggleCodeActions,
5879 window: &mut Window,
5880 cx: &mut Context<Self>,
5881 ) {
5882 let quick_launch = action.quick_launch;
5883 let mut context_menu = self.context_menu.borrow_mut();
5884 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5885 if code_actions.deployed_from == action.deployed_from {
5886 // Toggle if we're selecting the same one
5887 *context_menu = None;
5888 cx.notify();
5889 return;
5890 } else {
5891 // Otherwise, clear it and start a new one
5892 *context_menu = None;
5893 cx.notify();
5894 }
5895 }
5896 drop(context_menu);
5897 let snapshot = self.snapshot(window, cx);
5898 let deployed_from = action.deployed_from.clone();
5899 let action = action.clone();
5900 self.completion_tasks.clear();
5901 self.discard_inline_completion(false, cx);
5902
5903 let multibuffer_point = match &action.deployed_from {
5904 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5905 DisplayPoint::new(*row, 0).to_point(&snapshot)
5906 }
5907 _ => self.selections.newest::<Point>(cx).head(),
5908 };
5909 let Some((buffer, buffer_row)) = snapshot
5910 .buffer_snapshot
5911 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5912 .and_then(|(buffer_snapshot, range)| {
5913 self.buffer()
5914 .read(cx)
5915 .buffer(buffer_snapshot.remote_id())
5916 .map(|buffer| (buffer, range.start.row))
5917 })
5918 else {
5919 return;
5920 };
5921 let buffer_id = buffer.read(cx).remote_id();
5922 let tasks = self
5923 .tasks
5924 .get(&(buffer_id, buffer_row))
5925 .map(|t| Arc::new(t.to_owned()));
5926
5927 if !self.focus_handle.is_focused(window) {
5928 return;
5929 }
5930 let project = self.project.clone();
5931
5932 let code_actions_task = match deployed_from {
5933 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
5934 _ => self.code_actions(buffer_row, window, cx),
5935 };
5936
5937 let runnable_task = match deployed_from {
5938 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
5939 _ => {
5940 let mut task_context_task = Task::ready(None);
5941 if let Some(tasks) = &tasks {
5942 if let Some(project) = project {
5943 task_context_task =
5944 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
5945 }
5946 }
5947
5948 cx.spawn_in(window, {
5949 let buffer = buffer.clone();
5950 async move |editor, cx| {
5951 let task_context = task_context_task.await;
5952
5953 let resolved_tasks =
5954 tasks
5955 .zip(task_context.clone())
5956 .map(|(tasks, task_context)| ResolvedTasks {
5957 templates: tasks.resolve(&task_context).collect(),
5958 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5959 multibuffer_point.row,
5960 tasks.column,
5961 )),
5962 });
5963 let debug_scenarios = editor
5964 .update(cx, |editor, cx| {
5965 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
5966 })?
5967 .await;
5968 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
5969 }
5970 })
5971 }
5972 };
5973
5974 cx.spawn_in(window, async move |editor, cx| {
5975 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
5976 let code_actions = code_actions_task.await;
5977 let spawn_straight_away = quick_launch
5978 && resolved_tasks
5979 .as_ref()
5980 .map_or(false, |tasks| tasks.templates.len() == 1)
5981 && code_actions
5982 .as_ref()
5983 .map_or(true, |actions| actions.is_empty())
5984 && debug_scenarios.is_empty();
5985
5986 editor.update_in(cx, |editor, window, cx| {
5987 crate::hover_popover::hide_hover(editor, cx);
5988 let actions = CodeActionContents::new(
5989 resolved_tasks,
5990 code_actions,
5991 debug_scenarios,
5992 task_context.unwrap_or_default(),
5993 );
5994
5995 // Don't show the menu if there are no actions available
5996 if actions.is_empty() {
5997 cx.notify();
5998 return Task::ready(Ok(()));
5999 }
6000
6001 *editor.context_menu.borrow_mut() =
6002 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6003 buffer,
6004 actions,
6005 selected_item: Default::default(),
6006 scroll_handle: UniformListScrollHandle::default(),
6007 deployed_from,
6008 }));
6009 cx.notify();
6010 if spawn_straight_away {
6011 if let Some(task) = editor.confirm_code_action(
6012 &ConfirmCodeAction { item_ix: Some(0) },
6013 window,
6014 cx,
6015 ) {
6016 return task;
6017 }
6018 }
6019
6020 Task::ready(Ok(()))
6021 })
6022 })
6023 .detach_and_log_err(cx);
6024 }
6025
6026 fn debug_scenarios(
6027 &mut self,
6028 resolved_tasks: &Option<ResolvedTasks>,
6029 buffer: &Entity<Buffer>,
6030 cx: &mut App,
6031 ) -> Task<Vec<task::DebugScenario>> {
6032 maybe!({
6033 let project = self.project.as_ref()?;
6034 let dap_store = project.read(cx).dap_store();
6035 let mut scenarios = vec![];
6036 let resolved_tasks = resolved_tasks.as_ref()?;
6037 let buffer = buffer.read(cx);
6038 let language = buffer.language()?;
6039 let file = buffer.file();
6040 let debug_adapter = language_settings(language.name().into(), file, cx)
6041 .debuggers
6042 .first()
6043 .map(SharedString::from)
6044 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6045
6046 dap_store.update(cx, |dap_store, cx| {
6047 for (_, task) in &resolved_tasks.templates {
6048 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6049 task.original_task().clone(),
6050 debug_adapter.clone().into(),
6051 task.display_label().to_owned().into(),
6052 cx,
6053 );
6054 scenarios.push(maybe_scenario);
6055 }
6056 });
6057 Some(cx.background_spawn(async move {
6058 let scenarios = futures::future::join_all(scenarios)
6059 .await
6060 .into_iter()
6061 .flatten()
6062 .collect::<Vec<_>>();
6063 scenarios
6064 }))
6065 })
6066 .unwrap_or_else(|| Task::ready(vec![]))
6067 }
6068
6069 fn code_actions(
6070 &mut self,
6071 buffer_row: u32,
6072 window: &mut Window,
6073 cx: &mut Context<Self>,
6074 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6075 let mut task = self.code_actions_task.take();
6076 cx.spawn_in(window, async move |editor, cx| {
6077 while let Some(prev_task) = task {
6078 prev_task.await.log_err();
6079 task = editor
6080 .update(cx, |this, _| this.code_actions_task.take())
6081 .ok()?;
6082 }
6083
6084 editor
6085 .update(cx, |editor, cx| {
6086 editor
6087 .available_code_actions
6088 .clone()
6089 .and_then(|(location, code_actions)| {
6090 let snapshot = location.buffer.read(cx).snapshot();
6091 let point_range = location.range.to_point(&snapshot);
6092 let point_range = point_range.start.row..=point_range.end.row;
6093 if point_range.contains(&buffer_row) {
6094 Some(code_actions)
6095 } else {
6096 None
6097 }
6098 })
6099 })
6100 .ok()
6101 .flatten()
6102 })
6103 }
6104
6105 pub fn confirm_code_action(
6106 &mut self,
6107 action: &ConfirmCodeAction,
6108 window: &mut Window,
6109 cx: &mut Context<Self>,
6110 ) -> Option<Task<Result<()>>> {
6111 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6112
6113 let actions_menu =
6114 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6115 menu
6116 } else {
6117 return None;
6118 };
6119
6120 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6121 let action = actions_menu.actions.get(action_ix)?;
6122 let title = action.label();
6123 let buffer = actions_menu.buffer;
6124 let workspace = self.workspace()?;
6125
6126 match action {
6127 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6128 workspace.update(cx, |workspace, cx| {
6129 workspace.schedule_resolved_task(
6130 task_source_kind,
6131 resolved_task,
6132 false,
6133 window,
6134 cx,
6135 );
6136
6137 Some(Task::ready(Ok(())))
6138 })
6139 }
6140 CodeActionsItem::CodeAction {
6141 excerpt_id,
6142 action,
6143 provider,
6144 } => {
6145 let apply_code_action =
6146 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6147 let workspace = workspace.downgrade();
6148 Some(cx.spawn_in(window, async move |editor, cx| {
6149 let project_transaction = apply_code_action.await?;
6150 Self::open_project_transaction(
6151 &editor,
6152 workspace,
6153 project_transaction,
6154 title,
6155 cx,
6156 )
6157 .await
6158 }))
6159 }
6160 CodeActionsItem::DebugScenario(scenario) => {
6161 let context = actions_menu.actions.context.clone();
6162
6163 workspace.update(cx, |workspace, cx| {
6164 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6165 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
6166 });
6167 Some(Task::ready(Ok(())))
6168 }
6169 }
6170 }
6171
6172 pub async fn open_project_transaction(
6173 this: &WeakEntity<Editor>,
6174 workspace: WeakEntity<Workspace>,
6175 transaction: ProjectTransaction,
6176 title: String,
6177 cx: &mut AsyncWindowContext,
6178 ) -> Result<()> {
6179 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6180 cx.update(|_, cx| {
6181 entries.sort_unstable_by_key(|(buffer, _)| {
6182 buffer.read(cx).file().map(|f| f.path().clone())
6183 });
6184 })?;
6185
6186 // If the project transaction's edits are all contained within this editor, then
6187 // avoid opening a new editor to display them.
6188
6189 if let Some((buffer, transaction)) = entries.first() {
6190 if entries.len() == 1 {
6191 let excerpt = this.update(cx, |editor, cx| {
6192 editor
6193 .buffer()
6194 .read(cx)
6195 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6196 })?;
6197 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6198 if excerpted_buffer == *buffer {
6199 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6200 let excerpt_range = excerpt_range.to_offset(buffer);
6201 buffer
6202 .edited_ranges_for_transaction::<usize>(transaction)
6203 .all(|range| {
6204 excerpt_range.start <= range.start
6205 && excerpt_range.end >= range.end
6206 })
6207 })?;
6208
6209 if all_edits_within_excerpt {
6210 return Ok(());
6211 }
6212 }
6213 }
6214 }
6215 } else {
6216 return Ok(());
6217 }
6218
6219 let mut ranges_to_highlight = Vec::new();
6220 let excerpt_buffer = cx.new(|cx| {
6221 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6222 for (buffer_handle, transaction) in &entries {
6223 let edited_ranges = buffer_handle
6224 .read(cx)
6225 .edited_ranges_for_transaction::<Point>(transaction)
6226 .collect::<Vec<_>>();
6227 let (ranges, _) = multibuffer.set_excerpts_for_path(
6228 PathKey::for_buffer(buffer_handle, cx),
6229 buffer_handle.clone(),
6230 edited_ranges,
6231 DEFAULT_MULTIBUFFER_CONTEXT,
6232 cx,
6233 );
6234
6235 ranges_to_highlight.extend(ranges);
6236 }
6237 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6238 multibuffer
6239 })?;
6240
6241 workspace.update_in(cx, |workspace, window, cx| {
6242 let project = workspace.project().clone();
6243 let editor =
6244 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6245 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6246 editor.update(cx, |editor, cx| {
6247 editor.highlight_background::<Self>(
6248 &ranges_to_highlight,
6249 |theme| theme.colors().editor_highlighted_line_background,
6250 cx,
6251 );
6252 });
6253 })?;
6254
6255 Ok(())
6256 }
6257
6258 pub fn clear_code_action_providers(&mut self) {
6259 self.code_action_providers.clear();
6260 self.available_code_actions.take();
6261 }
6262
6263 pub fn add_code_action_provider(
6264 &mut self,
6265 provider: Rc<dyn CodeActionProvider>,
6266 window: &mut Window,
6267 cx: &mut Context<Self>,
6268 ) {
6269 if self
6270 .code_action_providers
6271 .iter()
6272 .any(|existing_provider| existing_provider.id() == provider.id())
6273 {
6274 return;
6275 }
6276
6277 self.code_action_providers.push(provider);
6278 self.refresh_code_actions(window, cx);
6279 }
6280
6281 pub fn remove_code_action_provider(
6282 &mut self,
6283 id: Arc<str>,
6284 window: &mut Window,
6285 cx: &mut Context<Self>,
6286 ) {
6287 self.code_action_providers
6288 .retain(|provider| provider.id() != id);
6289 self.refresh_code_actions(window, cx);
6290 }
6291
6292 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6293 !self.code_action_providers.is_empty()
6294 && EditorSettings::get_global(cx).toolbar.code_actions
6295 }
6296
6297 pub fn has_available_code_actions(&self) -> bool {
6298 self.available_code_actions
6299 .as_ref()
6300 .is_some_and(|(_, actions)| !actions.is_empty())
6301 }
6302
6303 fn render_inline_code_actions(
6304 &self,
6305 icon_size: ui::IconSize,
6306 display_row: DisplayRow,
6307 is_active: bool,
6308 cx: &mut Context<Self>,
6309 ) -> AnyElement {
6310 let show_tooltip = !self.context_menu_visible();
6311 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6312 .icon_size(icon_size)
6313 .shape(ui::IconButtonShape::Square)
6314 .style(ButtonStyle::Transparent)
6315 .icon_color(ui::Color::Hidden)
6316 .toggle_state(is_active)
6317 .when(show_tooltip, |this| {
6318 this.tooltip({
6319 let focus_handle = self.focus_handle.clone();
6320 move |window, cx| {
6321 Tooltip::for_action_in(
6322 "Toggle Code Actions",
6323 &ToggleCodeActions {
6324 deployed_from: None,
6325 quick_launch: false,
6326 },
6327 &focus_handle,
6328 window,
6329 cx,
6330 )
6331 }
6332 })
6333 })
6334 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6335 window.focus(&editor.focus_handle(cx));
6336 editor.toggle_code_actions(
6337 &crate::actions::ToggleCodeActions {
6338 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6339 display_row,
6340 )),
6341 quick_launch: false,
6342 },
6343 window,
6344 cx,
6345 );
6346 }))
6347 .into_any_element()
6348 }
6349
6350 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6351 &self.context_menu
6352 }
6353
6354 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6355 let newest_selection = self.selections.newest_anchor().clone();
6356 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6357 let buffer = self.buffer.read(cx);
6358 if newest_selection.head().diff_base_anchor.is_some() {
6359 return None;
6360 }
6361 let (start_buffer, start) =
6362 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6363 let (end_buffer, end) =
6364 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6365 if start_buffer != end_buffer {
6366 return None;
6367 }
6368
6369 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6370 cx.background_executor()
6371 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6372 .await;
6373
6374 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6375 let providers = this.code_action_providers.clone();
6376 let tasks = this
6377 .code_action_providers
6378 .iter()
6379 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6380 .collect::<Vec<_>>();
6381 (providers, tasks)
6382 })?;
6383
6384 let mut actions = Vec::new();
6385 for (provider, provider_actions) in
6386 providers.into_iter().zip(future::join_all(tasks).await)
6387 {
6388 if let Some(provider_actions) = provider_actions.log_err() {
6389 actions.extend(provider_actions.into_iter().map(|action| {
6390 AvailableCodeAction {
6391 excerpt_id: newest_selection.start.excerpt_id,
6392 action,
6393 provider: provider.clone(),
6394 }
6395 }));
6396 }
6397 }
6398
6399 this.update(cx, |this, cx| {
6400 this.available_code_actions = if actions.is_empty() {
6401 None
6402 } else {
6403 Some((
6404 Location {
6405 buffer: start_buffer,
6406 range: start..end,
6407 },
6408 actions.into(),
6409 ))
6410 };
6411 cx.notify();
6412 })
6413 }));
6414 None
6415 }
6416
6417 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6418 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6419 self.show_git_blame_inline = false;
6420
6421 self.show_git_blame_inline_delay_task =
6422 Some(cx.spawn_in(window, async move |this, cx| {
6423 cx.background_executor().timer(delay).await;
6424
6425 this.update(cx, |this, cx| {
6426 this.show_git_blame_inline = true;
6427 cx.notify();
6428 })
6429 .log_err();
6430 }));
6431 }
6432 }
6433
6434 fn show_blame_popover(
6435 &mut self,
6436 blame_entry: &BlameEntry,
6437 position: gpui::Point<Pixels>,
6438 cx: &mut Context<Self>,
6439 ) {
6440 if let Some(state) = &mut self.inline_blame_popover {
6441 state.hide_task.take();
6442 } else {
6443 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6444 let blame_entry = blame_entry.clone();
6445 let show_task = cx.spawn(async move |editor, cx| {
6446 cx.background_executor()
6447 .timer(std::time::Duration::from_millis(delay))
6448 .await;
6449 editor
6450 .update(cx, |editor, cx| {
6451 editor.inline_blame_popover_show_task.take();
6452 let Some(blame) = editor.blame.as_ref() else {
6453 return;
6454 };
6455 let blame = blame.read(cx);
6456 let details = blame.details_for_entry(&blame_entry);
6457 let markdown = cx.new(|cx| {
6458 Markdown::new(
6459 details
6460 .as_ref()
6461 .map(|message| message.message.clone())
6462 .unwrap_or_default(),
6463 None,
6464 None,
6465 cx,
6466 )
6467 });
6468 editor.inline_blame_popover = Some(InlineBlamePopover {
6469 position,
6470 hide_task: None,
6471 popover_bounds: None,
6472 popover_state: InlineBlamePopoverState {
6473 scroll_handle: ScrollHandle::new(),
6474 commit_message: details,
6475 markdown,
6476 },
6477 });
6478 cx.notify();
6479 })
6480 .ok();
6481 });
6482 self.inline_blame_popover_show_task = Some(show_task);
6483 }
6484 }
6485
6486 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6487 self.inline_blame_popover_show_task.take();
6488 if let Some(state) = &mut self.inline_blame_popover {
6489 let hide_task = cx.spawn(async move |editor, cx| {
6490 cx.background_executor()
6491 .timer(std::time::Duration::from_millis(100))
6492 .await;
6493 editor
6494 .update(cx, |editor, cx| {
6495 editor.inline_blame_popover.take();
6496 cx.notify();
6497 })
6498 .ok();
6499 });
6500 state.hide_task = Some(hide_task);
6501 }
6502 }
6503
6504 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6505 if self.pending_rename.is_some() {
6506 return None;
6507 }
6508
6509 let provider = self.semantics_provider.clone()?;
6510 let buffer = self.buffer.read(cx);
6511 let newest_selection = self.selections.newest_anchor().clone();
6512 let cursor_position = newest_selection.head();
6513 let (cursor_buffer, cursor_buffer_position) =
6514 buffer.text_anchor_for_position(cursor_position, cx)?;
6515 let (tail_buffer, tail_buffer_position) =
6516 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6517 if cursor_buffer != tail_buffer {
6518 return None;
6519 }
6520
6521 let snapshot = cursor_buffer.read(cx).snapshot();
6522 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6523 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6524 if start_word_range != end_word_range {
6525 self.document_highlights_task.take();
6526 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6527 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6528 return None;
6529 }
6530
6531 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6532 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6533 cx.background_executor()
6534 .timer(Duration::from_millis(debounce))
6535 .await;
6536
6537 let highlights = if let Some(highlights) = cx
6538 .update(|cx| {
6539 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6540 })
6541 .ok()
6542 .flatten()
6543 {
6544 highlights.await.log_err()
6545 } else {
6546 None
6547 };
6548
6549 if let Some(highlights) = highlights {
6550 this.update(cx, |this, cx| {
6551 if this.pending_rename.is_some() {
6552 return;
6553 }
6554
6555 let buffer_id = cursor_position.buffer_id;
6556 let buffer = this.buffer.read(cx);
6557 if !buffer
6558 .text_anchor_for_position(cursor_position, cx)
6559 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6560 {
6561 return;
6562 }
6563
6564 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6565 let mut write_ranges = Vec::new();
6566 let mut read_ranges = Vec::new();
6567 for highlight in highlights {
6568 for (excerpt_id, excerpt_range) in
6569 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6570 {
6571 let start = highlight
6572 .range
6573 .start
6574 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6575 let end = highlight
6576 .range
6577 .end
6578 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6579 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6580 continue;
6581 }
6582
6583 let range = Anchor {
6584 buffer_id,
6585 excerpt_id,
6586 text_anchor: start,
6587 diff_base_anchor: None,
6588 }..Anchor {
6589 buffer_id,
6590 excerpt_id,
6591 text_anchor: end,
6592 diff_base_anchor: None,
6593 };
6594 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6595 write_ranges.push(range);
6596 } else {
6597 read_ranges.push(range);
6598 }
6599 }
6600 }
6601
6602 this.highlight_background::<DocumentHighlightRead>(
6603 &read_ranges,
6604 |theme| theme.colors().editor_document_highlight_read_background,
6605 cx,
6606 );
6607 this.highlight_background::<DocumentHighlightWrite>(
6608 &write_ranges,
6609 |theme| theme.colors().editor_document_highlight_write_background,
6610 cx,
6611 );
6612 cx.notify();
6613 })
6614 .log_err();
6615 }
6616 }));
6617 None
6618 }
6619
6620 fn prepare_highlight_query_from_selection(
6621 &mut self,
6622 cx: &mut Context<Editor>,
6623 ) -> Option<(String, Range<Anchor>)> {
6624 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6625 return None;
6626 }
6627 if !EditorSettings::get_global(cx).selection_highlight {
6628 return None;
6629 }
6630 if self.selections.count() != 1 || self.selections.line_mode {
6631 return None;
6632 }
6633 let selection = self.selections.newest::<Point>(cx);
6634 if selection.is_empty() || selection.start.row != selection.end.row {
6635 return None;
6636 }
6637 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6638 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6639 let query = multi_buffer_snapshot
6640 .text_for_range(selection_anchor_range.clone())
6641 .collect::<String>();
6642 if query.trim().is_empty() {
6643 return None;
6644 }
6645 Some((query, selection_anchor_range))
6646 }
6647
6648 fn update_selection_occurrence_highlights(
6649 &mut self,
6650 query_text: String,
6651 query_range: Range<Anchor>,
6652 multi_buffer_range_to_query: Range<Point>,
6653 use_debounce: bool,
6654 window: &mut Window,
6655 cx: &mut Context<Editor>,
6656 ) -> Task<()> {
6657 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6658 cx.spawn_in(window, async move |editor, cx| {
6659 if use_debounce {
6660 cx.background_executor()
6661 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6662 .await;
6663 }
6664 let match_task = cx.background_spawn(async move {
6665 let buffer_ranges = multi_buffer_snapshot
6666 .range_to_buffer_ranges(multi_buffer_range_to_query)
6667 .into_iter()
6668 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6669 let mut match_ranges = Vec::new();
6670 let Ok(regex) = project::search::SearchQuery::text(
6671 query_text.clone(),
6672 false,
6673 false,
6674 false,
6675 Default::default(),
6676 Default::default(),
6677 false,
6678 None,
6679 ) else {
6680 return Vec::default();
6681 };
6682 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6683 match_ranges.extend(
6684 regex
6685 .search(&buffer_snapshot, Some(search_range.clone()))
6686 .await
6687 .into_iter()
6688 .filter_map(|match_range| {
6689 let match_start = buffer_snapshot
6690 .anchor_after(search_range.start + match_range.start);
6691 let match_end = buffer_snapshot
6692 .anchor_before(search_range.start + match_range.end);
6693 let match_anchor_range = Anchor::range_in_buffer(
6694 excerpt_id,
6695 buffer_snapshot.remote_id(),
6696 match_start..match_end,
6697 );
6698 (match_anchor_range != query_range).then_some(match_anchor_range)
6699 }),
6700 );
6701 }
6702 match_ranges
6703 });
6704 let match_ranges = match_task.await;
6705 editor
6706 .update_in(cx, |editor, _, cx| {
6707 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6708 if !match_ranges.is_empty() {
6709 editor.highlight_background::<SelectedTextHighlight>(
6710 &match_ranges,
6711 |theme| theme.colors().editor_document_highlight_bracket_background,
6712 cx,
6713 )
6714 }
6715 })
6716 .log_err();
6717 })
6718 }
6719
6720 fn refresh_selected_text_highlights(
6721 &mut self,
6722 on_buffer_edit: bool,
6723 window: &mut Window,
6724 cx: &mut Context<Editor>,
6725 ) {
6726 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6727 else {
6728 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6729 self.quick_selection_highlight_task.take();
6730 self.debounced_selection_highlight_task.take();
6731 return;
6732 };
6733 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6734 if on_buffer_edit
6735 || self
6736 .quick_selection_highlight_task
6737 .as_ref()
6738 .map_or(true, |(prev_anchor_range, _)| {
6739 prev_anchor_range != &query_range
6740 })
6741 {
6742 let multi_buffer_visible_start = self
6743 .scroll_manager
6744 .anchor()
6745 .anchor
6746 .to_point(&multi_buffer_snapshot);
6747 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6748 multi_buffer_visible_start
6749 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6750 Bias::Left,
6751 );
6752 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6753 self.quick_selection_highlight_task = Some((
6754 query_range.clone(),
6755 self.update_selection_occurrence_highlights(
6756 query_text.clone(),
6757 query_range.clone(),
6758 multi_buffer_visible_range,
6759 false,
6760 window,
6761 cx,
6762 ),
6763 ));
6764 }
6765 if on_buffer_edit
6766 || self
6767 .debounced_selection_highlight_task
6768 .as_ref()
6769 .map_or(true, |(prev_anchor_range, _)| {
6770 prev_anchor_range != &query_range
6771 })
6772 {
6773 let multi_buffer_start = multi_buffer_snapshot
6774 .anchor_before(0)
6775 .to_point(&multi_buffer_snapshot);
6776 let multi_buffer_end = multi_buffer_snapshot
6777 .anchor_after(multi_buffer_snapshot.len())
6778 .to_point(&multi_buffer_snapshot);
6779 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6780 self.debounced_selection_highlight_task = Some((
6781 query_range.clone(),
6782 self.update_selection_occurrence_highlights(
6783 query_text,
6784 query_range,
6785 multi_buffer_full_range,
6786 true,
6787 window,
6788 cx,
6789 ),
6790 ));
6791 }
6792 }
6793
6794 pub fn refresh_inline_completion(
6795 &mut self,
6796 debounce: bool,
6797 user_requested: bool,
6798 window: &mut Window,
6799 cx: &mut Context<Self>,
6800 ) -> Option<()> {
6801 let provider = self.edit_prediction_provider()?;
6802 let cursor = self.selections.newest_anchor().head();
6803 let (buffer, cursor_buffer_position) =
6804 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6805
6806 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6807 self.discard_inline_completion(false, cx);
6808 return None;
6809 }
6810
6811 if !user_requested
6812 && (!self.should_show_edit_predictions()
6813 || !self.is_focused(window)
6814 || buffer.read(cx).is_empty())
6815 {
6816 self.discard_inline_completion(false, cx);
6817 return None;
6818 }
6819
6820 self.update_visible_inline_completion(window, cx);
6821 provider.refresh(
6822 self.project.clone(),
6823 buffer,
6824 cursor_buffer_position,
6825 debounce,
6826 cx,
6827 );
6828 Some(())
6829 }
6830
6831 fn show_edit_predictions_in_menu(&self) -> bool {
6832 match self.edit_prediction_settings {
6833 EditPredictionSettings::Disabled => false,
6834 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6835 }
6836 }
6837
6838 pub fn edit_predictions_enabled(&self) -> bool {
6839 match self.edit_prediction_settings {
6840 EditPredictionSettings::Disabled => false,
6841 EditPredictionSettings::Enabled { .. } => true,
6842 }
6843 }
6844
6845 fn edit_prediction_requires_modifier(&self) -> bool {
6846 match self.edit_prediction_settings {
6847 EditPredictionSettings::Disabled => false,
6848 EditPredictionSettings::Enabled {
6849 preview_requires_modifier,
6850 ..
6851 } => preview_requires_modifier,
6852 }
6853 }
6854
6855 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6856 if self.edit_prediction_provider.is_none() {
6857 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6858 } else {
6859 let selection = self.selections.newest_anchor();
6860 let cursor = selection.head();
6861
6862 if let Some((buffer, cursor_buffer_position)) =
6863 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6864 {
6865 self.edit_prediction_settings =
6866 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6867 }
6868 }
6869 }
6870
6871 fn edit_prediction_settings_at_position(
6872 &self,
6873 buffer: &Entity<Buffer>,
6874 buffer_position: language::Anchor,
6875 cx: &App,
6876 ) -> EditPredictionSettings {
6877 if !self.mode.is_full()
6878 || !self.show_inline_completions_override.unwrap_or(true)
6879 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6880 {
6881 return EditPredictionSettings::Disabled;
6882 }
6883
6884 let buffer = buffer.read(cx);
6885
6886 let file = buffer.file();
6887
6888 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6889 return EditPredictionSettings::Disabled;
6890 };
6891
6892 let by_provider = matches!(
6893 self.menu_inline_completions_policy,
6894 MenuInlineCompletionsPolicy::ByProvider
6895 );
6896
6897 let show_in_menu = by_provider
6898 && self
6899 .edit_prediction_provider
6900 .as_ref()
6901 .map_or(false, |provider| {
6902 provider.provider.show_completions_in_menu()
6903 });
6904
6905 let preview_requires_modifier =
6906 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6907
6908 EditPredictionSettings::Enabled {
6909 show_in_menu,
6910 preview_requires_modifier,
6911 }
6912 }
6913
6914 fn should_show_edit_predictions(&self) -> bool {
6915 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6916 }
6917
6918 pub fn edit_prediction_preview_is_active(&self) -> bool {
6919 matches!(
6920 self.edit_prediction_preview,
6921 EditPredictionPreview::Active { .. }
6922 )
6923 }
6924
6925 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6926 let cursor = self.selections.newest_anchor().head();
6927 if let Some((buffer, cursor_position)) =
6928 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6929 {
6930 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6931 } else {
6932 false
6933 }
6934 }
6935
6936 pub fn supports_minimap(&self, cx: &App) -> bool {
6937 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6938 }
6939
6940 fn edit_predictions_enabled_in_buffer(
6941 &self,
6942 buffer: &Entity<Buffer>,
6943 buffer_position: language::Anchor,
6944 cx: &App,
6945 ) -> bool {
6946 maybe!({
6947 if self.read_only(cx) {
6948 return Some(false);
6949 }
6950 let provider = self.edit_prediction_provider()?;
6951 if !provider.is_enabled(&buffer, buffer_position, cx) {
6952 return Some(false);
6953 }
6954 let buffer = buffer.read(cx);
6955 let Some(file) = buffer.file() else {
6956 return Some(true);
6957 };
6958 let settings = all_language_settings(Some(file), cx);
6959 Some(settings.edit_predictions_enabled_for_file(file, cx))
6960 })
6961 .unwrap_or(false)
6962 }
6963
6964 fn cycle_inline_completion(
6965 &mut self,
6966 direction: Direction,
6967 window: &mut Window,
6968 cx: &mut Context<Self>,
6969 ) -> Option<()> {
6970 let provider = self.edit_prediction_provider()?;
6971 let cursor = self.selections.newest_anchor().head();
6972 let (buffer, cursor_buffer_position) =
6973 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6974 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6975 return None;
6976 }
6977
6978 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6979 self.update_visible_inline_completion(window, cx);
6980
6981 Some(())
6982 }
6983
6984 pub fn show_inline_completion(
6985 &mut self,
6986 _: &ShowEditPrediction,
6987 window: &mut Window,
6988 cx: &mut Context<Self>,
6989 ) {
6990 if !self.has_active_inline_completion() {
6991 self.refresh_inline_completion(false, true, window, cx);
6992 return;
6993 }
6994
6995 self.update_visible_inline_completion(window, cx);
6996 }
6997
6998 pub fn display_cursor_names(
6999 &mut self,
7000 _: &DisplayCursorNames,
7001 window: &mut Window,
7002 cx: &mut Context<Self>,
7003 ) {
7004 self.show_cursor_names(window, cx);
7005 }
7006
7007 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7008 self.show_cursor_names = true;
7009 cx.notify();
7010 cx.spawn_in(window, async move |this, cx| {
7011 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7012 this.update(cx, |this, cx| {
7013 this.show_cursor_names = false;
7014 cx.notify()
7015 })
7016 .ok()
7017 })
7018 .detach();
7019 }
7020
7021 pub fn next_edit_prediction(
7022 &mut self,
7023 _: &NextEditPrediction,
7024 window: &mut Window,
7025 cx: &mut Context<Self>,
7026 ) {
7027 if self.has_active_inline_completion() {
7028 self.cycle_inline_completion(Direction::Next, window, cx);
7029 } else {
7030 let is_copilot_disabled = self
7031 .refresh_inline_completion(false, true, window, cx)
7032 .is_none();
7033 if is_copilot_disabled {
7034 cx.propagate();
7035 }
7036 }
7037 }
7038
7039 pub fn previous_edit_prediction(
7040 &mut self,
7041 _: &PreviousEditPrediction,
7042 window: &mut Window,
7043 cx: &mut Context<Self>,
7044 ) {
7045 if self.has_active_inline_completion() {
7046 self.cycle_inline_completion(Direction::Prev, window, cx);
7047 } else {
7048 let is_copilot_disabled = self
7049 .refresh_inline_completion(false, true, window, cx)
7050 .is_none();
7051 if is_copilot_disabled {
7052 cx.propagate();
7053 }
7054 }
7055 }
7056
7057 pub fn accept_edit_prediction(
7058 &mut self,
7059 _: &AcceptEditPrediction,
7060 window: &mut Window,
7061 cx: &mut Context<Self>,
7062 ) {
7063 if self.show_edit_predictions_in_menu() {
7064 self.hide_context_menu(window, cx);
7065 }
7066
7067 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7068 return;
7069 };
7070
7071 self.report_inline_completion_event(
7072 active_inline_completion.completion_id.clone(),
7073 true,
7074 cx,
7075 );
7076
7077 match &active_inline_completion.completion {
7078 InlineCompletion::Move { target, .. } => {
7079 let target = *target;
7080
7081 if let Some(position_map) = &self.last_position_map {
7082 if position_map
7083 .visible_row_range
7084 .contains(&target.to_display_point(&position_map.snapshot).row())
7085 || !self.edit_prediction_requires_modifier()
7086 {
7087 self.unfold_ranges(&[target..target], true, false, cx);
7088 // Note that this is also done in vim's handler of the Tab action.
7089 self.change_selections(
7090 Some(Autoscroll::newest()),
7091 window,
7092 cx,
7093 |selections| {
7094 selections.select_anchor_ranges([target..target]);
7095 },
7096 );
7097 self.clear_row_highlights::<EditPredictionPreview>();
7098
7099 self.edit_prediction_preview
7100 .set_previous_scroll_position(None);
7101 } else {
7102 self.edit_prediction_preview
7103 .set_previous_scroll_position(Some(
7104 position_map.snapshot.scroll_anchor,
7105 ));
7106
7107 self.highlight_rows::<EditPredictionPreview>(
7108 target..target,
7109 cx.theme().colors().editor_highlighted_line_background,
7110 RowHighlightOptions {
7111 autoscroll: true,
7112 ..Default::default()
7113 },
7114 cx,
7115 );
7116 self.request_autoscroll(Autoscroll::fit(), cx);
7117 }
7118 }
7119 }
7120 InlineCompletion::Edit { edits, .. } => {
7121 if let Some(provider) = self.edit_prediction_provider() {
7122 provider.accept(cx);
7123 }
7124
7125 // Store the transaction ID and selections before applying the edit
7126 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7127
7128 let snapshot = self.buffer.read(cx).snapshot(cx);
7129 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7130
7131 self.buffer.update(cx, |buffer, cx| {
7132 buffer.edit(edits.iter().cloned(), None, cx)
7133 });
7134
7135 self.change_selections(None, window, cx, |s| {
7136 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7137 });
7138
7139 let selections = self.selections.disjoint_anchors();
7140 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7141 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7142 if has_new_transaction {
7143 self.selection_history
7144 .insert_transaction(transaction_id_now, selections);
7145 }
7146 }
7147
7148 self.update_visible_inline_completion(window, cx);
7149 if self.active_inline_completion.is_none() {
7150 self.refresh_inline_completion(true, true, window, cx);
7151 }
7152
7153 cx.notify();
7154 }
7155 }
7156
7157 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7158 }
7159
7160 pub fn accept_partial_inline_completion(
7161 &mut self,
7162 _: &AcceptPartialEditPrediction,
7163 window: &mut Window,
7164 cx: &mut Context<Self>,
7165 ) {
7166 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7167 return;
7168 };
7169 if self.selections.count() != 1 {
7170 return;
7171 }
7172
7173 self.report_inline_completion_event(
7174 active_inline_completion.completion_id.clone(),
7175 true,
7176 cx,
7177 );
7178
7179 match &active_inline_completion.completion {
7180 InlineCompletion::Move { target, .. } => {
7181 let target = *target;
7182 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
7183 selections.select_anchor_ranges([target..target]);
7184 });
7185 }
7186 InlineCompletion::Edit { edits, .. } => {
7187 // Find an insertion that starts at the cursor position.
7188 let snapshot = self.buffer.read(cx).snapshot(cx);
7189 let cursor_offset = self.selections.newest::<usize>(cx).head();
7190 let insertion = edits.iter().find_map(|(range, text)| {
7191 let range = range.to_offset(&snapshot);
7192 if range.is_empty() && range.start == cursor_offset {
7193 Some(text)
7194 } else {
7195 None
7196 }
7197 });
7198
7199 if let Some(text) = insertion {
7200 let mut partial_completion = text
7201 .chars()
7202 .by_ref()
7203 .take_while(|c| c.is_alphabetic())
7204 .collect::<String>();
7205 if partial_completion.is_empty() {
7206 partial_completion = text
7207 .chars()
7208 .by_ref()
7209 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7210 .collect::<String>();
7211 }
7212
7213 cx.emit(EditorEvent::InputHandled {
7214 utf16_range_to_replace: None,
7215 text: partial_completion.clone().into(),
7216 });
7217
7218 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7219
7220 self.refresh_inline_completion(true, true, window, cx);
7221 cx.notify();
7222 } else {
7223 self.accept_edit_prediction(&Default::default(), window, cx);
7224 }
7225 }
7226 }
7227 }
7228
7229 fn discard_inline_completion(
7230 &mut self,
7231 should_report_inline_completion_event: bool,
7232 cx: &mut Context<Self>,
7233 ) -> bool {
7234 if should_report_inline_completion_event {
7235 let completion_id = self
7236 .active_inline_completion
7237 .as_ref()
7238 .and_then(|active_completion| active_completion.completion_id.clone());
7239
7240 self.report_inline_completion_event(completion_id, false, cx);
7241 }
7242
7243 if let Some(provider) = self.edit_prediction_provider() {
7244 provider.discard(cx);
7245 }
7246
7247 self.take_active_inline_completion(cx)
7248 }
7249
7250 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7251 let Some(provider) = self.edit_prediction_provider() else {
7252 return;
7253 };
7254
7255 let Some((_, buffer, _)) = self
7256 .buffer
7257 .read(cx)
7258 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7259 else {
7260 return;
7261 };
7262
7263 let extension = buffer
7264 .read(cx)
7265 .file()
7266 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7267
7268 let event_type = match accepted {
7269 true => "Edit Prediction Accepted",
7270 false => "Edit Prediction Discarded",
7271 };
7272 telemetry::event!(
7273 event_type,
7274 provider = provider.name(),
7275 prediction_id = id,
7276 suggestion_accepted = accepted,
7277 file_extension = extension,
7278 );
7279 }
7280
7281 pub fn has_active_inline_completion(&self) -> bool {
7282 self.active_inline_completion.is_some()
7283 }
7284
7285 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7286 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7287 return false;
7288 };
7289
7290 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7291 self.clear_highlights::<InlineCompletionHighlight>(cx);
7292 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7293 true
7294 }
7295
7296 /// Returns true when we're displaying the edit prediction popover below the cursor
7297 /// like we are not previewing and the LSP autocomplete menu is visible
7298 /// or we are in `when_holding_modifier` mode.
7299 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7300 if self.edit_prediction_preview_is_active()
7301 || !self.show_edit_predictions_in_menu()
7302 || !self.edit_predictions_enabled()
7303 {
7304 return false;
7305 }
7306
7307 if self.has_visible_completions_menu() {
7308 return true;
7309 }
7310
7311 has_completion && self.edit_prediction_requires_modifier()
7312 }
7313
7314 fn handle_modifiers_changed(
7315 &mut self,
7316 modifiers: Modifiers,
7317 position_map: &PositionMap,
7318 window: &mut Window,
7319 cx: &mut Context<Self>,
7320 ) {
7321 if self.show_edit_predictions_in_menu() {
7322 self.update_edit_prediction_preview(&modifiers, window, cx);
7323 }
7324
7325 self.update_selection_mode(&modifiers, position_map, window, cx);
7326
7327 let mouse_position = window.mouse_position();
7328 if !position_map.text_hitbox.is_hovered(window) {
7329 return;
7330 }
7331
7332 self.update_hovered_link(
7333 position_map.point_for_position(mouse_position),
7334 &position_map.snapshot,
7335 modifiers,
7336 window,
7337 cx,
7338 )
7339 }
7340
7341 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7342 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7343 if invert {
7344 match multi_cursor_setting {
7345 MultiCursorModifier::Alt => modifiers.alt,
7346 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7347 }
7348 } else {
7349 match multi_cursor_setting {
7350 MultiCursorModifier::Alt => modifiers.secondary(),
7351 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7352 }
7353 }
7354 }
7355
7356 fn columnar_selection_mode(
7357 modifiers: &Modifiers,
7358 cx: &mut Context<Self>,
7359 ) -> Option<ColumnarMode> {
7360 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7361 if Self::multi_cursor_modifier(false, modifiers, cx) {
7362 Some(ColumnarMode::FromMouse)
7363 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7364 Some(ColumnarMode::FromSelection)
7365 } else {
7366 None
7367 }
7368 } else {
7369 None
7370 }
7371 }
7372
7373 fn update_selection_mode(
7374 &mut self,
7375 modifiers: &Modifiers,
7376 position_map: &PositionMap,
7377 window: &mut Window,
7378 cx: &mut Context<Self>,
7379 ) {
7380 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7381 return;
7382 };
7383 if self.selections.pending.is_none() {
7384 return;
7385 }
7386
7387 let mouse_position = window.mouse_position();
7388 let point_for_position = position_map.point_for_position(mouse_position);
7389 let position = point_for_position.previous_valid;
7390
7391 self.select(
7392 SelectPhase::BeginColumnar {
7393 position,
7394 reset: false,
7395 mode,
7396 goal_column: point_for_position.exact_unclipped.column(),
7397 },
7398 window,
7399 cx,
7400 );
7401 }
7402
7403 fn update_edit_prediction_preview(
7404 &mut self,
7405 modifiers: &Modifiers,
7406 window: &mut Window,
7407 cx: &mut Context<Self>,
7408 ) {
7409 let mut modifiers_held = false;
7410 if let Some(accept_keystroke) = self
7411 .accept_edit_prediction_keybind(false, window, cx)
7412 .keystroke()
7413 {
7414 modifiers_held = modifiers_held
7415 || (&accept_keystroke.modifiers == modifiers
7416 && accept_keystroke.modifiers.modified());
7417 };
7418 if let Some(accept_partial_keystroke) = self
7419 .accept_edit_prediction_keybind(true, window, cx)
7420 .keystroke()
7421 {
7422 modifiers_held = modifiers_held
7423 || (&accept_partial_keystroke.modifiers == modifiers
7424 && accept_partial_keystroke.modifiers.modified());
7425 }
7426
7427 if modifiers_held {
7428 if matches!(
7429 self.edit_prediction_preview,
7430 EditPredictionPreview::Inactive { .. }
7431 ) {
7432 self.edit_prediction_preview = EditPredictionPreview::Active {
7433 previous_scroll_position: None,
7434 since: Instant::now(),
7435 };
7436
7437 self.update_visible_inline_completion(window, cx);
7438 cx.notify();
7439 }
7440 } else if let EditPredictionPreview::Active {
7441 previous_scroll_position,
7442 since,
7443 } = self.edit_prediction_preview
7444 {
7445 if let (Some(previous_scroll_position), Some(position_map)) =
7446 (previous_scroll_position, self.last_position_map.as_ref())
7447 {
7448 self.set_scroll_position(
7449 previous_scroll_position
7450 .scroll_position(&position_map.snapshot.display_snapshot),
7451 window,
7452 cx,
7453 );
7454 }
7455
7456 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7457 released_too_fast: since.elapsed() < Duration::from_millis(200),
7458 };
7459 self.clear_row_highlights::<EditPredictionPreview>();
7460 self.update_visible_inline_completion(window, cx);
7461 cx.notify();
7462 }
7463 }
7464
7465 fn update_visible_inline_completion(
7466 &mut self,
7467 _window: &mut Window,
7468 cx: &mut Context<Self>,
7469 ) -> Option<()> {
7470 let selection = self.selections.newest_anchor();
7471 let cursor = selection.head();
7472 let multibuffer = self.buffer.read(cx).snapshot(cx);
7473 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7474 let excerpt_id = cursor.excerpt_id;
7475
7476 let show_in_menu = self.show_edit_predictions_in_menu();
7477 let completions_menu_has_precedence = !show_in_menu
7478 && (self.context_menu.borrow().is_some()
7479 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7480
7481 if completions_menu_has_precedence
7482 || !offset_selection.is_empty()
7483 || self
7484 .active_inline_completion
7485 .as_ref()
7486 .map_or(false, |completion| {
7487 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7488 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7489 !invalidation_range.contains(&offset_selection.head())
7490 })
7491 {
7492 self.discard_inline_completion(false, cx);
7493 return None;
7494 }
7495
7496 self.take_active_inline_completion(cx);
7497 let Some(provider) = self.edit_prediction_provider() else {
7498 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7499 return None;
7500 };
7501
7502 let (buffer, cursor_buffer_position) =
7503 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7504
7505 self.edit_prediction_settings =
7506 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7507
7508 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7509
7510 if self.edit_prediction_indent_conflict {
7511 let cursor_point = cursor.to_point(&multibuffer);
7512
7513 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7514
7515 if let Some((_, indent)) = indents.iter().next() {
7516 if indent.len == cursor_point.column {
7517 self.edit_prediction_indent_conflict = false;
7518 }
7519 }
7520 }
7521
7522 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7523 let edits = inline_completion
7524 .edits
7525 .into_iter()
7526 .flat_map(|(range, new_text)| {
7527 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7528 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7529 Some((start..end, new_text))
7530 })
7531 .collect::<Vec<_>>();
7532 if edits.is_empty() {
7533 return None;
7534 }
7535
7536 let first_edit_start = edits.first().unwrap().0.start;
7537 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7538 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7539
7540 let last_edit_end = edits.last().unwrap().0.end;
7541 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7542 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7543
7544 let cursor_row = cursor.to_point(&multibuffer).row;
7545
7546 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7547
7548 let mut inlay_ids = Vec::new();
7549 let invalidation_row_range;
7550 let move_invalidation_row_range = if cursor_row < edit_start_row {
7551 Some(cursor_row..edit_end_row)
7552 } else if cursor_row > edit_end_row {
7553 Some(edit_start_row..cursor_row)
7554 } else {
7555 None
7556 };
7557 let is_move =
7558 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7559 let completion = if is_move {
7560 invalidation_row_range =
7561 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7562 let target = first_edit_start;
7563 InlineCompletion::Move { target, snapshot }
7564 } else {
7565 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7566 && !self.inline_completions_hidden_for_vim_mode;
7567
7568 if show_completions_in_buffer {
7569 if edits
7570 .iter()
7571 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7572 {
7573 let mut inlays = Vec::new();
7574 for (range, new_text) in &edits {
7575 let inlay = Inlay::inline_completion(
7576 post_inc(&mut self.next_inlay_id),
7577 range.start,
7578 new_text.as_str(),
7579 );
7580 inlay_ids.push(inlay.id);
7581 inlays.push(inlay);
7582 }
7583
7584 self.splice_inlays(&[], inlays, cx);
7585 } else {
7586 let background_color = cx.theme().status().deleted_background;
7587 self.highlight_text::<InlineCompletionHighlight>(
7588 edits.iter().map(|(range, _)| range.clone()).collect(),
7589 HighlightStyle {
7590 background_color: Some(background_color),
7591 ..Default::default()
7592 },
7593 cx,
7594 );
7595 }
7596 }
7597
7598 invalidation_row_range = edit_start_row..edit_end_row;
7599
7600 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7601 if provider.show_tab_accept_marker() {
7602 EditDisplayMode::TabAccept
7603 } else {
7604 EditDisplayMode::Inline
7605 }
7606 } else {
7607 EditDisplayMode::DiffPopover
7608 };
7609
7610 InlineCompletion::Edit {
7611 edits,
7612 edit_preview: inline_completion.edit_preview,
7613 display_mode,
7614 snapshot,
7615 }
7616 };
7617
7618 let invalidation_range = multibuffer
7619 .anchor_before(Point::new(invalidation_row_range.start, 0))
7620 ..multibuffer.anchor_after(Point::new(
7621 invalidation_row_range.end,
7622 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7623 ));
7624
7625 self.stale_inline_completion_in_menu = None;
7626 self.active_inline_completion = Some(InlineCompletionState {
7627 inlay_ids,
7628 completion,
7629 completion_id: inline_completion.id,
7630 invalidation_range,
7631 });
7632
7633 cx.notify();
7634
7635 Some(())
7636 }
7637
7638 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7639 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7640 }
7641
7642 fn clear_tasks(&mut self) {
7643 self.tasks.clear()
7644 }
7645
7646 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7647 if self.tasks.insert(key, value).is_some() {
7648 // This case should hopefully be rare, but just in case...
7649 log::error!(
7650 "multiple different run targets found on a single line, only the last target will be rendered"
7651 )
7652 }
7653 }
7654
7655 /// Get all display points of breakpoints that will be rendered within editor
7656 ///
7657 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7658 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7659 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7660 fn active_breakpoints(
7661 &self,
7662 range: Range<DisplayRow>,
7663 window: &mut Window,
7664 cx: &mut Context<Self>,
7665 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7666 let mut breakpoint_display_points = HashMap::default();
7667
7668 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7669 return breakpoint_display_points;
7670 };
7671
7672 let snapshot = self.snapshot(window, cx);
7673
7674 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7675 let Some(project) = self.project.as_ref() else {
7676 return breakpoint_display_points;
7677 };
7678
7679 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7680 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7681
7682 for (buffer_snapshot, range, excerpt_id) in
7683 multi_buffer_snapshot.range_to_buffer_ranges(range)
7684 {
7685 let Some(buffer) = project
7686 .read(cx)
7687 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7688 else {
7689 continue;
7690 };
7691 let breakpoints = breakpoint_store.read(cx).breakpoints(
7692 &buffer,
7693 Some(
7694 buffer_snapshot.anchor_before(range.start)
7695 ..buffer_snapshot.anchor_after(range.end),
7696 ),
7697 buffer_snapshot,
7698 cx,
7699 );
7700 for (breakpoint, state) in breakpoints {
7701 let multi_buffer_anchor =
7702 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7703 let position = multi_buffer_anchor
7704 .to_point(&multi_buffer_snapshot)
7705 .to_display_point(&snapshot);
7706
7707 breakpoint_display_points.insert(
7708 position.row(),
7709 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7710 );
7711 }
7712 }
7713
7714 breakpoint_display_points
7715 }
7716
7717 fn breakpoint_context_menu(
7718 &self,
7719 anchor: Anchor,
7720 window: &mut Window,
7721 cx: &mut Context<Self>,
7722 ) -> Entity<ui::ContextMenu> {
7723 let weak_editor = cx.weak_entity();
7724 let focus_handle = self.focus_handle(cx);
7725
7726 let row = self
7727 .buffer
7728 .read(cx)
7729 .snapshot(cx)
7730 .summary_for_anchor::<Point>(&anchor)
7731 .row;
7732
7733 let breakpoint = self
7734 .breakpoint_at_row(row, window, cx)
7735 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7736
7737 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7738 "Edit Log Breakpoint"
7739 } else {
7740 "Set Log Breakpoint"
7741 };
7742
7743 let condition_breakpoint_msg = if breakpoint
7744 .as_ref()
7745 .is_some_and(|bp| bp.1.condition.is_some())
7746 {
7747 "Edit Condition Breakpoint"
7748 } else {
7749 "Set Condition Breakpoint"
7750 };
7751
7752 let hit_condition_breakpoint_msg = if breakpoint
7753 .as_ref()
7754 .is_some_and(|bp| bp.1.hit_condition.is_some())
7755 {
7756 "Edit Hit Condition Breakpoint"
7757 } else {
7758 "Set Hit Condition Breakpoint"
7759 };
7760
7761 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7762 "Unset Breakpoint"
7763 } else {
7764 "Set Breakpoint"
7765 };
7766
7767 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7768
7769 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7770 BreakpointState::Enabled => Some("Disable"),
7771 BreakpointState::Disabled => Some("Enable"),
7772 });
7773
7774 let (anchor, breakpoint) =
7775 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7776
7777 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7778 menu.on_blur_subscription(Subscription::new(|| {}))
7779 .context(focus_handle)
7780 .when(run_to_cursor, |this| {
7781 let weak_editor = weak_editor.clone();
7782 this.entry("Run to cursor", None, move |window, cx| {
7783 weak_editor
7784 .update(cx, |editor, cx| {
7785 editor.change_selections(None, window, cx, |s| {
7786 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7787 });
7788 })
7789 .ok();
7790
7791 window.dispatch_action(Box::new(RunToCursor), cx);
7792 })
7793 .separator()
7794 })
7795 .when_some(toggle_state_msg, |this, msg| {
7796 this.entry(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::InvertState,
7806 cx,
7807 );
7808 })
7809 .log_err();
7810 }
7811 })
7812 })
7813 .entry(set_breakpoint_msg, None, {
7814 let weak_editor = weak_editor.clone();
7815 let breakpoint = breakpoint.clone();
7816 move |_window, cx| {
7817 weak_editor
7818 .update(cx, |this, cx| {
7819 this.edit_breakpoint_at_anchor(
7820 anchor,
7821 breakpoint.as_ref().clone(),
7822 BreakpointEditAction::Toggle,
7823 cx,
7824 );
7825 })
7826 .log_err();
7827 }
7828 })
7829 .entry(log_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::Log,
7839 window,
7840 cx,
7841 );
7842 })
7843 .log_err();
7844 }
7845 })
7846 .entry(condition_breakpoint_msg, None, {
7847 let breakpoint = breakpoint.clone();
7848 let weak_editor = weak_editor.clone();
7849 move |window, cx| {
7850 weak_editor
7851 .update(cx, |this, cx| {
7852 this.add_edit_breakpoint_block(
7853 anchor,
7854 breakpoint.as_ref(),
7855 BreakpointPromptEditAction::Condition,
7856 window,
7857 cx,
7858 );
7859 })
7860 .log_err();
7861 }
7862 })
7863 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7864 weak_editor
7865 .update(cx, |this, cx| {
7866 this.add_edit_breakpoint_block(
7867 anchor,
7868 breakpoint.as_ref(),
7869 BreakpointPromptEditAction::HitCondition,
7870 window,
7871 cx,
7872 );
7873 })
7874 .log_err();
7875 })
7876 })
7877 }
7878
7879 fn render_breakpoint(
7880 &self,
7881 position: Anchor,
7882 row: DisplayRow,
7883 breakpoint: &Breakpoint,
7884 state: Option<BreakpointSessionState>,
7885 cx: &mut Context<Self>,
7886 ) -> IconButton {
7887 let is_rejected = state.is_some_and(|s| !s.verified);
7888 // Is it a breakpoint that shows up when hovering over gutter?
7889 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7890 (false, false),
7891 |PhantomBreakpointIndicator {
7892 is_active,
7893 display_row,
7894 collides_with_existing_breakpoint,
7895 }| {
7896 (
7897 is_active && display_row == row,
7898 collides_with_existing_breakpoint,
7899 )
7900 },
7901 );
7902
7903 let (color, icon) = {
7904 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7905 (false, false) => ui::IconName::DebugBreakpoint,
7906 (true, false) => ui::IconName::DebugLogBreakpoint,
7907 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7908 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7909 };
7910
7911 let color = if is_phantom {
7912 Color::Hint
7913 } else if is_rejected {
7914 Color::Disabled
7915 } else {
7916 Color::Debugger
7917 };
7918
7919 (color, icon)
7920 };
7921
7922 let breakpoint = Arc::from(breakpoint.clone());
7923
7924 let alt_as_text = gpui::Keystroke {
7925 modifiers: Modifiers::secondary_key(),
7926 ..Default::default()
7927 };
7928 let primary_action_text = if breakpoint.is_disabled() {
7929 "Enable breakpoint"
7930 } else if is_phantom && !collides_with_existing {
7931 "Set breakpoint"
7932 } else {
7933 "Unset breakpoint"
7934 };
7935 let focus_handle = self.focus_handle.clone();
7936
7937 let meta = if is_rejected {
7938 SharedString::from("No executable code is associated with this line.")
7939 } else if collides_with_existing && !breakpoint.is_disabled() {
7940 SharedString::from(format!(
7941 "{alt_as_text}-click to disable,\nright-click for more options."
7942 ))
7943 } else {
7944 SharedString::from("Right-click for more options.")
7945 };
7946 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7947 .icon_size(IconSize::XSmall)
7948 .size(ui::ButtonSize::None)
7949 .when(is_rejected, |this| {
7950 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7951 })
7952 .icon_color(color)
7953 .style(ButtonStyle::Transparent)
7954 .on_click(cx.listener({
7955 let breakpoint = breakpoint.clone();
7956
7957 move |editor, event: &ClickEvent, window, cx| {
7958 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7959 BreakpointEditAction::InvertState
7960 } else {
7961 BreakpointEditAction::Toggle
7962 };
7963
7964 window.focus(&editor.focus_handle(cx));
7965 editor.edit_breakpoint_at_anchor(
7966 position,
7967 breakpoint.as_ref().clone(),
7968 edit_action,
7969 cx,
7970 );
7971 }
7972 }))
7973 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7974 editor.set_breakpoint_context_menu(
7975 row,
7976 Some(position),
7977 event.down.position,
7978 window,
7979 cx,
7980 );
7981 }))
7982 .tooltip(move |window, cx| {
7983 Tooltip::with_meta_in(
7984 primary_action_text,
7985 Some(&ToggleBreakpoint),
7986 meta.clone(),
7987 &focus_handle,
7988 window,
7989 cx,
7990 )
7991 })
7992 }
7993
7994 fn build_tasks_context(
7995 project: &Entity<Project>,
7996 buffer: &Entity<Buffer>,
7997 buffer_row: u32,
7998 tasks: &Arc<RunnableTasks>,
7999 cx: &mut Context<Self>,
8000 ) -> Task<Option<task::TaskContext>> {
8001 let position = Point::new(buffer_row, tasks.column);
8002 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8003 let location = Location {
8004 buffer: buffer.clone(),
8005 range: range_start..range_start,
8006 };
8007 // Fill in the environmental variables from the tree-sitter captures
8008 let mut captured_task_variables = TaskVariables::default();
8009 for (capture_name, value) in tasks.extra_variables.clone() {
8010 captured_task_variables.insert(
8011 task::VariableName::Custom(capture_name.into()),
8012 value.clone(),
8013 );
8014 }
8015 project.update(cx, |project, cx| {
8016 project.task_store().update(cx, |task_store, cx| {
8017 task_store.task_context_for_location(captured_task_variables, location, cx)
8018 })
8019 })
8020 }
8021
8022 pub fn spawn_nearest_task(
8023 &mut self,
8024 action: &SpawnNearestTask,
8025 window: &mut Window,
8026 cx: &mut Context<Self>,
8027 ) {
8028 let Some((workspace, _)) = self.workspace.clone() else {
8029 return;
8030 };
8031 let Some(project) = self.project.clone() else {
8032 return;
8033 };
8034
8035 // Try to find a closest, enclosing node using tree-sitter that has a
8036 // task
8037 let Some((buffer, buffer_row, tasks)) = self
8038 .find_enclosing_node_task(cx)
8039 // Or find the task that's closest in row-distance.
8040 .or_else(|| self.find_closest_task(cx))
8041 else {
8042 return;
8043 };
8044
8045 let reveal_strategy = action.reveal;
8046 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8047 cx.spawn_in(window, async move |_, cx| {
8048 let context = task_context.await?;
8049 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8050
8051 let resolved = &mut resolved_task.resolved;
8052 resolved.reveal = reveal_strategy;
8053
8054 workspace
8055 .update_in(cx, |workspace, window, cx| {
8056 workspace.schedule_resolved_task(
8057 task_source_kind,
8058 resolved_task,
8059 false,
8060 window,
8061 cx,
8062 );
8063 })
8064 .ok()
8065 })
8066 .detach();
8067 }
8068
8069 fn find_closest_task(
8070 &mut self,
8071 cx: &mut Context<Self>,
8072 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8073 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8074
8075 let ((buffer_id, row), tasks) = self
8076 .tasks
8077 .iter()
8078 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8079
8080 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8081 let tasks = Arc::new(tasks.to_owned());
8082 Some((buffer, *row, tasks))
8083 }
8084
8085 fn find_enclosing_node_task(
8086 &mut self,
8087 cx: &mut Context<Self>,
8088 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8089 let snapshot = self.buffer.read(cx).snapshot(cx);
8090 let offset = self.selections.newest::<usize>(cx).head();
8091 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8092 let buffer_id = excerpt.buffer().remote_id();
8093
8094 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8095 let mut cursor = layer.node().walk();
8096
8097 while cursor.goto_first_child_for_byte(offset).is_some() {
8098 if cursor.node().end_byte() == offset {
8099 cursor.goto_next_sibling();
8100 }
8101 }
8102
8103 // Ascend to the smallest ancestor that contains the range and has a task.
8104 loop {
8105 let node = cursor.node();
8106 let node_range = node.byte_range();
8107 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8108
8109 // Check if this node contains our offset
8110 if node_range.start <= offset && node_range.end >= offset {
8111 // If it contains offset, check for task
8112 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8113 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8114 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8115 }
8116 }
8117
8118 if !cursor.goto_parent() {
8119 break;
8120 }
8121 }
8122 None
8123 }
8124
8125 fn render_run_indicator(
8126 &self,
8127 _style: &EditorStyle,
8128 is_active: bool,
8129 row: DisplayRow,
8130 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8131 cx: &mut Context<Self>,
8132 ) -> IconButton {
8133 let color = Color::Muted;
8134 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8135
8136 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
8137 .shape(ui::IconButtonShape::Square)
8138 .icon_size(IconSize::XSmall)
8139 .icon_color(color)
8140 .toggle_state(is_active)
8141 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8142 let quick_launch = e.down.button == MouseButton::Left;
8143 window.focus(&editor.focus_handle(cx));
8144 editor.toggle_code_actions(
8145 &ToggleCodeActions {
8146 deployed_from: Some(CodeActionSource::RunMenu(row)),
8147 quick_launch,
8148 },
8149 window,
8150 cx,
8151 );
8152 }))
8153 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8154 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
8155 }))
8156 }
8157
8158 pub fn context_menu_visible(&self) -> bool {
8159 !self.edit_prediction_preview_is_active()
8160 && self
8161 .context_menu
8162 .borrow()
8163 .as_ref()
8164 .map_or(false, |menu| menu.visible())
8165 }
8166
8167 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8168 self.context_menu
8169 .borrow()
8170 .as_ref()
8171 .map(|menu| menu.origin())
8172 }
8173
8174 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8175 self.context_menu_options = Some(options);
8176 }
8177
8178 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8179 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8180
8181 fn render_edit_prediction_popover(
8182 &mut self,
8183 text_bounds: &Bounds<Pixels>,
8184 content_origin: gpui::Point<Pixels>,
8185 right_margin: Pixels,
8186 editor_snapshot: &EditorSnapshot,
8187 visible_row_range: Range<DisplayRow>,
8188 scroll_top: f32,
8189 scroll_bottom: f32,
8190 line_layouts: &[LineWithInvisibles],
8191 line_height: Pixels,
8192 scroll_pixel_position: gpui::Point<Pixels>,
8193 newest_selection_head: Option<DisplayPoint>,
8194 editor_width: Pixels,
8195 style: &EditorStyle,
8196 window: &mut Window,
8197 cx: &mut App,
8198 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8199 if self.mode().is_minimap() {
8200 return None;
8201 }
8202 let active_inline_completion = self.active_inline_completion.as_ref()?;
8203
8204 if self.edit_prediction_visible_in_cursor_popover(true) {
8205 return None;
8206 }
8207
8208 match &active_inline_completion.completion {
8209 InlineCompletion::Move { target, .. } => {
8210 let target_display_point = target.to_display_point(editor_snapshot);
8211
8212 if self.edit_prediction_requires_modifier() {
8213 if !self.edit_prediction_preview_is_active() {
8214 return None;
8215 }
8216
8217 self.render_edit_prediction_modifier_jump_popover(
8218 text_bounds,
8219 content_origin,
8220 visible_row_range,
8221 line_layouts,
8222 line_height,
8223 scroll_pixel_position,
8224 newest_selection_head,
8225 target_display_point,
8226 window,
8227 cx,
8228 )
8229 } else {
8230 self.render_edit_prediction_eager_jump_popover(
8231 text_bounds,
8232 content_origin,
8233 editor_snapshot,
8234 visible_row_range,
8235 scroll_top,
8236 scroll_bottom,
8237 line_height,
8238 scroll_pixel_position,
8239 target_display_point,
8240 editor_width,
8241 window,
8242 cx,
8243 )
8244 }
8245 }
8246 InlineCompletion::Edit {
8247 display_mode: EditDisplayMode::Inline,
8248 ..
8249 } => None,
8250 InlineCompletion::Edit {
8251 display_mode: EditDisplayMode::TabAccept,
8252 edits,
8253 ..
8254 } => {
8255 let range = &edits.first()?.0;
8256 let target_display_point = range.end.to_display_point(editor_snapshot);
8257
8258 self.render_edit_prediction_end_of_line_popover(
8259 "Accept",
8260 editor_snapshot,
8261 visible_row_range,
8262 target_display_point,
8263 line_height,
8264 scroll_pixel_position,
8265 content_origin,
8266 editor_width,
8267 window,
8268 cx,
8269 )
8270 }
8271 InlineCompletion::Edit {
8272 edits,
8273 edit_preview,
8274 display_mode: EditDisplayMode::DiffPopover,
8275 snapshot,
8276 } => self.render_edit_prediction_diff_popover(
8277 text_bounds,
8278 content_origin,
8279 right_margin,
8280 editor_snapshot,
8281 visible_row_range,
8282 line_layouts,
8283 line_height,
8284 scroll_pixel_position,
8285 newest_selection_head,
8286 editor_width,
8287 style,
8288 edits,
8289 edit_preview,
8290 snapshot,
8291 window,
8292 cx,
8293 ),
8294 }
8295 }
8296
8297 fn render_edit_prediction_modifier_jump_popover(
8298 &mut self,
8299 text_bounds: &Bounds<Pixels>,
8300 content_origin: gpui::Point<Pixels>,
8301 visible_row_range: Range<DisplayRow>,
8302 line_layouts: &[LineWithInvisibles],
8303 line_height: Pixels,
8304 scroll_pixel_position: gpui::Point<Pixels>,
8305 newest_selection_head: Option<DisplayPoint>,
8306 target_display_point: DisplayPoint,
8307 window: &mut Window,
8308 cx: &mut App,
8309 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8310 let scrolled_content_origin =
8311 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8312
8313 const SCROLL_PADDING_Y: Pixels = px(12.);
8314
8315 if target_display_point.row() < visible_row_range.start {
8316 return self.render_edit_prediction_scroll_popover(
8317 |_| SCROLL_PADDING_Y,
8318 IconName::ArrowUp,
8319 visible_row_range,
8320 line_layouts,
8321 newest_selection_head,
8322 scrolled_content_origin,
8323 window,
8324 cx,
8325 );
8326 } else if target_display_point.row() >= visible_row_range.end {
8327 return self.render_edit_prediction_scroll_popover(
8328 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8329 IconName::ArrowDown,
8330 visible_row_range,
8331 line_layouts,
8332 newest_selection_head,
8333 scrolled_content_origin,
8334 window,
8335 cx,
8336 );
8337 }
8338
8339 const POLE_WIDTH: Pixels = px(2.);
8340
8341 let line_layout =
8342 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8343 let target_column = target_display_point.column() as usize;
8344
8345 let target_x = line_layout.x_for_index(target_column);
8346 let target_y =
8347 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8348
8349 let flag_on_right = target_x < text_bounds.size.width / 2.;
8350
8351 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8352 border_color.l += 0.001;
8353
8354 let mut element = v_flex()
8355 .items_end()
8356 .when(flag_on_right, |el| el.items_start())
8357 .child(if flag_on_right {
8358 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8359 .rounded_bl(px(0.))
8360 .rounded_tl(px(0.))
8361 .border_l_2()
8362 .border_color(border_color)
8363 } else {
8364 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8365 .rounded_br(px(0.))
8366 .rounded_tr(px(0.))
8367 .border_r_2()
8368 .border_color(border_color)
8369 })
8370 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8371 .into_any();
8372
8373 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8374
8375 let mut origin = scrolled_content_origin + point(target_x, target_y)
8376 - point(
8377 if flag_on_right {
8378 POLE_WIDTH
8379 } else {
8380 size.width - POLE_WIDTH
8381 },
8382 size.height - line_height,
8383 );
8384
8385 origin.x = origin.x.max(content_origin.x);
8386
8387 element.prepaint_at(origin, window, cx);
8388
8389 Some((element, origin))
8390 }
8391
8392 fn render_edit_prediction_scroll_popover(
8393 &mut self,
8394 to_y: impl Fn(Size<Pixels>) -> Pixels,
8395 scroll_icon: IconName,
8396 visible_row_range: Range<DisplayRow>,
8397 line_layouts: &[LineWithInvisibles],
8398 newest_selection_head: Option<DisplayPoint>,
8399 scrolled_content_origin: gpui::Point<Pixels>,
8400 window: &mut Window,
8401 cx: &mut App,
8402 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8403 let mut element = self
8404 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8405 .into_any();
8406
8407 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8408
8409 let cursor = newest_selection_head?;
8410 let cursor_row_layout =
8411 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8412 let cursor_column = cursor.column() as usize;
8413
8414 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8415
8416 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8417
8418 element.prepaint_at(origin, window, cx);
8419 Some((element, origin))
8420 }
8421
8422 fn render_edit_prediction_eager_jump_popover(
8423 &mut self,
8424 text_bounds: &Bounds<Pixels>,
8425 content_origin: gpui::Point<Pixels>,
8426 editor_snapshot: &EditorSnapshot,
8427 visible_row_range: Range<DisplayRow>,
8428 scroll_top: f32,
8429 scroll_bottom: f32,
8430 line_height: Pixels,
8431 scroll_pixel_position: gpui::Point<Pixels>,
8432 target_display_point: DisplayPoint,
8433 editor_width: Pixels,
8434 window: &mut Window,
8435 cx: &mut App,
8436 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8437 if target_display_point.row().as_f32() < scroll_top {
8438 let mut element = self
8439 .render_edit_prediction_line_popover(
8440 "Jump to Edit",
8441 Some(IconName::ArrowUp),
8442 window,
8443 cx,
8444 )?
8445 .into_any();
8446
8447 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8448 let offset = point(
8449 (text_bounds.size.width - size.width) / 2.,
8450 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8451 );
8452
8453 let origin = text_bounds.origin + offset;
8454 element.prepaint_at(origin, window, cx);
8455 Some((element, origin))
8456 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8457 let mut element = self
8458 .render_edit_prediction_line_popover(
8459 "Jump to Edit",
8460 Some(IconName::ArrowDown),
8461 window,
8462 cx,
8463 )?
8464 .into_any();
8465
8466 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8467 let offset = point(
8468 (text_bounds.size.width - size.width) / 2.,
8469 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8470 );
8471
8472 let origin = text_bounds.origin + offset;
8473 element.prepaint_at(origin, window, cx);
8474 Some((element, origin))
8475 } else {
8476 self.render_edit_prediction_end_of_line_popover(
8477 "Jump to Edit",
8478 editor_snapshot,
8479 visible_row_range,
8480 target_display_point,
8481 line_height,
8482 scroll_pixel_position,
8483 content_origin,
8484 editor_width,
8485 window,
8486 cx,
8487 )
8488 }
8489 }
8490
8491 fn render_edit_prediction_end_of_line_popover(
8492 self: &mut Editor,
8493 label: &'static str,
8494 editor_snapshot: &EditorSnapshot,
8495 visible_row_range: Range<DisplayRow>,
8496 target_display_point: DisplayPoint,
8497 line_height: Pixels,
8498 scroll_pixel_position: gpui::Point<Pixels>,
8499 content_origin: gpui::Point<Pixels>,
8500 editor_width: Pixels,
8501 window: &mut Window,
8502 cx: &mut App,
8503 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8504 let target_line_end = DisplayPoint::new(
8505 target_display_point.row(),
8506 editor_snapshot.line_len(target_display_point.row()),
8507 );
8508
8509 let mut element = self
8510 .render_edit_prediction_line_popover(label, None, window, cx)?
8511 .into_any();
8512
8513 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8514
8515 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8516
8517 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8518 let mut origin = start_point
8519 + line_origin
8520 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8521 origin.x = origin.x.max(content_origin.x);
8522
8523 let max_x = content_origin.x + editor_width - size.width;
8524
8525 if origin.x > max_x {
8526 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8527
8528 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8529 origin.y += offset;
8530 IconName::ArrowUp
8531 } else {
8532 origin.y -= offset;
8533 IconName::ArrowDown
8534 };
8535
8536 element = self
8537 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8538 .into_any();
8539
8540 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8541
8542 origin.x = content_origin.x + editor_width - size.width - px(2.);
8543 }
8544
8545 element.prepaint_at(origin, window, cx);
8546 Some((element, origin))
8547 }
8548
8549 fn render_edit_prediction_diff_popover(
8550 self: &Editor,
8551 text_bounds: &Bounds<Pixels>,
8552 content_origin: gpui::Point<Pixels>,
8553 right_margin: Pixels,
8554 editor_snapshot: &EditorSnapshot,
8555 visible_row_range: Range<DisplayRow>,
8556 line_layouts: &[LineWithInvisibles],
8557 line_height: Pixels,
8558 scroll_pixel_position: gpui::Point<Pixels>,
8559 newest_selection_head: Option<DisplayPoint>,
8560 editor_width: Pixels,
8561 style: &EditorStyle,
8562 edits: &Vec<(Range<Anchor>, String)>,
8563 edit_preview: &Option<language::EditPreview>,
8564 snapshot: &language::BufferSnapshot,
8565 window: &mut Window,
8566 cx: &mut App,
8567 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8568 let edit_start = edits
8569 .first()
8570 .unwrap()
8571 .0
8572 .start
8573 .to_display_point(editor_snapshot);
8574 let edit_end = edits
8575 .last()
8576 .unwrap()
8577 .0
8578 .end
8579 .to_display_point(editor_snapshot);
8580
8581 let is_visible = visible_row_range.contains(&edit_start.row())
8582 || visible_row_range.contains(&edit_end.row());
8583 if !is_visible {
8584 return None;
8585 }
8586
8587 let highlighted_edits =
8588 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8589
8590 let styled_text = highlighted_edits.to_styled_text(&style.text);
8591 let line_count = highlighted_edits.text.lines().count();
8592
8593 const BORDER_WIDTH: Pixels = px(1.);
8594
8595 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8596 let has_keybind = keybind.is_some();
8597
8598 let mut element = h_flex()
8599 .items_start()
8600 .child(
8601 h_flex()
8602 .bg(cx.theme().colors().editor_background)
8603 .border(BORDER_WIDTH)
8604 .shadow_sm()
8605 .border_color(cx.theme().colors().border)
8606 .rounded_l_lg()
8607 .when(line_count > 1, |el| el.rounded_br_lg())
8608 .pr_1()
8609 .child(styled_text),
8610 )
8611 .child(
8612 h_flex()
8613 .h(line_height + BORDER_WIDTH * 2.)
8614 .px_1p5()
8615 .gap_1()
8616 // Workaround: For some reason, there's a gap if we don't do this
8617 .ml(-BORDER_WIDTH)
8618 .shadow(vec![gpui::BoxShadow {
8619 color: gpui::black().opacity(0.05),
8620 offset: point(px(1.), px(1.)),
8621 blur_radius: px(2.),
8622 spread_radius: px(0.),
8623 }])
8624 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8625 .border(BORDER_WIDTH)
8626 .border_color(cx.theme().colors().border)
8627 .rounded_r_lg()
8628 .id("edit_prediction_diff_popover_keybind")
8629 .when(!has_keybind, |el| {
8630 let status_colors = cx.theme().status();
8631
8632 el.bg(status_colors.error_background)
8633 .border_color(status_colors.error.opacity(0.6))
8634 .child(Icon::new(IconName::Info).color(Color::Error))
8635 .cursor_default()
8636 .hoverable_tooltip(move |_window, cx| {
8637 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8638 })
8639 })
8640 .children(keybind),
8641 )
8642 .into_any();
8643
8644 let longest_row =
8645 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8646 let longest_line_width = if visible_row_range.contains(&longest_row) {
8647 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8648 } else {
8649 layout_line(
8650 longest_row,
8651 editor_snapshot,
8652 style,
8653 editor_width,
8654 |_| false,
8655 window,
8656 cx,
8657 )
8658 .width
8659 };
8660
8661 let viewport_bounds =
8662 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8663 right: -right_margin,
8664 ..Default::default()
8665 });
8666
8667 let x_after_longest =
8668 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8669 - scroll_pixel_position.x;
8670
8671 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8672
8673 // Fully visible if it can be displayed within the window (allow overlapping other
8674 // panes). However, this is only allowed if the popover starts within text_bounds.
8675 let can_position_to_the_right = x_after_longest < text_bounds.right()
8676 && x_after_longest + element_bounds.width < viewport_bounds.right();
8677
8678 let mut origin = if can_position_to_the_right {
8679 point(
8680 x_after_longest,
8681 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8682 - scroll_pixel_position.y,
8683 )
8684 } else {
8685 let cursor_row = newest_selection_head.map(|head| head.row());
8686 let above_edit = edit_start
8687 .row()
8688 .0
8689 .checked_sub(line_count as u32)
8690 .map(DisplayRow);
8691 let below_edit = Some(edit_end.row() + 1);
8692 let above_cursor =
8693 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8694 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8695
8696 // Place the edit popover adjacent to the edit if there is a location
8697 // available that is onscreen and does not obscure the cursor. Otherwise,
8698 // place it adjacent to the cursor.
8699 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8700 .into_iter()
8701 .flatten()
8702 .find(|&start_row| {
8703 let end_row = start_row + line_count as u32;
8704 visible_row_range.contains(&start_row)
8705 && visible_row_range.contains(&end_row)
8706 && cursor_row.map_or(true, |cursor_row| {
8707 !((start_row..end_row).contains(&cursor_row))
8708 })
8709 })?;
8710
8711 content_origin
8712 + point(
8713 -scroll_pixel_position.x,
8714 row_target.as_f32() * line_height - scroll_pixel_position.y,
8715 )
8716 };
8717
8718 origin.x -= BORDER_WIDTH;
8719
8720 window.defer_draw(element, origin, 1);
8721
8722 // Do not return an element, since it will already be drawn due to defer_draw.
8723 None
8724 }
8725
8726 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8727 px(30.)
8728 }
8729
8730 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8731 if self.read_only(cx) {
8732 cx.theme().players().read_only()
8733 } else {
8734 self.style.as_ref().unwrap().local_player
8735 }
8736 }
8737
8738 fn render_edit_prediction_accept_keybind(
8739 &self,
8740 window: &mut Window,
8741 cx: &App,
8742 ) -> Option<AnyElement> {
8743 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8744 let accept_keystroke = accept_binding.keystroke()?;
8745
8746 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8747
8748 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8749 Color::Accent
8750 } else {
8751 Color::Muted
8752 };
8753
8754 h_flex()
8755 .px_0p5()
8756 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8757 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8758 .text_size(TextSize::XSmall.rems(cx))
8759 .child(h_flex().children(ui::render_modifiers(
8760 &accept_keystroke.modifiers,
8761 PlatformStyle::platform(),
8762 Some(modifiers_color),
8763 Some(IconSize::XSmall.rems().into()),
8764 true,
8765 )))
8766 .when(is_platform_style_mac, |parent| {
8767 parent.child(accept_keystroke.key.clone())
8768 })
8769 .when(!is_platform_style_mac, |parent| {
8770 parent.child(
8771 Key::new(
8772 util::capitalize(&accept_keystroke.key),
8773 Some(Color::Default),
8774 )
8775 .size(Some(IconSize::XSmall.rems().into())),
8776 )
8777 })
8778 .into_any()
8779 .into()
8780 }
8781
8782 fn render_edit_prediction_line_popover(
8783 &self,
8784 label: impl Into<SharedString>,
8785 icon: Option<IconName>,
8786 window: &mut Window,
8787 cx: &App,
8788 ) -> Option<Stateful<Div>> {
8789 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8790
8791 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8792 let has_keybind = keybind.is_some();
8793
8794 let result = h_flex()
8795 .id("ep-line-popover")
8796 .py_0p5()
8797 .pl_1()
8798 .pr(padding_right)
8799 .gap_1()
8800 .rounded_md()
8801 .border_1()
8802 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8803 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8804 .shadow_sm()
8805 .when(!has_keybind, |el| {
8806 let status_colors = cx.theme().status();
8807
8808 el.bg(status_colors.error_background)
8809 .border_color(status_colors.error.opacity(0.6))
8810 .pl_2()
8811 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8812 .cursor_default()
8813 .hoverable_tooltip(move |_window, cx| {
8814 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8815 })
8816 })
8817 .children(keybind)
8818 .child(
8819 Label::new(label)
8820 .size(LabelSize::Small)
8821 .when(!has_keybind, |el| {
8822 el.color(cx.theme().status().error.into()).strikethrough()
8823 }),
8824 )
8825 .when(!has_keybind, |el| {
8826 el.child(
8827 h_flex().ml_1().child(
8828 Icon::new(IconName::Info)
8829 .size(IconSize::Small)
8830 .color(cx.theme().status().error.into()),
8831 ),
8832 )
8833 })
8834 .when_some(icon, |element, icon| {
8835 element.child(
8836 div()
8837 .mt(px(1.5))
8838 .child(Icon::new(icon).size(IconSize::Small)),
8839 )
8840 });
8841
8842 Some(result)
8843 }
8844
8845 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8846 let accent_color = cx.theme().colors().text_accent;
8847 let editor_bg_color = cx.theme().colors().editor_background;
8848 editor_bg_color.blend(accent_color.opacity(0.1))
8849 }
8850
8851 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8852 let accent_color = cx.theme().colors().text_accent;
8853 let editor_bg_color = cx.theme().colors().editor_background;
8854 editor_bg_color.blend(accent_color.opacity(0.6))
8855 }
8856
8857 fn render_edit_prediction_cursor_popover(
8858 &self,
8859 min_width: Pixels,
8860 max_width: Pixels,
8861 cursor_point: Point,
8862 style: &EditorStyle,
8863 accept_keystroke: Option<&gpui::Keystroke>,
8864 _window: &Window,
8865 cx: &mut Context<Editor>,
8866 ) -> Option<AnyElement> {
8867 let provider = self.edit_prediction_provider.as_ref()?;
8868
8869 if provider.provider.needs_terms_acceptance(cx) {
8870 return Some(
8871 h_flex()
8872 .min_w(min_width)
8873 .flex_1()
8874 .px_2()
8875 .py_1()
8876 .gap_3()
8877 .elevation_2(cx)
8878 .hover(|style| style.bg(cx.theme().colors().element_hover))
8879 .id("accept-terms")
8880 .cursor_pointer()
8881 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8882 .on_click(cx.listener(|this, _event, window, cx| {
8883 cx.stop_propagation();
8884 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8885 window.dispatch_action(
8886 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8887 cx,
8888 );
8889 }))
8890 .child(
8891 h_flex()
8892 .flex_1()
8893 .gap_2()
8894 .child(Icon::new(IconName::ZedPredict))
8895 .child(Label::new("Accept Terms of Service"))
8896 .child(div().w_full())
8897 .child(
8898 Icon::new(IconName::ArrowUpRight)
8899 .color(Color::Muted)
8900 .size(IconSize::Small),
8901 )
8902 .into_any_element(),
8903 )
8904 .into_any(),
8905 );
8906 }
8907
8908 let is_refreshing = provider.provider.is_refreshing(cx);
8909
8910 fn pending_completion_container() -> Div {
8911 h_flex()
8912 .h_full()
8913 .flex_1()
8914 .gap_2()
8915 .child(Icon::new(IconName::ZedPredict))
8916 }
8917
8918 let completion = match &self.active_inline_completion {
8919 Some(prediction) => {
8920 if !self.has_visible_completions_menu() {
8921 const RADIUS: Pixels = px(6.);
8922 const BORDER_WIDTH: Pixels = px(1.);
8923
8924 return Some(
8925 h_flex()
8926 .elevation_2(cx)
8927 .border(BORDER_WIDTH)
8928 .border_color(cx.theme().colors().border)
8929 .when(accept_keystroke.is_none(), |el| {
8930 el.border_color(cx.theme().status().error)
8931 })
8932 .rounded(RADIUS)
8933 .rounded_tl(px(0.))
8934 .overflow_hidden()
8935 .child(div().px_1p5().child(match &prediction.completion {
8936 InlineCompletion::Move { target, snapshot } => {
8937 use text::ToPoint as _;
8938 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8939 {
8940 Icon::new(IconName::ZedPredictDown)
8941 } else {
8942 Icon::new(IconName::ZedPredictUp)
8943 }
8944 }
8945 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8946 }))
8947 .child(
8948 h_flex()
8949 .gap_1()
8950 .py_1()
8951 .px_2()
8952 .rounded_r(RADIUS - BORDER_WIDTH)
8953 .border_l_1()
8954 .border_color(cx.theme().colors().border)
8955 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8956 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8957 el.child(
8958 Label::new("Hold")
8959 .size(LabelSize::Small)
8960 .when(accept_keystroke.is_none(), |el| {
8961 el.strikethrough()
8962 })
8963 .line_height_style(LineHeightStyle::UiLabel),
8964 )
8965 })
8966 .id("edit_prediction_cursor_popover_keybind")
8967 .when(accept_keystroke.is_none(), |el| {
8968 let status_colors = cx.theme().status();
8969
8970 el.bg(status_colors.error_background)
8971 .border_color(status_colors.error.opacity(0.6))
8972 .child(Icon::new(IconName::Info).color(Color::Error))
8973 .cursor_default()
8974 .hoverable_tooltip(move |_window, cx| {
8975 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8976 .into()
8977 })
8978 })
8979 .when_some(
8980 accept_keystroke.as_ref(),
8981 |el, accept_keystroke| {
8982 el.child(h_flex().children(ui::render_modifiers(
8983 &accept_keystroke.modifiers,
8984 PlatformStyle::platform(),
8985 Some(Color::Default),
8986 Some(IconSize::XSmall.rems().into()),
8987 false,
8988 )))
8989 },
8990 ),
8991 )
8992 .into_any(),
8993 );
8994 }
8995
8996 self.render_edit_prediction_cursor_popover_preview(
8997 prediction,
8998 cursor_point,
8999 style,
9000 cx,
9001 )?
9002 }
9003
9004 None if is_refreshing => match &self.stale_inline_completion_in_menu {
9005 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9006 stale_completion,
9007 cursor_point,
9008 style,
9009 cx,
9010 )?,
9011
9012 None => {
9013 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
9014 }
9015 },
9016
9017 None => pending_completion_container().child(Label::new("No Prediction")),
9018 };
9019
9020 let completion = if is_refreshing {
9021 completion
9022 .with_animation(
9023 "loading-completion",
9024 Animation::new(Duration::from_secs(2))
9025 .repeat()
9026 .with_easing(pulsating_between(0.4, 0.8)),
9027 |label, delta| label.opacity(delta),
9028 )
9029 .into_any_element()
9030 } else {
9031 completion.into_any_element()
9032 };
9033
9034 let has_completion = self.active_inline_completion.is_some();
9035
9036 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9037 Some(
9038 h_flex()
9039 .min_w(min_width)
9040 .max_w(max_width)
9041 .flex_1()
9042 .elevation_2(cx)
9043 .border_color(cx.theme().colors().border)
9044 .child(
9045 div()
9046 .flex_1()
9047 .py_1()
9048 .px_2()
9049 .overflow_hidden()
9050 .child(completion),
9051 )
9052 .when_some(accept_keystroke, |el, accept_keystroke| {
9053 if !accept_keystroke.modifiers.modified() {
9054 return el;
9055 }
9056
9057 el.child(
9058 h_flex()
9059 .h_full()
9060 .border_l_1()
9061 .rounded_r_lg()
9062 .border_color(cx.theme().colors().border)
9063 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9064 .gap_1()
9065 .py_1()
9066 .px_2()
9067 .child(
9068 h_flex()
9069 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9070 .when(is_platform_style_mac, |parent| parent.gap_1())
9071 .child(h_flex().children(ui::render_modifiers(
9072 &accept_keystroke.modifiers,
9073 PlatformStyle::platform(),
9074 Some(if !has_completion {
9075 Color::Muted
9076 } else {
9077 Color::Default
9078 }),
9079 None,
9080 false,
9081 ))),
9082 )
9083 .child(Label::new("Preview").into_any_element())
9084 .opacity(if has_completion { 1.0 } else { 0.4 }),
9085 )
9086 })
9087 .into_any(),
9088 )
9089 }
9090
9091 fn render_edit_prediction_cursor_popover_preview(
9092 &self,
9093 completion: &InlineCompletionState,
9094 cursor_point: Point,
9095 style: &EditorStyle,
9096 cx: &mut Context<Editor>,
9097 ) -> Option<Div> {
9098 use text::ToPoint as _;
9099
9100 fn render_relative_row_jump(
9101 prefix: impl Into<String>,
9102 current_row: u32,
9103 target_row: u32,
9104 ) -> Div {
9105 let (row_diff, arrow) = if target_row < current_row {
9106 (current_row - target_row, IconName::ArrowUp)
9107 } else {
9108 (target_row - current_row, IconName::ArrowDown)
9109 };
9110
9111 h_flex()
9112 .child(
9113 Label::new(format!("{}{}", prefix.into(), row_diff))
9114 .color(Color::Muted)
9115 .size(LabelSize::Small),
9116 )
9117 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9118 }
9119
9120 match &completion.completion {
9121 InlineCompletion::Move {
9122 target, snapshot, ..
9123 } => Some(
9124 h_flex()
9125 .px_2()
9126 .gap_2()
9127 .flex_1()
9128 .child(
9129 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9130 Icon::new(IconName::ZedPredictDown)
9131 } else {
9132 Icon::new(IconName::ZedPredictUp)
9133 },
9134 )
9135 .child(Label::new("Jump to Edit")),
9136 ),
9137
9138 InlineCompletion::Edit {
9139 edits,
9140 edit_preview,
9141 snapshot,
9142 display_mode: _,
9143 } => {
9144 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9145
9146 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
9147 &snapshot,
9148 &edits,
9149 edit_preview.as_ref()?,
9150 true,
9151 cx,
9152 )
9153 .first_line_preview();
9154
9155 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9156 .with_default_highlights(&style.text, highlighted_edits.highlights);
9157
9158 let preview = h_flex()
9159 .gap_1()
9160 .min_w_16()
9161 .child(styled_text)
9162 .when(has_more_lines, |parent| parent.child("…"));
9163
9164 let left = if first_edit_row != cursor_point.row {
9165 render_relative_row_jump("", cursor_point.row, first_edit_row)
9166 .into_any_element()
9167 } else {
9168 Icon::new(IconName::ZedPredict).into_any_element()
9169 };
9170
9171 Some(
9172 h_flex()
9173 .h_full()
9174 .flex_1()
9175 .gap_2()
9176 .pr_1()
9177 .overflow_x_hidden()
9178 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9179 .child(left)
9180 .child(preview),
9181 )
9182 }
9183 }
9184 }
9185
9186 pub fn render_context_menu(
9187 &self,
9188 style: &EditorStyle,
9189 max_height_in_lines: u32,
9190 window: &mut Window,
9191 cx: &mut Context<Editor>,
9192 ) -> Option<AnyElement> {
9193 let menu = self.context_menu.borrow();
9194 let menu = menu.as_ref()?;
9195 if !menu.visible() {
9196 return None;
9197 };
9198 Some(menu.render(style, max_height_in_lines, window, cx))
9199 }
9200
9201 fn render_context_menu_aside(
9202 &mut self,
9203 max_size: Size<Pixels>,
9204 window: &mut Window,
9205 cx: &mut Context<Editor>,
9206 ) -> Option<AnyElement> {
9207 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9208 if menu.visible() {
9209 menu.render_aside(max_size, window, cx)
9210 } else {
9211 None
9212 }
9213 })
9214 }
9215
9216 fn hide_context_menu(
9217 &mut self,
9218 window: &mut Window,
9219 cx: &mut Context<Self>,
9220 ) -> Option<CodeContextMenu> {
9221 cx.notify();
9222 self.completion_tasks.clear();
9223 let context_menu = self.context_menu.borrow_mut().take();
9224 self.stale_inline_completion_in_menu.take();
9225 self.update_visible_inline_completion(window, cx);
9226 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9227 if let Some(completion_provider) = &self.completion_provider {
9228 completion_provider.selection_changed(None, window, cx);
9229 }
9230 }
9231 context_menu
9232 }
9233
9234 fn show_snippet_choices(
9235 &mut self,
9236 choices: &Vec<String>,
9237 selection: Range<Anchor>,
9238 cx: &mut Context<Self>,
9239 ) {
9240 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9241 (Some(a), Some(b)) if a == b => a,
9242 _ => {
9243 log::error!("expected anchor range to have matching buffer IDs");
9244 return;
9245 }
9246 };
9247 let multi_buffer = self.buffer().read(cx);
9248 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9249 return;
9250 };
9251
9252 let id = post_inc(&mut self.next_completion_id);
9253 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9254 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9255 CompletionsMenu::new_snippet_choices(
9256 id,
9257 true,
9258 choices,
9259 selection,
9260 buffer,
9261 snippet_sort_order,
9262 ),
9263 ));
9264 }
9265
9266 pub fn insert_snippet(
9267 &mut self,
9268 insertion_ranges: &[Range<usize>],
9269 snippet: Snippet,
9270 window: &mut Window,
9271 cx: &mut Context<Self>,
9272 ) -> Result<()> {
9273 struct Tabstop<T> {
9274 is_end_tabstop: bool,
9275 ranges: Vec<Range<T>>,
9276 choices: Option<Vec<String>>,
9277 }
9278
9279 let tabstops = self.buffer.update(cx, |buffer, cx| {
9280 let snippet_text: Arc<str> = snippet.text.clone().into();
9281 let edits = insertion_ranges
9282 .iter()
9283 .cloned()
9284 .map(|range| (range, snippet_text.clone()));
9285 let autoindent_mode = AutoindentMode::Block {
9286 original_indent_columns: Vec::new(),
9287 };
9288 buffer.edit(edits, Some(autoindent_mode), cx);
9289
9290 let snapshot = &*buffer.read(cx);
9291 let snippet = &snippet;
9292 snippet
9293 .tabstops
9294 .iter()
9295 .map(|tabstop| {
9296 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9297 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9298 });
9299 let mut tabstop_ranges = tabstop
9300 .ranges
9301 .iter()
9302 .flat_map(|tabstop_range| {
9303 let mut delta = 0_isize;
9304 insertion_ranges.iter().map(move |insertion_range| {
9305 let insertion_start = insertion_range.start as isize + delta;
9306 delta +=
9307 snippet.text.len() as isize - insertion_range.len() as isize;
9308
9309 let start = ((insertion_start + tabstop_range.start) as usize)
9310 .min(snapshot.len());
9311 let end = ((insertion_start + tabstop_range.end) as usize)
9312 .min(snapshot.len());
9313 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9314 })
9315 })
9316 .collect::<Vec<_>>();
9317 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9318
9319 Tabstop {
9320 is_end_tabstop,
9321 ranges: tabstop_ranges,
9322 choices: tabstop.choices.clone(),
9323 }
9324 })
9325 .collect::<Vec<_>>()
9326 });
9327 if let Some(tabstop) = tabstops.first() {
9328 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9329 // Reverse order so that the first range is the newest created selection.
9330 // Completions will use it and autoscroll will prioritize it.
9331 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9332 });
9333
9334 if let Some(choices) = &tabstop.choices {
9335 if let Some(selection) = tabstop.ranges.first() {
9336 self.show_snippet_choices(choices, selection.clone(), cx)
9337 }
9338 }
9339
9340 // If we're already at the last tabstop and it's at the end of the snippet,
9341 // we're done, we don't need to keep the state around.
9342 if !tabstop.is_end_tabstop {
9343 let choices = tabstops
9344 .iter()
9345 .map(|tabstop| tabstop.choices.clone())
9346 .collect();
9347
9348 let ranges = tabstops
9349 .into_iter()
9350 .map(|tabstop| tabstop.ranges)
9351 .collect::<Vec<_>>();
9352
9353 self.snippet_stack.push(SnippetState {
9354 active_index: 0,
9355 ranges,
9356 choices,
9357 });
9358 }
9359
9360 // Check whether the just-entered snippet ends with an auto-closable bracket.
9361 if self.autoclose_regions.is_empty() {
9362 let snapshot = self.buffer.read(cx).snapshot(cx);
9363 for selection in &mut self.selections.all::<Point>(cx) {
9364 let selection_head = selection.head();
9365 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9366 continue;
9367 };
9368
9369 let mut bracket_pair = None;
9370 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9371 let prev_chars = snapshot
9372 .reversed_chars_at(selection_head)
9373 .collect::<String>();
9374 for (pair, enabled) in scope.brackets() {
9375 if enabled
9376 && pair.close
9377 && prev_chars.starts_with(pair.start.as_str())
9378 && next_chars.starts_with(pair.end.as_str())
9379 {
9380 bracket_pair = Some(pair.clone());
9381 break;
9382 }
9383 }
9384 if let Some(pair) = bracket_pair {
9385 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9386 let autoclose_enabled =
9387 self.use_autoclose && snapshot_settings.use_autoclose;
9388 if autoclose_enabled {
9389 let start = snapshot.anchor_after(selection_head);
9390 let end = snapshot.anchor_after(selection_head);
9391 self.autoclose_regions.push(AutocloseRegion {
9392 selection_id: selection.id,
9393 range: start..end,
9394 pair,
9395 });
9396 }
9397 }
9398 }
9399 }
9400 }
9401 Ok(())
9402 }
9403
9404 pub fn move_to_next_snippet_tabstop(
9405 &mut self,
9406 window: &mut Window,
9407 cx: &mut Context<Self>,
9408 ) -> bool {
9409 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9410 }
9411
9412 pub fn move_to_prev_snippet_tabstop(
9413 &mut self,
9414 window: &mut Window,
9415 cx: &mut Context<Self>,
9416 ) -> bool {
9417 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9418 }
9419
9420 pub fn move_to_snippet_tabstop(
9421 &mut self,
9422 bias: Bias,
9423 window: &mut Window,
9424 cx: &mut Context<Self>,
9425 ) -> bool {
9426 if let Some(mut snippet) = self.snippet_stack.pop() {
9427 match bias {
9428 Bias::Left => {
9429 if snippet.active_index > 0 {
9430 snippet.active_index -= 1;
9431 } else {
9432 self.snippet_stack.push(snippet);
9433 return false;
9434 }
9435 }
9436 Bias::Right => {
9437 if snippet.active_index + 1 < snippet.ranges.len() {
9438 snippet.active_index += 1;
9439 } else {
9440 self.snippet_stack.push(snippet);
9441 return false;
9442 }
9443 }
9444 }
9445 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9446 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9447 // Reverse order so that the first range is the newest created selection.
9448 // Completions will use it and autoscroll will prioritize it.
9449 s.select_ranges(current_ranges.iter().rev().cloned())
9450 });
9451
9452 if let Some(choices) = &snippet.choices[snippet.active_index] {
9453 if let Some(selection) = current_ranges.first() {
9454 self.show_snippet_choices(&choices, selection.clone(), cx);
9455 }
9456 }
9457
9458 // If snippet state is not at the last tabstop, push it back on the stack
9459 if snippet.active_index + 1 < snippet.ranges.len() {
9460 self.snippet_stack.push(snippet);
9461 }
9462 return true;
9463 }
9464 }
9465
9466 false
9467 }
9468
9469 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9470 self.transact(window, cx, |this, window, cx| {
9471 this.select_all(&SelectAll, window, cx);
9472 this.insert("", window, cx);
9473 });
9474 }
9475
9476 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9477 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9478 self.transact(window, cx, |this, window, cx| {
9479 this.select_autoclose_pair(window, cx);
9480 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9481 if !this.linked_edit_ranges.is_empty() {
9482 let selections = this.selections.all::<MultiBufferPoint>(cx);
9483 let snapshot = this.buffer.read(cx).snapshot(cx);
9484
9485 for selection in selections.iter() {
9486 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9487 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9488 if selection_start.buffer_id != selection_end.buffer_id {
9489 continue;
9490 }
9491 if let Some(ranges) =
9492 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9493 {
9494 for (buffer, entries) in ranges {
9495 linked_ranges.entry(buffer).or_default().extend(entries);
9496 }
9497 }
9498 }
9499 }
9500
9501 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9502 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9503 for selection in &mut selections {
9504 if selection.is_empty() {
9505 let old_head = selection.head();
9506 let mut new_head =
9507 movement::left(&display_map, old_head.to_display_point(&display_map))
9508 .to_point(&display_map);
9509 if let Some((buffer, line_buffer_range)) = display_map
9510 .buffer_snapshot
9511 .buffer_line_for_row(MultiBufferRow(old_head.row))
9512 {
9513 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9514 let indent_len = match indent_size.kind {
9515 IndentKind::Space => {
9516 buffer.settings_at(line_buffer_range.start, cx).tab_size
9517 }
9518 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9519 };
9520 if old_head.column <= indent_size.len && old_head.column > 0 {
9521 let indent_len = indent_len.get();
9522 new_head = cmp::min(
9523 new_head,
9524 MultiBufferPoint::new(
9525 old_head.row,
9526 ((old_head.column - 1) / indent_len) * indent_len,
9527 ),
9528 );
9529 }
9530 }
9531
9532 selection.set_head(new_head, SelectionGoal::None);
9533 }
9534 }
9535
9536 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9537 s.select(selections)
9538 });
9539 this.insert("", window, cx);
9540 let empty_str: Arc<str> = Arc::from("");
9541 for (buffer, edits) in linked_ranges {
9542 let snapshot = buffer.read(cx).snapshot();
9543 use text::ToPoint as TP;
9544
9545 let edits = edits
9546 .into_iter()
9547 .map(|range| {
9548 let end_point = TP::to_point(&range.end, &snapshot);
9549 let mut start_point = TP::to_point(&range.start, &snapshot);
9550
9551 if end_point == start_point {
9552 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9553 .saturating_sub(1);
9554 start_point =
9555 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9556 };
9557
9558 (start_point..end_point, empty_str.clone())
9559 })
9560 .sorted_by_key(|(range, _)| range.start)
9561 .collect::<Vec<_>>();
9562 buffer.update(cx, |this, cx| {
9563 this.edit(edits, None, cx);
9564 })
9565 }
9566 this.refresh_inline_completion(true, false, window, cx);
9567 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9568 });
9569 }
9570
9571 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9572 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9573 self.transact(window, cx, |this, window, cx| {
9574 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9575 s.move_with(|map, selection| {
9576 if selection.is_empty() {
9577 let cursor = movement::right(map, selection.head());
9578 selection.end = cursor;
9579 selection.reversed = true;
9580 selection.goal = SelectionGoal::None;
9581 }
9582 })
9583 });
9584 this.insert("", window, cx);
9585 this.refresh_inline_completion(true, false, window, cx);
9586 });
9587 }
9588
9589 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9590 if self.mode.is_single_line() {
9591 cx.propagate();
9592 return;
9593 }
9594
9595 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9596 if self.move_to_prev_snippet_tabstop(window, cx) {
9597 return;
9598 }
9599 self.outdent(&Outdent, window, cx);
9600 }
9601
9602 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9603 if self.mode.is_single_line() {
9604 cx.propagate();
9605 return;
9606 }
9607
9608 if self.move_to_next_snippet_tabstop(window, cx) {
9609 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9610 return;
9611 }
9612 if self.read_only(cx) {
9613 return;
9614 }
9615 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9616 let mut selections = self.selections.all_adjusted(cx);
9617 let buffer = self.buffer.read(cx);
9618 let snapshot = buffer.snapshot(cx);
9619 let rows_iter = selections.iter().map(|s| s.head().row);
9620 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9621
9622 let has_some_cursor_in_whitespace = selections
9623 .iter()
9624 .filter(|selection| selection.is_empty())
9625 .any(|selection| {
9626 let cursor = selection.head();
9627 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9628 cursor.column < current_indent.len
9629 });
9630
9631 let mut edits = Vec::new();
9632 let mut prev_edited_row = 0;
9633 let mut row_delta = 0;
9634 for selection in &mut selections {
9635 if selection.start.row != prev_edited_row {
9636 row_delta = 0;
9637 }
9638 prev_edited_row = selection.end.row;
9639
9640 // If the selection is non-empty, then increase the indentation of the selected lines.
9641 if !selection.is_empty() {
9642 row_delta =
9643 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9644 continue;
9645 }
9646
9647 let cursor = selection.head();
9648 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9649 if let Some(suggested_indent) =
9650 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9651 {
9652 // Don't do anything if already at suggested indent
9653 // and there is any other cursor which is not
9654 if has_some_cursor_in_whitespace
9655 && cursor.column == current_indent.len
9656 && current_indent.len == suggested_indent.len
9657 {
9658 continue;
9659 }
9660
9661 // Adjust line and move cursor to suggested indent
9662 // if cursor is not at suggested indent
9663 if cursor.column < suggested_indent.len
9664 && cursor.column <= current_indent.len
9665 && current_indent.len <= suggested_indent.len
9666 {
9667 selection.start = Point::new(cursor.row, suggested_indent.len);
9668 selection.end = selection.start;
9669 if row_delta == 0 {
9670 edits.extend(Buffer::edit_for_indent_size_adjustment(
9671 cursor.row,
9672 current_indent,
9673 suggested_indent,
9674 ));
9675 row_delta = suggested_indent.len - current_indent.len;
9676 }
9677 continue;
9678 }
9679
9680 // If current indent is more than suggested indent
9681 // only move cursor to current indent and skip indent
9682 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9683 selection.start = Point::new(cursor.row, current_indent.len);
9684 selection.end = selection.start;
9685 continue;
9686 }
9687 }
9688
9689 // Otherwise, insert a hard or soft tab.
9690 let settings = buffer.language_settings_at(cursor, cx);
9691 let tab_size = if settings.hard_tabs {
9692 IndentSize::tab()
9693 } else {
9694 let tab_size = settings.tab_size.get();
9695 let indent_remainder = snapshot
9696 .text_for_range(Point::new(cursor.row, 0)..cursor)
9697 .flat_map(str::chars)
9698 .fold(row_delta % tab_size, |counter: u32, c| {
9699 if c == '\t' {
9700 0
9701 } else {
9702 (counter + 1) % tab_size
9703 }
9704 });
9705
9706 let chars_to_next_tab_stop = tab_size - indent_remainder;
9707 IndentSize::spaces(chars_to_next_tab_stop)
9708 };
9709 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9710 selection.end = selection.start;
9711 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9712 row_delta += tab_size.len;
9713 }
9714
9715 self.transact(window, cx, |this, window, cx| {
9716 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9717 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9718 s.select(selections)
9719 });
9720 this.refresh_inline_completion(true, false, window, cx);
9721 });
9722 }
9723
9724 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9725 if self.read_only(cx) {
9726 return;
9727 }
9728 if self.mode.is_single_line() {
9729 cx.propagate();
9730 return;
9731 }
9732
9733 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9734 let mut selections = self.selections.all::<Point>(cx);
9735 let mut prev_edited_row = 0;
9736 let mut row_delta = 0;
9737 let mut edits = Vec::new();
9738 let buffer = self.buffer.read(cx);
9739 let snapshot = buffer.snapshot(cx);
9740 for selection in &mut selections {
9741 if selection.start.row != prev_edited_row {
9742 row_delta = 0;
9743 }
9744 prev_edited_row = selection.end.row;
9745
9746 row_delta =
9747 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9748 }
9749
9750 self.transact(window, cx, |this, window, cx| {
9751 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9752 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9753 s.select(selections)
9754 });
9755 });
9756 }
9757
9758 fn indent_selection(
9759 buffer: &MultiBuffer,
9760 snapshot: &MultiBufferSnapshot,
9761 selection: &mut Selection<Point>,
9762 edits: &mut Vec<(Range<Point>, String)>,
9763 delta_for_start_row: u32,
9764 cx: &App,
9765 ) -> u32 {
9766 let settings = buffer.language_settings_at(selection.start, cx);
9767 let tab_size = settings.tab_size.get();
9768 let indent_kind = if settings.hard_tabs {
9769 IndentKind::Tab
9770 } else {
9771 IndentKind::Space
9772 };
9773 let mut start_row = selection.start.row;
9774 let mut end_row = selection.end.row + 1;
9775
9776 // If a selection ends at the beginning of a line, don't indent
9777 // that last line.
9778 if selection.end.column == 0 && selection.end.row > selection.start.row {
9779 end_row -= 1;
9780 }
9781
9782 // Avoid re-indenting a row that has already been indented by a
9783 // previous selection, but still update this selection's column
9784 // to reflect that indentation.
9785 if delta_for_start_row > 0 {
9786 start_row += 1;
9787 selection.start.column += delta_for_start_row;
9788 if selection.end.row == selection.start.row {
9789 selection.end.column += delta_for_start_row;
9790 }
9791 }
9792
9793 let mut delta_for_end_row = 0;
9794 let has_multiple_rows = start_row + 1 != end_row;
9795 for row in start_row..end_row {
9796 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9797 let indent_delta = match (current_indent.kind, indent_kind) {
9798 (IndentKind::Space, IndentKind::Space) => {
9799 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9800 IndentSize::spaces(columns_to_next_tab_stop)
9801 }
9802 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9803 (_, IndentKind::Tab) => IndentSize::tab(),
9804 };
9805
9806 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9807 0
9808 } else {
9809 selection.start.column
9810 };
9811 let row_start = Point::new(row, start);
9812 edits.push((
9813 row_start..row_start,
9814 indent_delta.chars().collect::<String>(),
9815 ));
9816
9817 // Update this selection's endpoints to reflect the indentation.
9818 if row == selection.start.row {
9819 selection.start.column += indent_delta.len;
9820 }
9821 if row == selection.end.row {
9822 selection.end.column += indent_delta.len;
9823 delta_for_end_row = indent_delta.len;
9824 }
9825 }
9826
9827 if selection.start.row == selection.end.row {
9828 delta_for_start_row + delta_for_end_row
9829 } else {
9830 delta_for_end_row
9831 }
9832 }
9833
9834 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9835 if self.read_only(cx) {
9836 return;
9837 }
9838 if self.mode.is_single_line() {
9839 cx.propagate();
9840 return;
9841 }
9842
9843 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9844 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9845 let selections = self.selections.all::<Point>(cx);
9846 let mut deletion_ranges = Vec::new();
9847 let mut last_outdent = None;
9848 {
9849 let buffer = self.buffer.read(cx);
9850 let snapshot = buffer.snapshot(cx);
9851 for selection in &selections {
9852 let settings = buffer.language_settings_at(selection.start, cx);
9853 let tab_size = settings.tab_size.get();
9854 let mut rows = selection.spanned_rows(false, &display_map);
9855
9856 // Avoid re-outdenting a row that has already been outdented by a
9857 // previous selection.
9858 if let Some(last_row) = last_outdent {
9859 if last_row == rows.start {
9860 rows.start = rows.start.next_row();
9861 }
9862 }
9863 let has_multiple_rows = rows.len() > 1;
9864 for row in rows.iter_rows() {
9865 let indent_size = snapshot.indent_size_for_line(row);
9866 if indent_size.len > 0 {
9867 let deletion_len = match indent_size.kind {
9868 IndentKind::Space => {
9869 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9870 if columns_to_prev_tab_stop == 0 {
9871 tab_size
9872 } else {
9873 columns_to_prev_tab_stop
9874 }
9875 }
9876 IndentKind::Tab => 1,
9877 };
9878 let start = if has_multiple_rows
9879 || deletion_len > selection.start.column
9880 || indent_size.len < selection.start.column
9881 {
9882 0
9883 } else {
9884 selection.start.column - deletion_len
9885 };
9886 deletion_ranges.push(
9887 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9888 );
9889 last_outdent = Some(row);
9890 }
9891 }
9892 }
9893 }
9894
9895 self.transact(window, cx, |this, window, cx| {
9896 this.buffer.update(cx, |buffer, cx| {
9897 let empty_str: Arc<str> = Arc::default();
9898 buffer.edit(
9899 deletion_ranges
9900 .into_iter()
9901 .map(|range| (range, empty_str.clone())),
9902 None,
9903 cx,
9904 );
9905 });
9906 let selections = this.selections.all::<usize>(cx);
9907 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9908 s.select(selections)
9909 });
9910 });
9911 }
9912
9913 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9914 if self.read_only(cx) {
9915 return;
9916 }
9917 if self.mode.is_single_line() {
9918 cx.propagate();
9919 return;
9920 }
9921
9922 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9923 let selections = self
9924 .selections
9925 .all::<usize>(cx)
9926 .into_iter()
9927 .map(|s| s.range());
9928
9929 self.transact(window, cx, |this, window, cx| {
9930 this.buffer.update(cx, |buffer, cx| {
9931 buffer.autoindent_ranges(selections, cx);
9932 });
9933 let selections = this.selections.all::<usize>(cx);
9934 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9935 s.select(selections)
9936 });
9937 });
9938 }
9939
9940 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9941 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9942 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9943 let selections = self.selections.all::<Point>(cx);
9944
9945 let mut new_cursors = Vec::new();
9946 let mut edit_ranges = Vec::new();
9947 let mut selections = selections.iter().peekable();
9948 while let Some(selection) = selections.next() {
9949 let mut rows = selection.spanned_rows(false, &display_map);
9950 let goal_display_column = selection.head().to_display_point(&display_map).column();
9951
9952 // Accumulate contiguous regions of rows that we want to delete.
9953 while let Some(next_selection) = selections.peek() {
9954 let next_rows = next_selection.spanned_rows(false, &display_map);
9955 if next_rows.start <= rows.end {
9956 rows.end = next_rows.end;
9957 selections.next().unwrap();
9958 } else {
9959 break;
9960 }
9961 }
9962
9963 let buffer = &display_map.buffer_snapshot;
9964 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9965 let edit_end;
9966 let cursor_buffer_row;
9967 if buffer.max_point().row >= rows.end.0 {
9968 // If there's a line after the range, delete the \n from the end of the row range
9969 // and position the cursor on the next line.
9970 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9971 cursor_buffer_row = rows.end;
9972 } else {
9973 // If there isn't a line after the range, delete the \n from the line before the
9974 // start of the row range and position the cursor there.
9975 edit_start = edit_start.saturating_sub(1);
9976 edit_end = buffer.len();
9977 cursor_buffer_row = rows.start.previous_row();
9978 }
9979
9980 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9981 *cursor.column_mut() =
9982 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9983
9984 new_cursors.push((
9985 selection.id,
9986 buffer.anchor_after(cursor.to_point(&display_map)),
9987 ));
9988 edit_ranges.push(edit_start..edit_end);
9989 }
9990
9991 self.transact(window, cx, |this, window, cx| {
9992 let buffer = this.buffer.update(cx, |buffer, cx| {
9993 let empty_str: Arc<str> = Arc::default();
9994 buffer.edit(
9995 edit_ranges
9996 .into_iter()
9997 .map(|range| (range, empty_str.clone())),
9998 None,
9999 cx,
10000 );
10001 buffer.snapshot(cx)
10002 });
10003 let new_selections = new_cursors
10004 .into_iter()
10005 .map(|(id, cursor)| {
10006 let cursor = cursor.to_point(&buffer);
10007 Selection {
10008 id,
10009 start: cursor,
10010 end: cursor,
10011 reversed: false,
10012 goal: SelectionGoal::None,
10013 }
10014 })
10015 .collect();
10016
10017 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10018 s.select(new_selections);
10019 });
10020 });
10021 }
10022
10023 pub fn join_lines_impl(
10024 &mut self,
10025 insert_whitespace: bool,
10026 window: &mut Window,
10027 cx: &mut Context<Self>,
10028 ) {
10029 if self.read_only(cx) {
10030 return;
10031 }
10032 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10033 for selection in self.selections.all::<Point>(cx) {
10034 let start = MultiBufferRow(selection.start.row);
10035 // Treat single line selections as if they include the next line. Otherwise this action
10036 // would do nothing for single line selections individual cursors.
10037 let end = if selection.start.row == selection.end.row {
10038 MultiBufferRow(selection.start.row + 1)
10039 } else {
10040 MultiBufferRow(selection.end.row)
10041 };
10042
10043 if let Some(last_row_range) = row_ranges.last_mut() {
10044 if start <= last_row_range.end {
10045 last_row_range.end = end;
10046 continue;
10047 }
10048 }
10049 row_ranges.push(start..end);
10050 }
10051
10052 let snapshot = self.buffer.read(cx).snapshot(cx);
10053 let mut cursor_positions = Vec::new();
10054 for row_range in &row_ranges {
10055 let anchor = snapshot.anchor_before(Point::new(
10056 row_range.end.previous_row().0,
10057 snapshot.line_len(row_range.end.previous_row()),
10058 ));
10059 cursor_positions.push(anchor..anchor);
10060 }
10061
10062 self.transact(window, cx, |this, window, cx| {
10063 for row_range in row_ranges.into_iter().rev() {
10064 for row in row_range.iter_rows().rev() {
10065 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10066 let next_line_row = row.next_row();
10067 let indent = snapshot.indent_size_for_line(next_line_row);
10068 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10069
10070 let replace =
10071 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10072 " "
10073 } else {
10074 ""
10075 };
10076
10077 this.buffer.update(cx, |buffer, cx| {
10078 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10079 });
10080 }
10081 }
10082
10083 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10084 s.select_anchor_ranges(cursor_positions)
10085 });
10086 });
10087 }
10088
10089 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10090 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10091 self.join_lines_impl(true, window, cx);
10092 }
10093
10094 pub fn sort_lines_case_sensitive(
10095 &mut self,
10096 _: &SortLinesCaseSensitive,
10097 window: &mut Window,
10098 cx: &mut Context<Self>,
10099 ) {
10100 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10101 }
10102
10103 pub fn sort_lines_case_insensitive(
10104 &mut self,
10105 _: &SortLinesCaseInsensitive,
10106 window: &mut Window,
10107 cx: &mut Context<Self>,
10108 ) {
10109 self.manipulate_immutable_lines(window, cx, |lines| {
10110 lines.sort_by_key(|line| line.to_lowercase())
10111 })
10112 }
10113
10114 pub fn unique_lines_case_insensitive(
10115 &mut self,
10116 _: &UniqueLinesCaseInsensitive,
10117 window: &mut Window,
10118 cx: &mut Context<Self>,
10119 ) {
10120 self.manipulate_immutable_lines(window, cx, |lines| {
10121 let mut seen = HashSet::default();
10122 lines.retain(|line| seen.insert(line.to_lowercase()));
10123 })
10124 }
10125
10126 pub fn unique_lines_case_sensitive(
10127 &mut self,
10128 _: &UniqueLinesCaseSensitive,
10129 window: &mut Window,
10130 cx: &mut Context<Self>,
10131 ) {
10132 self.manipulate_immutable_lines(window, cx, |lines| {
10133 let mut seen = HashSet::default();
10134 lines.retain(|line| seen.insert(*line));
10135 })
10136 }
10137
10138 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10139 let Some(project) = self.project.clone() else {
10140 return;
10141 };
10142 self.reload(project, window, cx)
10143 .detach_and_notify_err(window, cx);
10144 }
10145
10146 pub fn restore_file(
10147 &mut self,
10148 _: &::git::RestoreFile,
10149 window: &mut Window,
10150 cx: &mut Context<Self>,
10151 ) {
10152 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10153 let mut buffer_ids = HashSet::default();
10154 let snapshot = self.buffer().read(cx).snapshot(cx);
10155 for selection in self.selections.all::<usize>(cx) {
10156 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10157 }
10158
10159 let buffer = self.buffer().read(cx);
10160 let ranges = buffer_ids
10161 .into_iter()
10162 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10163 .collect::<Vec<_>>();
10164
10165 self.restore_hunks_in_ranges(ranges, window, cx);
10166 }
10167
10168 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10169 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10170 let selections = self
10171 .selections
10172 .all(cx)
10173 .into_iter()
10174 .map(|s| s.range())
10175 .collect();
10176 self.restore_hunks_in_ranges(selections, window, cx);
10177 }
10178
10179 pub fn restore_hunks_in_ranges(
10180 &mut self,
10181 ranges: Vec<Range<Point>>,
10182 window: &mut Window,
10183 cx: &mut Context<Editor>,
10184 ) {
10185 let mut revert_changes = HashMap::default();
10186 let chunk_by = self
10187 .snapshot(window, cx)
10188 .hunks_for_ranges(ranges)
10189 .into_iter()
10190 .chunk_by(|hunk| hunk.buffer_id);
10191 for (buffer_id, hunks) in &chunk_by {
10192 let hunks = hunks.collect::<Vec<_>>();
10193 for hunk in &hunks {
10194 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10195 }
10196 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10197 }
10198 drop(chunk_by);
10199 if !revert_changes.is_empty() {
10200 self.transact(window, cx, |editor, window, cx| {
10201 editor.restore(revert_changes, window, cx);
10202 });
10203 }
10204 }
10205
10206 pub fn open_active_item_in_terminal(
10207 &mut self,
10208 _: &OpenInTerminal,
10209 window: &mut Window,
10210 cx: &mut Context<Self>,
10211 ) {
10212 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10213 let project_path = buffer.read(cx).project_path(cx)?;
10214 let project = self.project.as_ref()?.read(cx);
10215 let entry = project.entry_for_path(&project_path, cx)?;
10216 let parent = match &entry.canonical_path {
10217 Some(canonical_path) => canonical_path.to_path_buf(),
10218 None => project.absolute_path(&project_path, cx)?,
10219 }
10220 .parent()?
10221 .to_path_buf();
10222 Some(parent)
10223 }) {
10224 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10225 }
10226 }
10227
10228 fn set_breakpoint_context_menu(
10229 &mut self,
10230 display_row: DisplayRow,
10231 position: Option<Anchor>,
10232 clicked_point: gpui::Point<Pixels>,
10233 window: &mut Window,
10234 cx: &mut Context<Self>,
10235 ) {
10236 let source = self
10237 .buffer
10238 .read(cx)
10239 .snapshot(cx)
10240 .anchor_before(Point::new(display_row.0, 0u32));
10241
10242 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10243
10244 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10245 self,
10246 source,
10247 clicked_point,
10248 context_menu,
10249 window,
10250 cx,
10251 );
10252 }
10253
10254 fn add_edit_breakpoint_block(
10255 &mut self,
10256 anchor: Anchor,
10257 breakpoint: &Breakpoint,
10258 edit_action: BreakpointPromptEditAction,
10259 window: &mut Window,
10260 cx: &mut Context<Self>,
10261 ) {
10262 let weak_editor = cx.weak_entity();
10263 let bp_prompt = cx.new(|cx| {
10264 BreakpointPromptEditor::new(
10265 weak_editor,
10266 anchor,
10267 breakpoint.clone(),
10268 edit_action,
10269 window,
10270 cx,
10271 )
10272 });
10273
10274 let height = bp_prompt.update(cx, |this, cx| {
10275 this.prompt
10276 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10277 });
10278 let cloned_prompt = bp_prompt.clone();
10279 let blocks = vec![BlockProperties {
10280 style: BlockStyle::Sticky,
10281 placement: BlockPlacement::Above(anchor),
10282 height: Some(height),
10283 render: Arc::new(move |cx| {
10284 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10285 cloned_prompt.clone().into_any_element()
10286 }),
10287 priority: 0,
10288 render_in_minimap: true,
10289 }];
10290
10291 let focus_handle = bp_prompt.focus_handle(cx);
10292 window.focus(&focus_handle);
10293
10294 let block_ids = self.insert_blocks(blocks, None, cx);
10295 bp_prompt.update(cx, |prompt, _| {
10296 prompt.add_block_ids(block_ids);
10297 });
10298 }
10299
10300 pub(crate) fn breakpoint_at_row(
10301 &self,
10302 row: u32,
10303 window: &mut Window,
10304 cx: &mut Context<Self>,
10305 ) -> Option<(Anchor, Breakpoint)> {
10306 let snapshot = self.snapshot(window, cx);
10307 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10308
10309 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10310 }
10311
10312 pub(crate) fn breakpoint_at_anchor(
10313 &self,
10314 breakpoint_position: Anchor,
10315 snapshot: &EditorSnapshot,
10316 cx: &mut Context<Self>,
10317 ) -> Option<(Anchor, Breakpoint)> {
10318 let project = self.project.clone()?;
10319
10320 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10321 snapshot
10322 .buffer_snapshot
10323 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10324 })?;
10325
10326 let enclosing_excerpt = breakpoint_position.excerpt_id;
10327 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10328 let buffer_snapshot = buffer.read(cx).snapshot();
10329
10330 let row = buffer_snapshot
10331 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10332 .row;
10333
10334 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10335 let anchor_end = snapshot
10336 .buffer_snapshot
10337 .anchor_after(Point::new(row, line_len));
10338
10339 let bp = self
10340 .breakpoint_store
10341 .as_ref()?
10342 .read_with(cx, |breakpoint_store, cx| {
10343 breakpoint_store
10344 .breakpoints(
10345 &buffer,
10346 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10347 &buffer_snapshot,
10348 cx,
10349 )
10350 .next()
10351 .and_then(|(bp, _)| {
10352 let breakpoint_row = buffer_snapshot
10353 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10354 .row;
10355
10356 if breakpoint_row == row {
10357 snapshot
10358 .buffer_snapshot
10359 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10360 .map(|position| (position, bp.bp.clone()))
10361 } else {
10362 None
10363 }
10364 })
10365 });
10366 bp
10367 }
10368
10369 pub fn edit_log_breakpoint(
10370 &mut self,
10371 _: &EditLogBreakpoint,
10372 window: &mut Window,
10373 cx: &mut Context<Self>,
10374 ) {
10375 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10376 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10377 message: None,
10378 state: BreakpointState::Enabled,
10379 condition: None,
10380 hit_condition: None,
10381 });
10382
10383 self.add_edit_breakpoint_block(
10384 anchor,
10385 &breakpoint,
10386 BreakpointPromptEditAction::Log,
10387 window,
10388 cx,
10389 );
10390 }
10391 }
10392
10393 fn breakpoints_at_cursors(
10394 &self,
10395 window: &mut Window,
10396 cx: &mut Context<Self>,
10397 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10398 let snapshot = self.snapshot(window, cx);
10399 let cursors = self
10400 .selections
10401 .disjoint_anchors()
10402 .into_iter()
10403 .map(|selection| {
10404 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10405
10406 let breakpoint_position = self
10407 .breakpoint_at_row(cursor_position.row, window, cx)
10408 .map(|bp| bp.0)
10409 .unwrap_or_else(|| {
10410 snapshot
10411 .display_snapshot
10412 .buffer_snapshot
10413 .anchor_after(Point::new(cursor_position.row, 0))
10414 });
10415
10416 let breakpoint = self
10417 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10418 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10419
10420 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10421 })
10422 // 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.
10423 .collect::<HashMap<Anchor, _>>();
10424
10425 cursors.into_iter().collect()
10426 }
10427
10428 pub fn enable_breakpoint(
10429 &mut self,
10430 _: &crate::actions::EnableBreakpoint,
10431 window: &mut Window,
10432 cx: &mut Context<Self>,
10433 ) {
10434 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10435 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10436 continue;
10437 };
10438 self.edit_breakpoint_at_anchor(
10439 anchor,
10440 breakpoint,
10441 BreakpointEditAction::InvertState,
10442 cx,
10443 );
10444 }
10445 }
10446
10447 pub fn disable_breakpoint(
10448 &mut self,
10449 _: &crate::actions::DisableBreakpoint,
10450 window: &mut Window,
10451 cx: &mut Context<Self>,
10452 ) {
10453 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10454 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10455 continue;
10456 };
10457 self.edit_breakpoint_at_anchor(
10458 anchor,
10459 breakpoint,
10460 BreakpointEditAction::InvertState,
10461 cx,
10462 );
10463 }
10464 }
10465
10466 pub fn toggle_breakpoint(
10467 &mut self,
10468 _: &crate::actions::ToggleBreakpoint,
10469 window: &mut Window,
10470 cx: &mut Context<Self>,
10471 ) {
10472 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10473 if let Some(breakpoint) = breakpoint {
10474 self.edit_breakpoint_at_anchor(
10475 anchor,
10476 breakpoint,
10477 BreakpointEditAction::Toggle,
10478 cx,
10479 );
10480 } else {
10481 self.edit_breakpoint_at_anchor(
10482 anchor,
10483 Breakpoint::new_standard(),
10484 BreakpointEditAction::Toggle,
10485 cx,
10486 );
10487 }
10488 }
10489 }
10490
10491 pub fn edit_breakpoint_at_anchor(
10492 &mut self,
10493 breakpoint_position: Anchor,
10494 breakpoint: Breakpoint,
10495 edit_action: BreakpointEditAction,
10496 cx: &mut Context<Self>,
10497 ) {
10498 let Some(breakpoint_store) = &self.breakpoint_store else {
10499 return;
10500 };
10501
10502 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10503 if breakpoint_position == Anchor::min() {
10504 self.buffer()
10505 .read(cx)
10506 .excerpt_buffer_ids()
10507 .into_iter()
10508 .next()
10509 } else {
10510 None
10511 }
10512 }) else {
10513 return;
10514 };
10515
10516 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10517 return;
10518 };
10519
10520 breakpoint_store.update(cx, |breakpoint_store, cx| {
10521 breakpoint_store.toggle_breakpoint(
10522 buffer,
10523 BreakpointWithPosition {
10524 position: breakpoint_position.text_anchor,
10525 bp: breakpoint,
10526 },
10527 edit_action,
10528 cx,
10529 );
10530 });
10531
10532 cx.notify();
10533 }
10534
10535 #[cfg(any(test, feature = "test-support"))]
10536 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10537 self.breakpoint_store.clone()
10538 }
10539
10540 pub fn prepare_restore_change(
10541 &self,
10542 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10543 hunk: &MultiBufferDiffHunk,
10544 cx: &mut App,
10545 ) -> Option<()> {
10546 if hunk.is_created_file() {
10547 return None;
10548 }
10549 let buffer = self.buffer.read(cx);
10550 let diff = buffer.diff_for(hunk.buffer_id)?;
10551 let buffer = buffer.buffer(hunk.buffer_id)?;
10552 let buffer = buffer.read(cx);
10553 let original_text = diff
10554 .read(cx)
10555 .base_text()
10556 .as_rope()
10557 .slice(hunk.diff_base_byte_range.clone());
10558 let buffer_snapshot = buffer.snapshot();
10559 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10560 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10561 probe
10562 .0
10563 .start
10564 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10565 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10566 }) {
10567 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10568 Some(())
10569 } else {
10570 None
10571 }
10572 }
10573
10574 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10575 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10576 }
10577
10578 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10579 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10580 }
10581
10582 fn manipulate_lines<M>(
10583 &mut self,
10584 window: &mut Window,
10585 cx: &mut Context<Self>,
10586 mut manipulate: M,
10587 ) where
10588 M: FnMut(&str) -> LineManipulationResult,
10589 {
10590 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10591
10592 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10593 let buffer = self.buffer.read(cx).snapshot(cx);
10594
10595 let mut edits = Vec::new();
10596
10597 let selections = self.selections.all::<Point>(cx);
10598 let mut selections = selections.iter().peekable();
10599 let mut contiguous_row_selections = Vec::new();
10600 let mut new_selections = Vec::new();
10601 let mut added_lines = 0;
10602 let mut removed_lines = 0;
10603
10604 while let Some(selection) = selections.next() {
10605 let (start_row, end_row) = consume_contiguous_rows(
10606 &mut contiguous_row_selections,
10607 selection,
10608 &display_map,
10609 &mut selections,
10610 );
10611
10612 let start_point = Point::new(start_row.0, 0);
10613 let end_point = Point::new(
10614 end_row.previous_row().0,
10615 buffer.line_len(end_row.previous_row()),
10616 );
10617 let text = buffer
10618 .text_for_range(start_point..end_point)
10619 .collect::<String>();
10620
10621 let LineManipulationResult {
10622 new_text,
10623 line_count_before,
10624 line_count_after,
10625 } = manipulate(&text);
10626
10627 edits.push((start_point..end_point, new_text));
10628
10629 // Selections must change based on added and removed line count
10630 let start_row =
10631 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10632 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10633 new_selections.push(Selection {
10634 id: selection.id,
10635 start: start_row,
10636 end: end_row,
10637 goal: SelectionGoal::None,
10638 reversed: selection.reversed,
10639 });
10640
10641 if line_count_after > line_count_before {
10642 added_lines += line_count_after - line_count_before;
10643 } else if line_count_before > line_count_after {
10644 removed_lines += line_count_before - line_count_after;
10645 }
10646 }
10647
10648 self.transact(window, cx, |this, window, cx| {
10649 let buffer = this.buffer.update(cx, |buffer, cx| {
10650 buffer.edit(edits, None, cx);
10651 buffer.snapshot(cx)
10652 });
10653
10654 // Recalculate offsets on newly edited buffer
10655 let new_selections = new_selections
10656 .iter()
10657 .map(|s| {
10658 let start_point = Point::new(s.start.0, 0);
10659 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10660 Selection {
10661 id: s.id,
10662 start: buffer.point_to_offset(start_point),
10663 end: buffer.point_to_offset(end_point),
10664 goal: s.goal,
10665 reversed: s.reversed,
10666 }
10667 })
10668 .collect();
10669
10670 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10671 s.select(new_selections);
10672 });
10673
10674 this.request_autoscroll(Autoscroll::fit(), cx);
10675 });
10676 }
10677
10678 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10679 self.manipulate_text(window, cx, |text| {
10680 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10681 if has_upper_case_characters {
10682 text.to_lowercase()
10683 } else {
10684 text.to_uppercase()
10685 }
10686 })
10687 }
10688
10689 fn manipulate_immutable_lines<Fn>(
10690 &mut self,
10691 window: &mut Window,
10692 cx: &mut Context<Self>,
10693 mut callback: Fn,
10694 ) where
10695 Fn: FnMut(&mut Vec<&str>),
10696 {
10697 self.manipulate_lines(window, cx, |text| {
10698 let mut lines: Vec<&str> = text.split('\n').collect();
10699 let line_count_before = lines.len();
10700
10701 callback(&mut lines);
10702
10703 LineManipulationResult {
10704 new_text: lines.join("\n"),
10705 line_count_before,
10706 line_count_after: lines.len(),
10707 }
10708 });
10709 }
10710
10711 fn manipulate_mutable_lines<Fn>(
10712 &mut self,
10713 window: &mut Window,
10714 cx: &mut Context<Self>,
10715 mut callback: Fn,
10716 ) where
10717 Fn: FnMut(&mut Vec<Cow<'_, str>>),
10718 {
10719 self.manipulate_lines(window, cx, |text| {
10720 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
10721 let line_count_before = lines.len();
10722
10723 callback(&mut lines);
10724
10725 LineManipulationResult {
10726 new_text: lines.join("\n"),
10727 line_count_before,
10728 line_count_after: lines.len(),
10729 }
10730 });
10731 }
10732
10733 pub fn convert_indentation_to_spaces(
10734 &mut self,
10735 _: &ConvertIndentationToSpaces,
10736 window: &mut Window,
10737 cx: &mut Context<Self>,
10738 ) {
10739 let settings = self.buffer.read(cx).language_settings(cx);
10740 let tab_size = settings.tab_size.get() as usize;
10741
10742 self.manipulate_mutable_lines(window, cx, |lines| {
10743 // Allocates a reasonably sized scratch buffer once for the whole loop
10744 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10745 // Avoids recomputing spaces that could be inserted many times
10746 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10747 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10748 .collect();
10749
10750 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10751 let mut chars = line.as_ref().chars();
10752 let mut col = 0;
10753 let mut changed = false;
10754
10755 while let Some(ch) = chars.next() {
10756 match ch {
10757 ' ' => {
10758 reindented_line.push(' ');
10759 col += 1;
10760 }
10761 '\t' => {
10762 // \t are converted to spaces depending on the current column
10763 let spaces_len = tab_size - (col % tab_size);
10764 reindented_line.extend(&space_cache[spaces_len - 1]);
10765 col += spaces_len;
10766 changed = true;
10767 }
10768 _ => {
10769 // If we dont append before break, the character is consumed
10770 reindented_line.push(ch);
10771 break;
10772 }
10773 }
10774 }
10775
10776 if !changed {
10777 reindented_line.clear();
10778 continue;
10779 }
10780 // Append the rest of the line and replace old reference with new one
10781 reindented_line.extend(chars);
10782 *line = Cow::Owned(reindented_line.clone());
10783 reindented_line.clear();
10784 }
10785 });
10786 }
10787
10788 pub fn convert_indentation_to_tabs(
10789 &mut self,
10790 _: &ConvertIndentationToTabs,
10791 window: &mut Window,
10792 cx: &mut Context<Self>,
10793 ) {
10794 let settings = self.buffer.read(cx).language_settings(cx);
10795 let tab_size = settings.tab_size.get() as usize;
10796
10797 self.manipulate_mutable_lines(window, cx, |lines| {
10798 // Allocates a reasonably sized buffer once for the whole loop
10799 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10800 // Avoids recomputing spaces that could be inserted many times
10801 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10802 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10803 .collect();
10804
10805 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10806 let mut chars = line.chars();
10807 let mut spaces_count = 0;
10808 let mut first_non_indent_char = None;
10809 let mut changed = false;
10810
10811 while let Some(ch) = chars.next() {
10812 match ch {
10813 ' ' => {
10814 // Keep track of spaces. Append \t when we reach tab_size
10815 spaces_count += 1;
10816 changed = true;
10817 if spaces_count == tab_size {
10818 reindented_line.push('\t');
10819 spaces_count = 0;
10820 }
10821 }
10822 '\t' => {
10823 reindented_line.push('\t');
10824 spaces_count = 0;
10825 }
10826 _ => {
10827 // Dont append it yet, we might have remaining spaces
10828 first_non_indent_char = Some(ch);
10829 break;
10830 }
10831 }
10832 }
10833
10834 if !changed {
10835 reindented_line.clear();
10836 continue;
10837 }
10838 // Remaining spaces that didn't make a full tab stop
10839 if spaces_count > 0 {
10840 reindented_line.extend(&space_cache[spaces_count - 1]);
10841 }
10842 // If we consume an extra character that was not indentation, add it back
10843 if let Some(extra_char) = first_non_indent_char {
10844 reindented_line.push(extra_char);
10845 }
10846 // Append the rest of the line and replace old reference with new one
10847 reindented_line.extend(chars);
10848 *line = Cow::Owned(reindented_line.clone());
10849 reindented_line.clear();
10850 }
10851 });
10852 }
10853
10854 pub fn convert_to_upper_case(
10855 &mut self,
10856 _: &ConvertToUpperCase,
10857 window: &mut Window,
10858 cx: &mut Context<Self>,
10859 ) {
10860 self.manipulate_text(window, cx, |text| text.to_uppercase())
10861 }
10862
10863 pub fn convert_to_lower_case(
10864 &mut self,
10865 _: &ConvertToLowerCase,
10866 window: &mut Window,
10867 cx: &mut Context<Self>,
10868 ) {
10869 self.manipulate_text(window, cx, |text| text.to_lowercase())
10870 }
10871
10872 pub fn convert_to_title_case(
10873 &mut self,
10874 _: &ConvertToTitleCase,
10875 window: &mut Window,
10876 cx: &mut Context<Self>,
10877 ) {
10878 self.manipulate_text(window, cx, |text| {
10879 text.split('\n')
10880 .map(|line| line.to_case(Case::Title))
10881 .join("\n")
10882 })
10883 }
10884
10885 pub fn convert_to_snake_case(
10886 &mut self,
10887 _: &ConvertToSnakeCase,
10888 window: &mut Window,
10889 cx: &mut Context<Self>,
10890 ) {
10891 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10892 }
10893
10894 pub fn convert_to_kebab_case(
10895 &mut self,
10896 _: &ConvertToKebabCase,
10897 window: &mut Window,
10898 cx: &mut Context<Self>,
10899 ) {
10900 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10901 }
10902
10903 pub fn convert_to_upper_camel_case(
10904 &mut self,
10905 _: &ConvertToUpperCamelCase,
10906 window: &mut Window,
10907 cx: &mut Context<Self>,
10908 ) {
10909 self.manipulate_text(window, cx, |text| {
10910 text.split('\n')
10911 .map(|line| line.to_case(Case::UpperCamel))
10912 .join("\n")
10913 })
10914 }
10915
10916 pub fn convert_to_lower_camel_case(
10917 &mut self,
10918 _: &ConvertToLowerCamelCase,
10919 window: &mut Window,
10920 cx: &mut Context<Self>,
10921 ) {
10922 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10923 }
10924
10925 pub fn convert_to_opposite_case(
10926 &mut self,
10927 _: &ConvertToOppositeCase,
10928 window: &mut Window,
10929 cx: &mut Context<Self>,
10930 ) {
10931 self.manipulate_text(window, cx, |text| {
10932 text.chars()
10933 .fold(String::with_capacity(text.len()), |mut t, c| {
10934 if c.is_uppercase() {
10935 t.extend(c.to_lowercase());
10936 } else {
10937 t.extend(c.to_uppercase());
10938 }
10939 t
10940 })
10941 })
10942 }
10943
10944 pub fn convert_to_rot13(
10945 &mut self,
10946 _: &ConvertToRot13,
10947 window: &mut Window,
10948 cx: &mut Context<Self>,
10949 ) {
10950 self.manipulate_text(window, cx, |text| {
10951 text.chars()
10952 .map(|c| match c {
10953 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10954 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10955 _ => c,
10956 })
10957 .collect()
10958 })
10959 }
10960
10961 pub fn convert_to_rot47(
10962 &mut self,
10963 _: &ConvertToRot47,
10964 window: &mut Window,
10965 cx: &mut Context<Self>,
10966 ) {
10967 self.manipulate_text(window, cx, |text| {
10968 text.chars()
10969 .map(|c| {
10970 let code_point = c as u32;
10971 if code_point >= 33 && code_point <= 126 {
10972 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10973 }
10974 c
10975 })
10976 .collect()
10977 })
10978 }
10979
10980 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10981 where
10982 Fn: FnMut(&str) -> String,
10983 {
10984 let buffer = self.buffer.read(cx).snapshot(cx);
10985
10986 let mut new_selections = Vec::new();
10987 let mut edits = Vec::new();
10988 let mut selection_adjustment = 0i32;
10989
10990 for selection in self.selections.all::<usize>(cx) {
10991 let selection_is_empty = selection.is_empty();
10992
10993 let (start, end) = if selection_is_empty {
10994 let (word_range, _) = buffer.surrounding_word(selection.start, false);
10995 (word_range.start, word_range.end)
10996 } else {
10997 (selection.start, selection.end)
10998 };
10999
11000 let text = buffer.text_for_range(start..end).collect::<String>();
11001 let old_length = text.len() as i32;
11002 let text = callback(&text);
11003
11004 new_selections.push(Selection {
11005 start: (start as i32 - selection_adjustment) as usize,
11006 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11007 goal: SelectionGoal::None,
11008 ..selection
11009 });
11010
11011 selection_adjustment += old_length - text.len() as i32;
11012
11013 edits.push((start..end, text));
11014 }
11015
11016 self.transact(window, cx, |this, window, cx| {
11017 this.buffer.update(cx, |buffer, cx| {
11018 buffer.edit(edits, None, cx);
11019 });
11020
11021 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11022 s.select(new_selections);
11023 });
11024
11025 this.request_autoscroll(Autoscroll::fit(), cx);
11026 });
11027 }
11028
11029 pub fn move_selection_on_drop(
11030 &mut self,
11031 selection: &Selection<Anchor>,
11032 target: DisplayPoint,
11033 is_cut: bool,
11034 window: &mut Window,
11035 cx: &mut Context<Self>,
11036 ) {
11037 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11038 let buffer = &display_map.buffer_snapshot;
11039 let mut edits = Vec::new();
11040 let insert_point = display_map
11041 .clip_point(target, Bias::Left)
11042 .to_point(&display_map);
11043 let text = buffer
11044 .text_for_range(selection.start..selection.end)
11045 .collect::<String>();
11046 if is_cut {
11047 edits.push(((selection.start..selection.end), String::new()));
11048 }
11049 let insert_anchor = buffer.anchor_before(insert_point);
11050 edits.push(((insert_anchor..insert_anchor), text));
11051 let last_edit_start = insert_anchor.bias_left(buffer);
11052 let last_edit_end = insert_anchor.bias_right(buffer);
11053 self.transact(window, cx, |this, window, cx| {
11054 this.buffer.update(cx, |buffer, cx| {
11055 buffer.edit(edits, None, cx);
11056 });
11057 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11058 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11059 });
11060 });
11061 }
11062
11063 pub fn clear_selection_drag_state(&mut self) {
11064 self.selection_drag_state = SelectionDragState::None;
11065 }
11066
11067 pub fn duplicate(
11068 &mut self,
11069 upwards: bool,
11070 whole_lines: bool,
11071 window: &mut Window,
11072 cx: &mut Context<Self>,
11073 ) {
11074 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11075
11076 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11077 let buffer = &display_map.buffer_snapshot;
11078 let selections = self.selections.all::<Point>(cx);
11079
11080 let mut edits = Vec::new();
11081 let mut selections_iter = selections.iter().peekable();
11082 while let Some(selection) = selections_iter.next() {
11083 let mut rows = selection.spanned_rows(false, &display_map);
11084 // duplicate line-wise
11085 if whole_lines || selection.start == selection.end {
11086 // Avoid duplicating the same lines twice.
11087 while let Some(next_selection) = selections_iter.peek() {
11088 let next_rows = next_selection.spanned_rows(false, &display_map);
11089 if next_rows.start < rows.end {
11090 rows.end = next_rows.end;
11091 selections_iter.next().unwrap();
11092 } else {
11093 break;
11094 }
11095 }
11096
11097 // Copy the text from the selected row region and splice it either at the start
11098 // or end of the region.
11099 let start = Point::new(rows.start.0, 0);
11100 let end = Point::new(
11101 rows.end.previous_row().0,
11102 buffer.line_len(rows.end.previous_row()),
11103 );
11104 let text = buffer
11105 .text_for_range(start..end)
11106 .chain(Some("\n"))
11107 .collect::<String>();
11108 let insert_location = if upwards {
11109 Point::new(rows.end.0, 0)
11110 } else {
11111 start
11112 };
11113 edits.push((insert_location..insert_location, text));
11114 } else {
11115 // duplicate character-wise
11116 let start = selection.start;
11117 let end = selection.end;
11118 let text = buffer.text_for_range(start..end).collect::<String>();
11119 edits.push((selection.end..selection.end, text));
11120 }
11121 }
11122
11123 self.transact(window, cx, |this, _, cx| {
11124 this.buffer.update(cx, |buffer, cx| {
11125 buffer.edit(edits, None, cx);
11126 });
11127
11128 this.request_autoscroll(Autoscroll::fit(), cx);
11129 });
11130 }
11131
11132 pub fn duplicate_line_up(
11133 &mut self,
11134 _: &DuplicateLineUp,
11135 window: &mut Window,
11136 cx: &mut Context<Self>,
11137 ) {
11138 self.duplicate(true, true, window, cx);
11139 }
11140
11141 pub fn duplicate_line_down(
11142 &mut self,
11143 _: &DuplicateLineDown,
11144 window: &mut Window,
11145 cx: &mut Context<Self>,
11146 ) {
11147 self.duplicate(false, true, window, cx);
11148 }
11149
11150 pub fn duplicate_selection(
11151 &mut self,
11152 _: &DuplicateSelection,
11153 window: &mut Window,
11154 cx: &mut Context<Self>,
11155 ) {
11156 self.duplicate(false, false, window, cx);
11157 }
11158
11159 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11160 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11161 if self.mode.is_single_line() {
11162 cx.propagate();
11163 return;
11164 }
11165
11166 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11167 let buffer = self.buffer.read(cx).snapshot(cx);
11168
11169 let mut edits = Vec::new();
11170 let mut unfold_ranges = Vec::new();
11171 let mut refold_creases = Vec::new();
11172
11173 let selections = self.selections.all::<Point>(cx);
11174 let mut selections = selections.iter().peekable();
11175 let mut contiguous_row_selections = Vec::new();
11176 let mut new_selections = Vec::new();
11177
11178 while let Some(selection) = selections.next() {
11179 // Find all the selections that span a contiguous row range
11180 let (start_row, end_row) = consume_contiguous_rows(
11181 &mut contiguous_row_selections,
11182 selection,
11183 &display_map,
11184 &mut selections,
11185 );
11186
11187 // Move the text spanned by the row range to be before the line preceding the row range
11188 if start_row.0 > 0 {
11189 let range_to_move = Point::new(
11190 start_row.previous_row().0,
11191 buffer.line_len(start_row.previous_row()),
11192 )
11193 ..Point::new(
11194 end_row.previous_row().0,
11195 buffer.line_len(end_row.previous_row()),
11196 );
11197 let insertion_point = display_map
11198 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11199 .0;
11200
11201 // Don't move lines across excerpts
11202 if buffer
11203 .excerpt_containing(insertion_point..range_to_move.end)
11204 .is_some()
11205 {
11206 let text = buffer
11207 .text_for_range(range_to_move.clone())
11208 .flat_map(|s| s.chars())
11209 .skip(1)
11210 .chain(['\n'])
11211 .collect::<String>();
11212
11213 edits.push((
11214 buffer.anchor_after(range_to_move.start)
11215 ..buffer.anchor_before(range_to_move.end),
11216 String::new(),
11217 ));
11218 let insertion_anchor = buffer.anchor_after(insertion_point);
11219 edits.push((insertion_anchor..insertion_anchor, text));
11220
11221 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11222
11223 // Move selections up
11224 new_selections.extend(contiguous_row_selections.drain(..).map(
11225 |mut selection| {
11226 selection.start.row -= row_delta;
11227 selection.end.row -= row_delta;
11228 selection
11229 },
11230 ));
11231
11232 // Move folds up
11233 unfold_ranges.push(range_to_move.clone());
11234 for fold in display_map.folds_in_range(
11235 buffer.anchor_before(range_to_move.start)
11236 ..buffer.anchor_after(range_to_move.end),
11237 ) {
11238 let mut start = fold.range.start.to_point(&buffer);
11239 let mut end = fold.range.end.to_point(&buffer);
11240 start.row -= row_delta;
11241 end.row -= row_delta;
11242 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11243 }
11244 }
11245 }
11246
11247 // If we didn't move line(s), preserve the existing selections
11248 new_selections.append(&mut contiguous_row_selections);
11249 }
11250
11251 self.transact(window, cx, |this, window, cx| {
11252 this.unfold_ranges(&unfold_ranges, true, true, cx);
11253 this.buffer.update(cx, |buffer, cx| {
11254 for (range, text) in edits {
11255 buffer.edit([(range, text)], None, cx);
11256 }
11257 });
11258 this.fold_creases(refold_creases, true, window, cx);
11259 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11260 s.select(new_selections);
11261 })
11262 });
11263 }
11264
11265 pub fn move_line_down(
11266 &mut self,
11267 _: &MoveLineDown,
11268 window: &mut Window,
11269 cx: &mut Context<Self>,
11270 ) {
11271 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11272 if self.mode.is_single_line() {
11273 cx.propagate();
11274 return;
11275 }
11276
11277 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11278 let buffer = self.buffer.read(cx).snapshot(cx);
11279
11280 let mut edits = Vec::new();
11281 let mut unfold_ranges = Vec::new();
11282 let mut refold_creases = Vec::new();
11283
11284 let selections = self.selections.all::<Point>(cx);
11285 let mut selections = selections.iter().peekable();
11286 let mut contiguous_row_selections = Vec::new();
11287 let mut new_selections = Vec::new();
11288
11289 while let Some(selection) = selections.next() {
11290 // Find all the selections that span a contiguous row range
11291 let (start_row, end_row) = consume_contiguous_rows(
11292 &mut contiguous_row_selections,
11293 selection,
11294 &display_map,
11295 &mut selections,
11296 );
11297
11298 // Move the text spanned by the row range to be after the last line of the row range
11299 if end_row.0 <= buffer.max_point().row {
11300 let range_to_move =
11301 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11302 let insertion_point = display_map
11303 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11304 .0;
11305
11306 // Don't move lines across excerpt boundaries
11307 if buffer
11308 .excerpt_containing(range_to_move.start..insertion_point)
11309 .is_some()
11310 {
11311 let mut text = String::from("\n");
11312 text.extend(buffer.text_for_range(range_to_move.clone()));
11313 text.pop(); // Drop trailing newline
11314 edits.push((
11315 buffer.anchor_after(range_to_move.start)
11316 ..buffer.anchor_before(range_to_move.end),
11317 String::new(),
11318 ));
11319 let insertion_anchor = buffer.anchor_after(insertion_point);
11320 edits.push((insertion_anchor..insertion_anchor, text));
11321
11322 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11323
11324 // Move selections down
11325 new_selections.extend(contiguous_row_selections.drain(..).map(
11326 |mut selection| {
11327 selection.start.row += row_delta;
11328 selection.end.row += row_delta;
11329 selection
11330 },
11331 ));
11332
11333 // Move folds down
11334 unfold_ranges.push(range_to_move.clone());
11335 for fold in display_map.folds_in_range(
11336 buffer.anchor_before(range_to_move.start)
11337 ..buffer.anchor_after(range_to_move.end),
11338 ) {
11339 let mut start = fold.range.start.to_point(&buffer);
11340 let mut end = fold.range.end.to_point(&buffer);
11341 start.row += row_delta;
11342 end.row += row_delta;
11343 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11344 }
11345 }
11346 }
11347
11348 // If we didn't move line(s), preserve the existing selections
11349 new_selections.append(&mut contiguous_row_selections);
11350 }
11351
11352 self.transact(window, cx, |this, window, cx| {
11353 this.unfold_ranges(&unfold_ranges, true, true, cx);
11354 this.buffer.update(cx, |buffer, cx| {
11355 for (range, text) in edits {
11356 buffer.edit([(range, text)], None, cx);
11357 }
11358 });
11359 this.fold_creases(refold_creases, true, window, cx);
11360 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11361 s.select(new_selections)
11362 });
11363 });
11364 }
11365
11366 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11367 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11368 let text_layout_details = &self.text_layout_details(window);
11369 self.transact(window, cx, |this, window, cx| {
11370 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11371 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11372 s.move_with(|display_map, selection| {
11373 if !selection.is_empty() {
11374 return;
11375 }
11376
11377 let mut head = selection.head();
11378 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11379 if head.column() == display_map.line_len(head.row()) {
11380 transpose_offset = display_map
11381 .buffer_snapshot
11382 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11383 }
11384
11385 if transpose_offset == 0 {
11386 return;
11387 }
11388
11389 *head.column_mut() += 1;
11390 head = display_map.clip_point(head, Bias::Right);
11391 let goal = SelectionGoal::HorizontalPosition(
11392 display_map
11393 .x_for_display_point(head, text_layout_details)
11394 .into(),
11395 );
11396 selection.collapse_to(head, goal);
11397
11398 let transpose_start = display_map
11399 .buffer_snapshot
11400 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11401 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11402 let transpose_end = display_map
11403 .buffer_snapshot
11404 .clip_offset(transpose_offset + 1, Bias::Right);
11405 if let Some(ch) =
11406 display_map.buffer_snapshot.chars_at(transpose_start).next()
11407 {
11408 edits.push((transpose_start..transpose_offset, String::new()));
11409 edits.push((transpose_end..transpose_end, ch.to_string()));
11410 }
11411 }
11412 });
11413 edits
11414 });
11415 this.buffer
11416 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11417 let selections = this.selections.all::<usize>(cx);
11418 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11419 s.select(selections);
11420 });
11421 });
11422 }
11423
11424 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11425 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11426 if self.mode.is_single_line() {
11427 cx.propagate();
11428 return;
11429 }
11430
11431 self.rewrap_impl(RewrapOptions::default(), cx)
11432 }
11433
11434 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11435 let buffer = self.buffer.read(cx).snapshot(cx);
11436 let selections = self.selections.all::<Point>(cx);
11437
11438 // Shrink and split selections to respect paragraph boundaries.
11439 let ranges = selections.into_iter().flat_map(|selection| {
11440 let language_settings = buffer.language_settings_at(selection.head(), cx);
11441 let language_scope = buffer.language_scope_at(selection.head());
11442
11443 let Some(start_row) = (selection.start.row..=selection.end.row)
11444 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11445 else {
11446 return vec![];
11447 };
11448 let Some(end_row) = (selection.start.row..=selection.end.row)
11449 .rev()
11450 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11451 else {
11452 return vec![];
11453 };
11454
11455 let mut row = start_row;
11456 let mut ranges = Vec::new();
11457 while let Some(blank_row) =
11458 (row..end_row).find(|row| buffer.is_line_blank(MultiBufferRow(*row)))
11459 {
11460 let next_paragraph_start = (blank_row + 1..=end_row)
11461 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11462 .unwrap();
11463 ranges.push((
11464 language_settings.clone(),
11465 language_scope.clone(),
11466 Point::new(row, 0)..Point::new(blank_row - 1, 0),
11467 ));
11468 row = next_paragraph_start;
11469 }
11470 ranges.push((
11471 language_settings.clone(),
11472 language_scope.clone(),
11473 Point::new(row, 0)..Point::new(end_row, 0),
11474 ));
11475
11476 ranges
11477 });
11478
11479 let mut edits = Vec::new();
11480 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11481
11482 for (language_settings, language_scope, range) in ranges {
11483 let mut start_row = range.start.row;
11484 let mut end_row = range.end.row;
11485
11486 // Skip selections that overlap with a range that has already been rewrapped.
11487 let selection_range = start_row..end_row;
11488 if rewrapped_row_ranges
11489 .iter()
11490 .any(|range| range.overlaps(&selection_range))
11491 {
11492 continue;
11493 }
11494
11495 let tab_size = language_settings.tab_size;
11496
11497 // Since not all lines in the selection may be at the same indent
11498 // level, choose the indent size that is the most common between all
11499 // of the lines.
11500 //
11501 // If there is a tie, we use the deepest indent.
11502 let (indent_size, indent_end) = {
11503 let mut indent_size_occurrences = HashMap::default();
11504 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
11505
11506 for row in start_row..=end_row {
11507 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11508 rows_by_indent_size.entry(indent).or_default().push(row);
11509 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
11510 }
11511
11512 let indent_size = indent_size_occurrences
11513 .into_iter()
11514 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
11515 .map(|(indent, _)| indent)
11516 .unwrap_or_default();
11517 let row = rows_by_indent_size[&indent_size][0];
11518 let indent_end = Point::new(row, indent_size.len);
11519
11520 (indent_size, indent_end)
11521 };
11522
11523 let mut line_prefix = indent_size.chars().collect::<String>();
11524
11525 let mut inside_comment = false;
11526 if let Some(comment_prefix) = language_scope.and_then(|language| {
11527 language
11528 .line_comment_prefixes()
11529 .iter()
11530 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11531 .cloned()
11532 }) {
11533 line_prefix.push_str(&comment_prefix);
11534 inside_comment = true;
11535 }
11536
11537 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11538 RewrapBehavior::InComments => inside_comment,
11539 RewrapBehavior::InSelections => !range.is_empty(),
11540 RewrapBehavior::Anywhere => true,
11541 };
11542
11543 let should_rewrap = options.override_language_settings
11544 || allow_rewrap_based_on_language
11545 || self.hard_wrap.is_some();
11546 if !should_rewrap {
11547 continue;
11548 }
11549
11550 if range.is_empty() {
11551 'expand_upwards: while start_row > 0 {
11552 let prev_row = start_row - 1;
11553 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11554 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11555 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11556 {
11557 start_row = prev_row;
11558 } else {
11559 break 'expand_upwards;
11560 }
11561 }
11562
11563 'expand_downwards: while end_row < buffer.max_point().row {
11564 let next_row = end_row + 1;
11565 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11566 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11567 && !buffer.is_line_blank(MultiBufferRow(next_row))
11568 {
11569 end_row = next_row;
11570 } else {
11571 break 'expand_downwards;
11572 }
11573 }
11574 }
11575
11576 let start = Point::new(start_row, 0);
11577 let start_offset = start.to_offset(&buffer);
11578 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11579 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11580 let Some(lines_without_prefixes) = selection_text
11581 .lines()
11582 .map(|line| {
11583 line.strip_prefix(&line_prefix)
11584 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
11585 .with_context(|| {
11586 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11587 })
11588 })
11589 .collect::<Result<Vec<_>, _>>()
11590 .log_err()
11591 else {
11592 continue;
11593 };
11594
11595 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11596 buffer
11597 .language_settings_at(Point::new(start_row, 0), cx)
11598 .preferred_line_length as usize
11599 });
11600 let wrapped_text = wrap_with_prefix(
11601 line_prefix,
11602 lines_without_prefixes.join("\n"),
11603 wrap_column,
11604 tab_size,
11605 options.preserve_existing_whitespace,
11606 );
11607
11608 // TODO: should always use char-based diff while still supporting cursor behavior that
11609 // matches vim.
11610 let mut diff_options = DiffOptions::default();
11611 if options.override_language_settings {
11612 diff_options.max_word_diff_len = 0;
11613 diff_options.max_word_diff_line_count = 0;
11614 } else {
11615 diff_options.max_word_diff_len = usize::MAX;
11616 diff_options.max_word_diff_line_count = usize::MAX;
11617 }
11618
11619 for (old_range, new_text) in
11620 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11621 {
11622 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11623 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11624 edits.push((edit_start..edit_end, new_text));
11625 }
11626
11627 rewrapped_row_ranges.push(start_row..=end_row);
11628 }
11629
11630 self.buffer
11631 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11632 }
11633
11634 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11635 let mut text = String::new();
11636 let buffer = self.buffer.read(cx).snapshot(cx);
11637 let mut selections = self.selections.all::<Point>(cx);
11638 let mut clipboard_selections = Vec::with_capacity(selections.len());
11639 {
11640 let max_point = buffer.max_point();
11641 let mut is_first = true;
11642 for selection in &mut selections {
11643 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11644 if is_entire_line {
11645 selection.start = Point::new(selection.start.row, 0);
11646 if !selection.is_empty() && selection.end.column == 0 {
11647 selection.end = cmp::min(max_point, selection.end);
11648 } else {
11649 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11650 }
11651 selection.goal = SelectionGoal::None;
11652 }
11653 if is_first {
11654 is_first = false;
11655 } else {
11656 text += "\n";
11657 }
11658 let mut len = 0;
11659 for chunk in buffer.text_for_range(selection.start..selection.end) {
11660 text.push_str(chunk);
11661 len += chunk.len();
11662 }
11663 clipboard_selections.push(ClipboardSelection {
11664 len,
11665 is_entire_line,
11666 first_line_indent: buffer
11667 .indent_size_for_line(MultiBufferRow(selection.start.row))
11668 .len,
11669 });
11670 }
11671 }
11672
11673 self.transact(window, cx, |this, window, cx| {
11674 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11675 s.select(selections);
11676 });
11677 this.insert("", window, cx);
11678 });
11679 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11680 }
11681
11682 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11683 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11684 let item = self.cut_common(window, cx);
11685 cx.write_to_clipboard(item);
11686 }
11687
11688 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11689 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11690 self.change_selections(None, window, cx, |s| {
11691 s.move_with(|snapshot, sel| {
11692 if sel.is_empty() {
11693 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11694 }
11695 });
11696 });
11697 let item = self.cut_common(window, cx);
11698 cx.set_global(KillRing(item))
11699 }
11700
11701 pub fn kill_ring_yank(
11702 &mut self,
11703 _: &KillRingYank,
11704 window: &mut Window,
11705 cx: &mut Context<Self>,
11706 ) {
11707 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11708 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11709 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11710 (kill_ring.text().to_string(), kill_ring.metadata_json())
11711 } else {
11712 return;
11713 }
11714 } else {
11715 return;
11716 };
11717 self.do_paste(&text, metadata, false, window, cx);
11718 }
11719
11720 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11721 self.do_copy(true, cx);
11722 }
11723
11724 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11725 self.do_copy(false, cx);
11726 }
11727
11728 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11729 let selections = self.selections.all::<Point>(cx);
11730 let buffer = self.buffer.read(cx).read(cx);
11731 let mut text = String::new();
11732
11733 let mut clipboard_selections = Vec::with_capacity(selections.len());
11734 {
11735 let max_point = buffer.max_point();
11736 let mut is_first = true;
11737 for selection in &selections {
11738 let mut start = selection.start;
11739 let mut end = selection.end;
11740 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11741 if is_entire_line {
11742 start = Point::new(start.row, 0);
11743 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11744 }
11745
11746 let mut trimmed_selections = Vec::new();
11747 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11748 let row = MultiBufferRow(start.row);
11749 let first_indent = buffer.indent_size_for_line(row);
11750 if first_indent.len == 0 || start.column > first_indent.len {
11751 trimmed_selections.push(start..end);
11752 } else {
11753 trimmed_selections.push(
11754 Point::new(row.0, first_indent.len)
11755 ..Point::new(row.0, buffer.line_len(row)),
11756 );
11757 for row in start.row + 1..=end.row {
11758 let mut line_len = buffer.line_len(MultiBufferRow(row));
11759 if row == end.row {
11760 line_len = end.column;
11761 }
11762 if line_len == 0 {
11763 trimmed_selections
11764 .push(Point::new(row, 0)..Point::new(row, line_len));
11765 continue;
11766 }
11767 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11768 if row_indent_size.len >= first_indent.len {
11769 trimmed_selections.push(
11770 Point::new(row, first_indent.len)..Point::new(row, line_len),
11771 );
11772 } else {
11773 trimmed_selections.clear();
11774 trimmed_selections.push(start..end);
11775 break;
11776 }
11777 }
11778 }
11779 } else {
11780 trimmed_selections.push(start..end);
11781 }
11782
11783 for trimmed_range in trimmed_selections {
11784 if is_first {
11785 is_first = false;
11786 } else {
11787 text += "\n";
11788 }
11789 let mut len = 0;
11790 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11791 text.push_str(chunk);
11792 len += chunk.len();
11793 }
11794 clipboard_selections.push(ClipboardSelection {
11795 len,
11796 is_entire_line,
11797 first_line_indent: buffer
11798 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11799 .len,
11800 });
11801 }
11802 }
11803 }
11804
11805 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11806 text,
11807 clipboard_selections,
11808 ));
11809 }
11810
11811 pub fn do_paste(
11812 &mut self,
11813 text: &String,
11814 clipboard_selections: Option<Vec<ClipboardSelection>>,
11815 handle_entire_lines: bool,
11816 window: &mut Window,
11817 cx: &mut Context<Self>,
11818 ) {
11819 if self.read_only(cx) {
11820 return;
11821 }
11822
11823 let clipboard_text = Cow::Borrowed(text);
11824
11825 self.transact(window, cx, |this, window, cx| {
11826 if let Some(mut clipboard_selections) = clipboard_selections {
11827 let old_selections = this.selections.all::<usize>(cx);
11828 let all_selections_were_entire_line =
11829 clipboard_selections.iter().all(|s| s.is_entire_line);
11830 let first_selection_indent_column =
11831 clipboard_selections.first().map(|s| s.first_line_indent);
11832 if clipboard_selections.len() != old_selections.len() {
11833 clipboard_selections.drain(..);
11834 }
11835 let cursor_offset = this.selections.last::<usize>(cx).head();
11836 let mut auto_indent_on_paste = true;
11837
11838 this.buffer.update(cx, |buffer, cx| {
11839 let snapshot = buffer.read(cx);
11840 auto_indent_on_paste = snapshot
11841 .language_settings_at(cursor_offset, cx)
11842 .auto_indent_on_paste;
11843
11844 let mut start_offset = 0;
11845 let mut edits = Vec::new();
11846 let mut original_indent_columns = Vec::new();
11847 for (ix, selection) in old_selections.iter().enumerate() {
11848 let to_insert;
11849 let entire_line;
11850 let original_indent_column;
11851 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11852 let end_offset = start_offset + clipboard_selection.len;
11853 to_insert = &clipboard_text[start_offset..end_offset];
11854 entire_line = clipboard_selection.is_entire_line;
11855 start_offset = end_offset + 1;
11856 original_indent_column = Some(clipboard_selection.first_line_indent);
11857 } else {
11858 to_insert = clipboard_text.as_str();
11859 entire_line = all_selections_were_entire_line;
11860 original_indent_column = first_selection_indent_column
11861 }
11862
11863 // If the corresponding selection was empty when this slice of the
11864 // clipboard text was written, then the entire line containing the
11865 // selection was copied. If this selection is also currently empty,
11866 // then paste the line before the current line of the buffer.
11867 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11868 let column = selection.start.to_point(&snapshot).column as usize;
11869 let line_start = selection.start - column;
11870 line_start..line_start
11871 } else {
11872 selection.range()
11873 };
11874
11875 edits.push((range, to_insert));
11876 original_indent_columns.push(original_indent_column);
11877 }
11878 drop(snapshot);
11879
11880 buffer.edit(
11881 edits,
11882 if auto_indent_on_paste {
11883 Some(AutoindentMode::Block {
11884 original_indent_columns,
11885 })
11886 } else {
11887 None
11888 },
11889 cx,
11890 );
11891 });
11892
11893 let selections = this.selections.all::<usize>(cx);
11894 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11895 s.select(selections)
11896 });
11897 } else {
11898 this.insert(&clipboard_text, window, cx);
11899 }
11900 });
11901 }
11902
11903 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11904 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11905 if let Some(item) = cx.read_from_clipboard() {
11906 let entries = item.entries();
11907
11908 match entries.first() {
11909 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11910 // of all the pasted entries.
11911 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11912 .do_paste(
11913 clipboard_string.text(),
11914 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11915 true,
11916 window,
11917 cx,
11918 ),
11919 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11920 }
11921 }
11922 }
11923
11924 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11925 if self.read_only(cx) {
11926 return;
11927 }
11928
11929 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11930
11931 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11932 if let Some((selections, _)) =
11933 self.selection_history.transaction(transaction_id).cloned()
11934 {
11935 self.change_selections(None, window, cx, |s| {
11936 s.select_anchors(selections.to_vec());
11937 });
11938 } else {
11939 log::error!(
11940 "No entry in selection_history found for undo. \
11941 This may correspond to a bug where undo does not update the selection. \
11942 If this is occurring, please add details to \
11943 https://github.com/zed-industries/zed/issues/22692"
11944 );
11945 }
11946 self.request_autoscroll(Autoscroll::fit(), cx);
11947 self.unmark_text(window, cx);
11948 self.refresh_inline_completion(true, false, window, cx);
11949 cx.emit(EditorEvent::Edited { transaction_id });
11950 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11951 }
11952 }
11953
11954 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11955 if self.read_only(cx) {
11956 return;
11957 }
11958
11959 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11960
11961 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11962 if let Some((_, Some(selections))) =
11963 self.selection_history.transaction(transaction_id).cloned()
11964 {
11965 self.change_selections(None, window, cx, |s| {
11966 s.select_anchors(selections.to_vec());
11967 });
11968 } else {
11969 log::error!(
11970 "No entry in selection_history found for redo. \
11971 This may correspond to a bug where undo does not update the selection. \
11972 If this is occurring, please add details to \
11973 https://github.com/zed-industries/zed/issues/22692"
11974 );
11975 }
11976 self.request_autoscroll(Autoscroll::fit(), cx);
11977 self.unmark_text(window, cx);
11978 self.refresh_inline_completion(true, false, window, cx);
11979 cx.emit(EditorEvent::Edited { transaction_id });
11980 }
11981 }
11982
11983 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11984 self.buffer
11985 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11986 }
11987
11988 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11989 self.buffer
11990 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11991 }
11992
11993 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11994 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11995 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11996 s.move_with(|map, selection| {
11997 let cursor = if selection.is_empty() {
11998 movement::left(map, selection.start)
11999 } else {
12000 selection.start
12001 };
12002 selection.collapse_to(cursor, SelectionGoal::None);
12003 });
12004 })
12005 }
12006
12007 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12008 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12009 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12010 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12011 })
12012 }
12013
12014 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12015 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12016 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12017 s.move_with(|map, selection| {
12018 let cursor = if selection.is_empty() {
12019 movement::right(map, selection.end)
12020 } else {
12021 selection.end
12022 };
12023 selection.collapse_to(cursor, SelectionGoal::None)
12024 });
12025 })
12026 }
12027
12028 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12029 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12030 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12031 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12032 })
12033 }
12034
12035 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12036 if self.take_rename(true, window, cx).is_some() {
12037 return;
12038 }
12039
12040 if self.mode.is_single_line() {
12041 cx.propagate();
12042 return;
12043 }
12044
12045 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12046
12047 let text_layout_details = &self.text_layout_details(window);
12048 let selection_count = self.selections.count();
12049 let first_selection = self.selections.first_anchor();
12050
12051 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12052 s.move_with(|map, selection| {
12053 if !selection.is_empty() {
12054 selection.goal = SelectionGoal::None;
12055 }
12056 let (cursor, goal) = movement::up(
12057 map,
12058 selection.start,
12059 selection.goal,
12060 false,
12061 text_layout_details,
12062 );
12063 selection.collapse_to(cursor, goal);
12064 });
12065 });
12066
12067 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12068 {
12069 cx.propagate();
12070 }
12071 }
12072
12073 pub fn move_up_by_lines(
12074 &mut self,
12075 action: &MoveUpByLines,
12076 window: &mut Window,
12077 cx: &mut Context<Self>,
12078 ) {
12079 if self.take_rename(true, window, cx).is_some() {
12080 return;
12081 }
12082
12083 if self.mode.is_single_line() {
12084 cx.propagate();
12085 return;
12086 }
12087
12088 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12089
12090 let text_layout_details = &self.text_layout_details(window);
12091
12092 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12093 s.move_with(|map, selection| {
12094 if !selection.is_empty() {
12095 selection.goal = SelectionGoal::None;
12096 }
12097 let (cursor, goal) = movement::up_by_rows(
12098 map,
12099 selection.start,
12100 action.lines,
12101 selection.goal,
12102 false,
12103 text_layout_details,
12104 );
12105 selection.collapse_to(cursor, goal);
12106 });
12107 })
12108 }
12109
12110 pub fn move_down_by_lines(
12111 &mut self,
12112 action: &MoveDownByLines,
12113 window: &mut Window,
12114 cx: &mut Context<Self>,
12115 ) {
12116 if self.take_rename(true, window, cx).is_some() {
12117 return;
12118 }
12119
12120 if self.mode.is_single_line() {
12121 cx.propagate();
12122 return;
12123 }
12124
12125 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12126
12127 let text_layout_details = &self.text_layout_details(window);
12128
12129 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12130 s.move_with(|map, selection| {
12131 if !selection.is_empty() {
12132 selection.goal = SelectionGoal::None;
12133 }
12134 let (cursor, goal) = movement::down_by_rows(
12135 map,
12136 selection.start,
12137 action.lines,
12138 selection.goal,
12139 false,
12140 text_layout_details,
12141 );
12142 selection.collapse_to(cursor, goal);
12143 });
12144 })
12145 }
12146
12147 pub fn select_down_by_lines(
12148 &mut self,
12149 action: &SelectDownByLines,
12150 window: &mut Window,
12151 cx: &mut Context<Self>,
12152 ) {
12153 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12154 let text_layout_details = &self.text_layout_details(window);
12155 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12156 s.move_heads_with(|map, head, goal| {
12157 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12158 })
12159 })
12160 }
12161
12162 pub fn select_up_by_lines(
12163 &mut self,
12164 action: &SelectUpByLines,
12165 window: &mut Window,
12166 cx: &mut Context<Self>,
12167 ) {
12168 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12169 let text_layout_details = &self.text_layout_details(window);
12170 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12171 s.move_heads_with(|map, head, goal| {
12172 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12173 })
12174 })
12175 }
12176
12177 pub fn select_page_up(
12178 &mut self,
12179 _: &SelectPageUp,
12180 window: &mut Window,
12181 cx: &mut Context<Self>,
12182 ) {
12183 let Some(row_count) = self.visible_row_count() else {
12184 return;
12185 };
12186
12187 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12188
12189 let text_layout_details = &self.text_layout_details(window);
12190
12191 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12192 s.move_heads_with(|map, head, goal| {
12193 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12194 })
12195 })
12196 }
12197
12198 pub fn move_page_up(
12199 &mut self,
12200 action: &MovePageUp,
12201 window: &mut Window,
12202 cx: &mut Context<Self>,
12203 ) {
12204 if self.take_rename(true, window, cx).is_some() {
12205 return;
12206 }
12207
12208 if self
12209 .context_menu
12210 .borrow_mut()
12211 .as_mut()
12212 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12213 .unwrap_or(false)
12214 {
12215 return;
12216 }
12217
12218 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12219 cx.propagate();
12220 return;
12221 }
12222
12223 let Some(row_count) = self.visible_row_count() else {
12224 return;
12225 };
12226
12227 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12228
12229 let autoscroll = if action.center_cursor {
12230 Autoscroll::center()
12231 } else {
12232 Autoscroll::fit()
12233 };
12234
12235 let text_layout_details = &self.text_layout_details(window);
12236
12237 self.change_selections(Some(autoscroll), window, cx, |s| {
12238 s.move_with(|map, selection| {
12239 if !selection.is_empty() {
12240 selection.goal = SelectionGoal::None;
12241 }
12242 let (cursor, goal) = movement::up_by_rows(
12243 map,
12244 selection.end,
12245 row_count,
12246 selection.goal,
12247 false,
12248 text_layout_details,
12249 );
12250 selection.collapse_to(cursor, goal);
12251 });
12252 });
12253 }
12254
12255 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12256 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12257 let text_layout_details = &self.text_layout_details(window);
12258 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12259 s.move_heads_with(|map, head, goal| {
12260 movement::up(map, head, goal, false, text_layout_details)
12261 })
12262 })
12263 }
12264
12265 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12266 self.take_rename(true, window, cx);
12267
12268 if self.mode.is_single_line() {
12269 cx.propagate();
12270 return;
12271 }
12272
12273 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12274
12275 let text_layout_details = &self.text_layout_details(window);
12276 let selection_count = self.selections.count();
12277 let first_selection = self.selections.first_anchor();
12278
12279 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12280 s.move_with(|map, selection| {
12281 if !selection.is_empty() {
12282 selection.goal = SelectionGoal::None;
12283 }
12284 let (cursor, goal) = movement::down(
12285 map,
12286 selection.end,
12287 selection.goal,
12288 false,
12289 text_layout_details,
12290 );
12291 selection.collapse_to(cursor, goal);
12292 });
12293 });
12294
12295 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12296 {
12297 cx.propagate();
12298 }
12299 }
12300
12301 pub fn select_page_down(
12302 &mut self,
12303 _: &SelectPageDown,
12304 window: &mut Window,
12305 cx: &mut Context<Self>,
12306 ) {
12307 let Some(row_count) = self.visible_row_count() else {
12308 return;
12309 };
12310
12311 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12312
12313 let text_layout_details = &self.text_layout_details(window);
12314
12315 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12316 s.move_heads_with(|map, head, goal| {
12317 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12318 })
12319 })
12320 }
12321
12322 pub fn move_page_down(
12323 &mut self,
12324 action: &MovePageDown,
12325 window: &mut Window,
12326 cx: &mut Context<Self>,
12327 ) {
12328 if self.take_rename(true, window, cx).is_some() {
12329 return;
12330 }
12331
12332 if self
12333 .context_menu
12334 .borrow_mut()
12335 .as_mut()
12336 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12337 .unwrap_or(false)
12338 {
12339 return;
12340 }
12341
12342 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12343 cx.propagate();
12344 return;
12345 }
12346
12347 let Some(row_count) = self.visible_row_count() else {
12348 return;
12349 };
12350
12351 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12352
12353 let autoscroll = if action.center_cursor {
12354 Autoscroll::center()
12355 } else {
12356 Autoscroll::fit()
12357 };
12358
12359 let text_layout_details = &self.text_layout_details(window);
12360 self.change_selections(Some(autoscroll), window, cx, |s| {
12361 s.move_with(|map, selection| {
12362 if !selection.is_empty() {
12363 selection.goal = SelectionGoal::None;
12364 }
12365 let (cursor, goal) = movement::down_by_rows(
12366 map,
12367 selection.end,
12368 row_count,
12369 selection.goal,
12370 false,
12371 text_layout_details,
12372 );
12373 selection.collapse_to(cursor, goal);
12374 });
12375 });
12376 }
12377
12378 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12379 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12380 let text_layout_details = &self.text_layout_details(window);
12381 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12382 s.move_heads_with(|map, head, goal| {
12383 movement::down(map, head, goal, false, text_layout_details)
12384 })
12385 });
12386 }
12387
12388 pub fn context_menu_first(
12389 &mut self,
12390 _: &ContextMenuFirst,
12391 window: &mut Window,
12392 cx: &mut Context<Self>,
12393 ) {
12394 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12395 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12396 }
12397 }
12398
12399 pub fn context_menu_prev(
12400 &mut self,
12401 _: &ContextMenuPrevious,
12402 window: &mut Window,
12403 cx: &mut Context<Self>,
12404 ) {
12405 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12406 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12407 }
12408 }
12409
12410 pub fn context_menu_next(
12411 &mut self,
12412 _: &ContextMenuNext,
12413 window: &mut Window,
12414 cx: &mut Context<Self>,
12415 ) {
12416 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12417 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12418 }
12419 }
12420
12421 pub fn context_menu_last(
12422 &mut self,
12423 _: &ContextMenuLast,
12424 window: &mut Window,
12425 cx: &mut Context<Self>,
12426 ) {
12427 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12428 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12429 }
12430 }
12431
12432 pub fn move_to_previous_word_start(
12433 &mut self,
12434 _: &MoveToPreviousWordStart,
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_word_start(map, head),
12443 SelectionGoal::None,
12444 )
12445 });
12446 })
12447 }
12448
12449 pub fn move_to_previous_subword_start(
12450 &mut self,
12451 _: &MoveToPreviousSubwordStart,
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_cursors_with(|map, head, _| {
12458 (
12459 movement::previous_subword_start(map, head),
12460 SelectionGoal::None,
12461 )
12462 });
12463 })
12464 }
12465
12466 pub fn select_to_previous_word_start(
12467 &mut self,
12468 _: &SelectToPreviousWordStart,
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_word_start(map, head),
12477 SelectionGoal::None,
12478 )
12479 });
12480 })
12481 }
12482
12483 pub fn select_to_previous_subword_start(
12484 &mut self,
12485 _: &SelectToPreviousSubwordStart,
12486 window: &mut Window,
12487 cx: &mut Context<Self>,
12488 ) {
12489 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12490 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12491 s.move_heads_with(|map, head, _| {
12492 (
12493 movement::previous_subword_start(map, head),
12494 SelectionGoal::None,
12495 )
12496 });
12497 })
12498 }
12499
12500 pub fn delete_to_previous_word_start(
12501 &mut self,
12502 action: &DeleteToPreviousWordStart,
12503 window: &mut Window,
12504 cx: &mut Context<Self>,
12505 ) {
12506 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12507 self.transact(window, cx, |this, window, cx| {
12508 this.select_autoclose_pair(window, cx);
12509 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12510 s.move_with(|map, selection| {
12511 if selection.is_empty() {
12512 let cursor = if action.ignore_newlines {
12513 movement::previous_word_start(map, selection.head())
12514 } else {
12515 movement::previous_word_start_or_newline(map, selection.head())
12516 };
12517 selection.set_head(cursor, SelectionGoal::None);
12518 }
12519 });
12520 });
12521 this.insert("", window, cx);
12522 });
12523 }
12524
12525 pub fn delete_to_previous_subword_start(
12526 &mut self,
12527 _: &DeleteToPreviousSubwordStart,
12528 window: &mut Window,
12529 cx: &mut Context<Self>,
12530 ) {
12531 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12532 self.transact(window, cx, |this, window, cx| {
12533 this.select_autoclose_pair(window, cx);
12534 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12535 s.move_with(|map, selection| {
12536 if selection.is_empty() {
12537 let cursor = movement::previous_subword_start(map, selection.head());
12538 selection.set_head(cursor, SelectionGoal::None);
12539 }
12540 });
12541 });
12542 this.insert("", window, cx);
12543 });
12544 }
12545
12546 pub fn move_to_next_word_end(
12547 &mut self,
12548 _: &MoveToNextWordEnd,
12549 window: &mut Window,
12550 cx: &mut Context<Self>,
12551 ) {
12552 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12553 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12554 s.move_cursors_with(|map, head, _| {
12555 (movement::next_word_end(map, head), SelectionGoal::None)
12556 });
12557 })
12558 }
12559
12560 pub fn move_to_next_subword_end(
12561 &mut self,
12562 _: &MoveToNextSubwordEnd,
12563 window: &mut Window,
12564 cx: &mut Context<Self>,
12565 ) {
12566 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12567 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12568 s.move_cursors_with(|map, head, _| {
12569 (movement::next_subword_end(map, head), SelectionGoal::None)
12570 });
12571 })
12572 }
12573
12574 pub fn select_to_next_word_end(
12575 &mut self,
12576 _: &SelectToNextWordEnd,
12577 window: &mut Window,
12578 cx: &mut Context<Self>,
12579 ) {
12580 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12581 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12582 s.move_heads_with(|map, head, _| {
12583 (movement::next_word_end(map, head), SelectionGoal::None)
12584 });
12585 })
12586 }
12587
12588 pub fn select_to_next_subword_end(
12589 &mut self,
12590 _: &SelectToNextSubwordEnd,
12591 window: &mut Window,
12592 cx: &mut Context<Self>,
12593 ) {
12594 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12595 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12596 s.move_heads_with(|map, head, _| {
12597 (movement::next_subword_end(map, head), SelectionGoal::None)
12598 });
12599 })
12600 }
12601
12602 pub fn delete_to_next_word_end(
12603 &mut self,
12604 action: &DeleteToNextWordEnd,
12605 window: &mut Window,
12606 cx: &mut Context<Self>,
12607 ) {
12608 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12609 self.transact(window, cx, |this, window, cx| {
12610 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12611 s.move_with(|map, selection| {
12612 if selection.is_empty() {
12613 let cursor = if action.ignore_newlines {
12614 movement::next_word_end(map, selection.head())
12615 } else {
12616 movement::next_word_end_or_newline(map, selection.head())
12617 };
12618 selection.set_head(cursor, SelectionGoal::None);
12619 }
12620 });
12621 });
12622 this.insert("", window, cx);
12623 });
12624 }
12625
12626 pub fn delete_to_next_subword_end(
12627 &mut self,
12628 _: &DeleteToNextSubwordEnd,
12629 window: &mut Window,
12630 cx: &mut Context<Self>,
12631 ) {
12632 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12633 self.transact(window, cx, |this, window, cx| {
12634 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12635 s.move_with(|map, selection| {
12636 if selection.is_empty() {
12637 let cursor = movement::next_subword_end(map, selection.head());
12638 selection.set_head(cursor, SelectionGoal::None);
12639 }
12640 });
12641 });
12642 this.insert("", window, cx);
12643 });
12644 }
12645
12646 pub fn move_to_beginning_of_line(
12647 &mut self,
12648 action: &MoveToBeginningOfLine,
12649 window: &mut Window,
12650 cx: &mut Context<Self>,
12651 ) {
12652 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12653 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12654 s.move_cursors_with(|map, head, _| {
12655 (
12656 movement::indented_line_beginning(
12657 map,
12658 head,
12659 action.stop_at_soft_wraps,
12660 action.stop_at_indent,
12661 ),
12662 SelectionGoal::None,
12663 )
12664 });
12665 })
12666 }
12667
12668 pub fn select_to_beginning_of_line(
12669 &mut self,
12670 action: &SelectToBeginningOfLine,
12671 window: &mut Window,
12672 cx: &mut Context<Self>,
12673 ) {
12674 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12675 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12676 s.move_heads_with(|map, head, _| {
12677 (
12678 movement::indented_line_beginning(
12679 map,
12680 head,
12681 action.stop_at_soft_wraps,
12682 action.stop_at_indent,
12683 ),
12684 SelectionGoal::None,
12685 )
12686 });
12687 });
12688 }
12689
12690 pub fn delete_to_beginning_of_line(
12691 &mut self,
12692 action: &DeleteToBeginningOfLine,
12693 window: &mut Window,
12694 cx: &mut Context<Self>,
12695 ) {
12696 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12697 self.transact(window, cx, |this, window, cx| {
12698 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12699 s.move_with(|_, selection| {
12700 selection.reversed = true;
12701 });
12702 });
12703
12704 this.select_to_beginning_of_line(
12705 &SelectToBeginningOfLine {
12706 stop_at_soft_wraps: false,
12707 stop_at_indent: action.stop_at_indent,
12708 },
12709 window,
12710 cx,
12711 );
12712 this.backspace(&Backspace, window, cx);
12713 });
12714 }
12715
12716 pub fn move_to_end_of_line(
12717 &mut self,
12718 action: &MoveToEndOfLine,
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_cursors_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 select_to_end_of_line(
12734 &mut self,
12735 action: &SelectToEndOfLine,
12736 window: &mut Window,
12737 cx: &mut Context<Self>,
12738 ) {
12739 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12740 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12741 s.move_heads_with(|map, head, _| {
12742 (
12743 movement::line_end(map, head, action.stop_at_soft_wraps),
12744 SelectionGoal::None,
12745 )
12746 });
12747 })
12748 }
12749
12750 pub fn delete_to_end_of_line(
12751 &mut self,
12752 _: &DeleteToEndOfLine,
12753 window: &mut Window,
12754 cx: &mut Context<Self>,
12755 ) {
12756 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12757 self.transact(window, cx, |this, window, cx| {
12758 this.select_to_end_of_line(
12759 &SelectToEndOfLine {
12760 stop_at_soft_wraps: false,
12761 },
12762 window,
12763 cx,
12764 );
12765 this.delete(&Delete, window, cx);
12766 });
12767 }
12768
12769 pub fn cut_to_end_of_line(
12770 &mut self,
12771 _: &CutToEndOfLine,
12772 window: &mut Window,
12773 cx: &mut Context<Self>,
12774 ) {
12775 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12776 self.transact(window, cx, |this, window, cx| {
12777 this.select_to_end_of_line(
12778 &SelectToEndOfLine {
12779 stop_at_soft_wraps: false,
12780 },
12781 window,
12782 cx,
12783 );
12784 this.cut(&Cut, window, cx);
12785 });
12786 }
12787
12788 pub fn move_to_start_of_paragraph(
12789 &mut self,
12790 _: &MoveToStartOfParagraph,
12791 window: &mut Window,
12792 cx: &mut Context<Self>,
12793 ) {
12794 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12795 cx.propagate();
12796 return;
12797 }
12798 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12799 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12800 s.move_with(|map, selection| {
12801 selection.collapse_to(
12802 movement::start_of_paragraph(map, selection.head(), 1),
12803 SelectionGoal::None,
12804 )
12805 });
12806 })
12807 }
12808
12809 pub fn move_to_end_of_paragraph(
12810 &mut self,
12811 _: &MoveToEndOfParagraph,
12812 window: &mut Window,
12813 cx: &mut Context<Self>,
12814 ) {
12815 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12816 cx.propagate();
12817 return;
12818 }
12819 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12820 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12821 s.move_with(|map, selection| {
12822 selection.collapse_to(
12823 movement::end_of_paragraph(map, selection.head(), 1),
12824 SelectionGoal::None,
12825 )
12826 });
12827 })
12828 }
12829
12830 pub fn select_to_start_of_paragraph(
12831 &mut self,
12832 _: &SelectToStartOfParagraph,
12833 window: &mut Window,
12834 cx: &mut Context<Self>,
12835 ) {
12836 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12837 cx.propagate();
12838 return;
12839 }
12840 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12841 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12842 s.move_heads_with(|map, head, _| {
12843 (
12844 movement::start_of_paragraph(map, head, 1),
12845 SelectionGoal::None,
12846 )
12847 });
12848 })
12849 }
12850
12851 pub fn select_to_end_of_paragraph(
12852 &mut self,
12853 _: &SelectToEndOfParagraph,
12854 window: &mut Window,
12855 cx: &mut Context<Self>,
12856 ) {
12857 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12858 cx.propagate();
12859 return;
12860 }
12861 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12862 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12863 s.move_heads_with(|map, head, _| {
12864 (
12865 movement::end_of_paragraph(map, head, 1),
12866 SelectionGoal::None,
12867 )
12868 });
12869 })
12870 }
12871
12872 pub fn move_to_start_of_excerpt(
12873 &mut self,
12874 _: &MoveToStartOfExcerpt,
12875 window: &mut Window,
12876 cx: &mut Context<Self>,
12877 ) {
12878 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12879 cx.propagate();
12880 return;
12881 }
12882 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12883 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12884 s.move_with(|map, selection| {
12885 selection.collapse_to(
12886 movement::start_of_excerpt(
12887 map,
12888 selection.head(),
12889 workspace::searchable::Direction::Prev,
12890 ),
12891 SelectionGoal::None,
12892 )
12893 });
12894 })
12895 }
12896
12897 pub fn move_to_start_of_next_excerpt(
12898 &mut self,
12899 _: &MoveToStartOfNextExcerpt,
12900 window: &mut Window,
12901 cx: &mut Context<Self>,
12902 ) {
12903 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12904 cx.propagate();
12905 return;
12906 }
12907
12908 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12909 s.move_with(|map, selection| {
12910 selection.collapse_to(
12911 movement::start_of_excerpt(
12912 map,
12913 selection.head(),
12914 workspace::searchable::Direction::Next,
12915 ),
12916 SelectionGoal::None,
12917 )
12918 });
12919 })
12920 }
12921
12922 pub fn move_to_end_of_excerpt(
12923 &mut self,
12924 _: &MoveToEndOfExcerpt,
12925 window: &mut Window,
12926 cx: &mut Context<Self>,
12927 ) {
12928 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12929 cx.propagate();
12930 return;
12931 }
12932 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12933 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12934 s.move_with(|map, selection| {
12935 selection.collapse_to(
12936 movement::end_of_excerpt(
12937 map,
12938 selection.head(),
12939 workspace::searchable::Direction::Next,
12940 ),
12941 SelectionGoal::None,
12942 )
12943 });
12944 })
12945 }
12946
12947 pub fn move_to_end_of_previous_excerpt(
12948 &mut self,
12949 _: &MoveToEndOfPreviousExcerpt,
12950 window: &mut Window,
12951 cx: &mut Context<Self>,
12952 ) {
12953 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12954 cx.propagate();
12955 return;
12956 }
12957 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12958 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12959 s.move_with(|map, selection| {
12960 selection.collapse_to(
12961 movement::end_of_excerpt(
12962 map,
12963 selection.head(),
12964 workspace::searchable::Direction::Prev,
12965 ),
12966 SelectionGoal::None,
12967 )
12968 });
12969 })
12970 }
12971
12972 pub fn select_to_start_of_excerpt(
12973 &mut self,
12974 _: &SelectToStartOfExcerpt,
12975 window: &mut Window,
12976 cx: &mut Context<Self>,
12977 ) {
12978 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12979 cx.propagate();
12980 return;
12981 }
12982 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12983 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12984 s.move_heads_with(|map, head, _| {
12985 (
12986 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12987 SelectionGoal::None,
12988 )
12989 });
12990 })
12991 }
12992
12993 pub fn select_to_start_of_next_excerpt(
12994 &mut self,
12995 _: &SelectToStartOfNextExcerpt,
12996 window: &mut Window,
12997 cx: &mut Context<Self>,
12998 ) {
12999 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13000 cx.propagate();
13001 return;
13002 }
13003 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13004 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13005 s.move_heads_with(|map, head, _| {
13006 (
13007 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13008 SelectionGoal::None,
13009 )
13010 });
13011 })
13012 }
13013
13014 pub fn select_to_end_of_excerpt(
13015 &mut self,
13016 _: &SelectToEndOfExcerpt,
13017 window: &mut Window,
13018 cx: &mut Context<Self>,
13019 ) {
13020 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13021 cx.propagate();
13022 return;
13023 }
13024 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13025 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13026 s.move_heads_with(|map, head, _| {
13027 (
13028 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13029 SelectionGoal::None,
13030 )
13031 });
13032 })
13033 }
13034
13035 pub fn select_to_end_of_previous_excerpt(
13036 &mut self,
13037 _: &SelectToEndOfPreviousExcerpt,
13038 window: &mut Window,
13039 cx: &mut Context<Self>,
13040 ) {
13041 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13042 cx.propagate();
13043 return;
13044 }
13045 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13046 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13047 s.move_heads_with(|map, head, _| {
13048 (
13049 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13050 SelectionGoal::None,
13051 )
13052 });
13053 })
13054 }
13055
13056 pub fn move_to_beginning(
13057 &mut self,
13058 _: &MoveToBeginning,
13059 window: &mut Window,
13060 cx: &mut Context<Self>,
13061 ) {
13062 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13063 cx.propagate();
13064 return;
13065 }
13066 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13067 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13068 s.select_ranges(vec![0..0]);
13069 });
13070 }
13071
13072 pub fn select_to_beginning(
13073 &mut self,
13074 _: &SelectToBeginning,
13075 window: &mut Window,
13076 cx: &mut Context<Self>,
13077 ) {
13078 let mut selection = self.selections.last::<Point>(cx);
13079 selection.set_head(Point::zero(), SelectionGoal::None);
13080 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13081 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13082 s.select(vec![selection]);
13083 });
13084 }
13085
13086 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13087 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13088 cx.propagate();
13089 return;
13090 }
13091 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13092 let cursor = self.buffer.read(cx).read(cx).len();
13093 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13094 s.select_ranges(vec![cursor..cursor])
13095 });
13096 }
13097
13098 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13099 self.nav_history = nav_history;
13100 }
13101
13102 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13103 self.nav_history.as_ref()
13104 }
13105
13106 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13107 self.push_to_nav_history(
13108 self.selections.newest_anchor().head(),
13109 None,
13110 false,
13111 true,
13112 cx,
13113 );
13114 }
13115
13116 fn push_to_nav_history(
13117 &mut self,
13118 cursor_anchor: Anchor,
13119 new_position: Option<Point>,
13120 is_deactivate: bool,
13121 always: bool,
13122 cx: &mut Context<Self>,
13123 ) {
13124 if let Some(nav_history) = self.nav_history.as_mut() {
13125 let buffer = self.buffer.read(cx).read(cx);
13126 let cursor_position = cursor_anchor.to_point(&buffer);
13127 let scroll_state = self.scroll_manager.anchor();
13128 let scroll_top_row = scroll_state.top_row(&buffer);
13129 drop(buffer);
13130
13131 if let Some(new_position) = new_position {
13132 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13133 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13134 return;
13135 }
13136 }
13137
13138 nav_history.push(
13139 Some(NavigationData {
13140 cursor_anchor,
13141 cursor_position,
13142 scroll_anchor: scroll_state,
13143 scroll_top_row,
13144 }),
13145 cx,
13146 );
13147 cx.emit(EditorEvent::PushedToNavHistory {
13148 anchor: cursor_anchor,
13149 is_deactivate,
13150 })
13151 }
13152 }
13153
13154 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13155 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13156 let buffer = self.buffer.read(cx).snapshot(cx);
13157 let mut selection = self.selections.first::<usize>(cx);
13158 selection.set_head(buffer.len(), SelectionGoal::None);
13159 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13160 s.select(vec![selection]);
13161 });
13162 }
13163
13164 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13165 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13166 let end = self.buffer.read(cx).read(cx).len();
13167 self.change_selections(None, window, cx, |s| {
13168 s.select_ranges(vec![0..end]);
13169 });
13170 }
13171
13172 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13173 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13174 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13175 let mut selections = self.selections.all::<Point>(cx);
13176 let max_point = display_map.buffer_snapshot.max_point();
13177 for selection in &mut selections {
13178 let rows = selection.spanned_rows(true, &display_map);
13179 selection.start = Point::new(rows.start.0, 0);
13180 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13181 selection.reversed = false;
13182 }
13183 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13184 s.select(selections);
13185 });
13186 }
13187
13188 pub fn split_selection_into_lines(
13189 &mut self,
13190 _: &SplitSelectionIntoLines,
13191 window: &mut Window,
13192 cx: &mut Context<Self>,
13193 ) {
13194 let selections = self
13195 .selections
13196 .all::<Point>(cx)
13197 .into_iter()
13198 .map(|selection| selection.start..selection.end)
13199 .collect::<Vec<_>>();
13200 self.unfold_ranges(&selections, true, true, cx);
13201
13202 let mut new_selection_ranges = Vec::new();
13203 {
13204 let buffer = self.buffer.read(cx).read(cx);
13205 for selection in selections {
13206 for row in selection.start.row..selection.end.row {
13207 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13208 new_selection_ranges.push(cursor..cursor);
13209 }
13210
13211 let is_multiline_selection = selection.start.row != selection.end.row;
13212 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13213 // so this action feels more ergonomic when paired with other selection operations
13214 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13215 if !should_skip_last {
13216 new_selection_ranges.push(selection.end..selection.end);
13217 }
13218 }
13219 }
13220 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13221 s.select_ranges(new_selection_ranges);
13222 });
13223 }
13224
13225 pub fn add_selection_above(
13226 &mut self,
13227 _: &AddSelectionAbove,
13228 window: &mut Window,
13229 cx: &mut Context<Self>,
13230 ) {
13231 self.add_selection(true, window, cx);
13232 }
13233
13234 pub fn add_selection_below(
13235 &mut self,
13236 _: &AddSelectionBelow,
13237 window: &mut Window,
13238 cx: &mut Context<Self>,
13239 ) {
13240 self.add_selection(false, window, cx);
13241 }
13242
13243 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13244 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13245
13246 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13247 let all_selections = self.selections.all::<Point>(cx);
13248 let text_layout_details = self.text_layout_details(window);
13249
13250 let (mut columnar_selections, new_selections_to_columnarize) = {
13251 if let Some(state) = self.add_selections_state.as_ref() {
13252 let columnar_selection_ids: HashSet<_> = state
13253 .groups
13254 .iter()
13255 .flat_map(|group| group.stack.iter())
13256 .copied()
13257 .collect();
13258
13259 all_selections
13260 .into_iter()
13261 .partition(|s| columnar_selection_ids.contains(&s.id))
13262 } else {
13263 (Vec::new(), all_selections)
13264 }
13265 };
13266
13267 let mut state = self
13268 .add_selections_state
13269 .take()
13270 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13271
13272 for selection in new_selections_to_columnarize {
13273 let range = selection.display_range(&display_map).sorted();
13274 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13275 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13276 let positions = start_x.min(end_x)..start_x.max(end_x);
13277 let mut stack = Vec::new();
13278 for row in range.start.row().0..=range.end.row().0 {
13279 if let Some(selection) = self.selections.build_columnar_selection(
13280 &display_map,
13281 DisplayRow(row),
13282 &positions,
13283 selection.reversed,
13284 &text_layout_details,
13285 ) {
13286 stack.push(selection.id);
13287 columnar_selections.push(selection);
13288 }
13289 }
13290 if !stack.is_empty() {
13291 if above {
13292 stack.reverse();
13293 }
13294 state.groups.push(AddSelectionsGroup { above, stack });
13295 }
13296 }
13297
13298 let mut final_selections = Vec::new();
13299 let end_row = if above {
13300 DisplayRow(0)
13301 } else {
13302 display_map.max_point().row()
13303 };
13304
13305 let mut last_added_item_per_group = HashMap::default();
13306 for group in state.groups.iter_mut() {
13307 if let Some(last_id) = group.stack.last() {
13308 last_added_item_per_group.insert(*last_id, group);
13309 }
13310 }
13311
13312 for selection in columnar_selections {
13313 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13314 if above == group.above {
13315 let range = selection.display_range(&display_map).sorted();
13316 debug_assert_eq!(range.start.row(), range.end.row());
13317 let mut row = range.start.row();
13318 let positions =
13319 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13320 px(start)..px(end)
13321 } else {
13322 let start_x =
13323 display_map.x_for_display_point(range.start, &text_layout_details);
13324 let end_x =
13325 display_map.x_for_display_point(range.end, &text_layout_details);
13326 start_x.min(end_x)..start_x.max(end_x)
13327 };
13328
13329 let mut maybe_new_selection = None;
13330 while row != end_row {
13331 if above {
13332 row.0 -= 1;
13333 } else {
13334 row.0 += 1;
13335 }
13336 if let Some(new_selection) = self.selections.build_columnar_selection(
13337 &display_map,
13338 row,
13339 &positions,
13340 selection.reversed,
13341 &text_layout_details,
13342 ) {
13343 maybe_new_selection = Some(new_selection);
13344 break;
13345 }
13346 }
13347
13348 if let Some(new_selection) = maybe_new_selection {
13349 group.stack.push(new_selection.id);
13350 if above {
13351 final_selections.push(new_selection);
13352 final_selections.push(selection);
13353 } else {
13354 final_selections.push(selection);
13355 final_selections.push(new_selection);
13356 }
13357 } else {
13358 final_selections.push(selection);
13359 }
13360 } else {
13361 group.stack.pop();
13362 }
13363 } else {
13364 final_selections.push(selection);
13365 }
13366 }
13367
13368 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13369 s.select(final_selections);
13370 });
13371
13372 let final_selection_ids: HashSet<_> = self
13373 .selections
13374 .all::<Point>(cx)
13375 .iter()
13376 .map(|s| s.id)
13377 .collect();
13378 state.groups.retain_mut(|group| {
13379 // selections might get merged above so we remove invalid items from stacks
13380 group.stack.retain(|id| final_selection_ids.contains(id));
13381
13382 // single selection in stack can be treated as initial state
13383 group.stack.len() > 1
13384 });
13385
13386 if !state.groups.is_empty() {
13387 self.add_selections_state = Some(state);
13388 }
13389 }
13390
13391 fn select_match_ranges(
13392 &mut self,
13393 range: Range<usize>,
13394 reversed: bool,
13395 replace_newest: bool,
13396 auto_scroll: Option<Autoscroll>,
13397 window: &mut Window,
13398 cx: &mut Context<Editor>,
13399 ) {
13400 self.unfold_ranges(
13401 std::slice::from_ref(&range),
13402 false,
13403 auto_scroll.is_some(),
13404 cx,
13405 );
13406 self.change_selections(auto_scroll, window, cx, |s| {
13407 if replace_newest {
13408 s.delete(s.newest_anchor().id);
13409 }
13410 if reversed {
13411 s.insert_range(range.end..range.start);
13412 } else {
13413 s.insert_range(range);
13414 }
13415 });
13416 }
13417
13418 pub fn select_next_match_internal(
13419 &mut self,
13420 display_map: &DisplaySnapshot,
13421 replace_newest: bool,
13422 autoscroll: Option<Autoscroll>,
13423 window: &mut Window,
13424 cx: &mut Context<Self>,
13425 ) -> Result<()> {
13426 let buffer = &display_map.buffer_snapshot;
13427 let mut selections = self.selections.all::<usize>(cx);
13428 if let Some(mut select_next_state) = self.select_next_state.take() {
13429 let query = &select_next_state.query;
13430 if !select_next_state.done {
13431 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13432 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13433 let mut next_selected_range = None;
13434
13435 let bytes_after_last_selection =
13436 buffer.bytes_in_range(last_selection.end..buffer.len());
13437 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13438 let query_matches = query
13439 .stream_find_iter(bytes_after_last_selection)
13440 .map(|result| (last_selection.end, result))
13441 .chain(
13442 query
13443 .stream_find_iter(bytes_before_first_selection)
13444 .map(|result| (0, result)),
13445 );
13446
13447 for (start_offset, query_match) in query_matches {
13448 let query_match = query_match.unwrap(); // can only fail due to I/O
13449 let offset_range =
13450 start_offset + query_match.start()..start_offset + query_match.end();
13451
13452 if !select_next_state.wordwise
13453 || (!buffer.is_inside_word(offset_range.start, false)
13454 && !buffer.is_inside_word(offset_range.end, false))
13455 {
13456 // TODO: This is n^2, because we might check all the selections
13457 if !selections
13458 .iter()
13459 .any(|selection| selection.range().overlaps(&offset_range))
13460 {
13461 next_selected_range = Some(offset_range);
13462 break;
13463 }
13464 }
13465 }
13466
13467 if let Some(next_selected_range) = next_selected_range {
13468 self.select_match_ranges(
13469 next_selected_range,
13470 last_selection.reversed,
13471 replace_newest,
13472 autoscroll,
13473 window,
13474 cx,
13475 );
13476 } else {
13477 select_next_state.done = true;
13478 }
13479 }
13480
13481 self.select_next_state = Some(select_next_state);
13482 } else {
13483 let mut only_carets = true;
13484 let mut same_text_selected = true;
13485 let mut selected_text = None;
13486
13487 let mut selections_iter = selections.iter().peekable();
13488 while let Some(selection) = selections_iter.next() {
13489 if selection.start != selection.end {
13490 only_carets = false;
13491 }
13492
13493 if same_text_selected {
13494 if selected_text.is_none() {
13495 selected_text =
13496 Some(buffer.text_for_range(selection.range()).collect::<String>());
13497 }
13498
13499 if let Some(next_selection) = selections_iter.peek() {
13500 if next_selection.range().len() == selection.range().len() {
13501 let next_selected_text = buffer
13502 .text_for_range(next_selection.range())
13503 .collect::<String>();
13504 if Some(next_selected_text) != selected_text {
13505 same_text_selected = false;
13506 selected_text = None;
13507 }
13508 } else {
13509 same_text_selected = false;
13510 selected_text = None;
13511 }
13512 }
13513 }
13514 }
13515
13516 if only_carets {
13517 for selection in &mut selections {
13518 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13519 selection.start = word_range.start;
13520 selection.end = word_range.end;
13521 selection.goal = SelectionGoal::None;
13522 selection.reversed = false;
13523 self.select_match_ranges(
13524 selection.start..selection.end,
13525 selection.reversed,
13526 replace_newest,
13527 autoscroll,
13528 window,
13529 cx,
13530 );
13531 }
13532
13533 if selections.len() == 1 {
13534 let selection = selections
13535 .last()
13536 .expect("ensured that there's only one selection");
13537 let query = buffer
13538 .text_for_range(selection.start..selection.end)
13539 .collect::<String>();
13540 let is_empty = query.is_empty();
13541 let select_state = SelectNextState {
13542 query: AhoCorasick::new(&[query])?,
13543 wordwise: true,
13544 done: is_empty,
13545 };
13546 self.select_next_state = Some(select_state);
13547 } else {
13548 self.select_next_state = None;
13549 }
13550 } else if let Some(selected_text) = selected_text {
13551 self.select_next_state = Some(SelectNextState {
13552 query: AhoCorasick::new(&[selected_text])?,
13553 wordwise: false,
13554 done: false,
13555 });
13556 self.select_next_match_internal(
13557 display_map,
13558 replace_newest,
13559 autoscroll,
13560 window,
13561 cx,
13562 )?;
13563 }
13564 }
13565 Ok(())
13566 }
13567
13568 pub fn select_all_matches(
13569 &mut self,
13570 _action: &SelectAllMatches,
13571 window: &mut Window,
13572 cx: &mut Context<Self>,
13573 ) -> Result<()> {
13574 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13575
13576 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13577
13578 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13579 let Some(select_next_state) = self.select_next_state.as_mut() else {
13580 return Ok(());
13581 };
13582 if select_next_state.done {
13583 return Ok(());
13584 }
13585
13586 let mut new_selections = Vec::new();
13587
13588 let reversed = self.selections.oldest::<usize>(cx).reversed;
13589 let buffer = &display_map.buffer_snapshot;
13590 let query_matches = select_next_state
13591 .query
13592 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13593
13594 for query_match in query_matches.into_iter() {
13595 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13596 let offset_range = if reversed {
13597 query_match.end()..query_match.start()
13598 } else {
13599 query_match.start()..query_match.end()
13600 };
13601
13602 if !select_next_state.wordwise
13603 || (!buffer.is_inside_word(offset_range.start, false)
13604 && !buffer.is_inside_word(offset_range.end, false))
13605 {
13606 new_selections.push(offset_range.start..offset_range.end);
13607 }
13608 }
13609
13610 select_next_state.done = true;
13611
13612 if new_selections.is_empty() {
13613 log::error!("bug: new_selections is empty in select_all_matches");
13614 return Ok(());
13615 }
13616
13617 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13618 self.change_selections(None, window, cx, |selections| {
13619 selections.select_ranges(new_selections)
13620 });
13621
13622 Ok(())
13623 }
13624
13625 pub fn select_next(
13626 &mut self,
13627 action: &SelectNext,
13628 window: &mut Window,
13629 cx: &mut Context<Self>,
13630 ) -> Result<()> {
13631 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13632 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13633 self.select_next_match_internal(
13634 &display_map,
13635 action.replace_newest,
13636 Some(Autoscroll::newest()),
13637 window,
13638 cx,
13639 )?;
13640 Ok(())
13641 }
13642
13643 pub fn select_previous(
13644 &mut self,
13645 action: &SelectPrevious,
13646 window: &mut Window,
13647 cx: &mut Context<Self>,
13648 ) -> Result<()> {
13649 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13650 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13651 let buffer = &display_map.buffer_snapshot;
13652 let mut selections = self.selections.all::<usize>(cx);
13653 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13654 let query = &select_prev_state.query;
13655 if !select_prev_state.done {
13656 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13657 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13658 let mut next_selected_range = None;
13659 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13660 let bytes_before_last_selection =
13661 buffer.reversed_bytes_in_range(0..last_selection.start);
13662 let bytes_after_first_selection =
13663 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13664 let query_matches = query
13665 .stream_find_iter(bytes_before_last_selection)
13666 .map(|result| (last_selection.start, result))
13667 .chain(
13668 query
13669 .stream_find_iter(bytes_after_first_selection)
13670 .map(|result| (buffer.len(), result)),
13671 );
13672 for (end_offset, query_match) in query_matches {
13673 let query_match = query_match.unwrap(); // can only fail due to I/O
13674 let offset_range =
13675 end_offset - query_match.end()..end_offset - query_match.start();
13676
13677 if !select_prev_state.wordwise
13678 || (!buffer.is_inside_word(offset_range.start, false)
13679 && !buffer.is_inside_word(offset_range.end, false))
13680 {
13681 next_selected_range = Some(offset_range);
13682 break;
13683 }
13684 }
13685
13686 if let Some(next_selected_range) = next_selected_range {
13687 self.select_match_ranges(
13688 next_selected_range,
13689 last_selection.reversed,
13690 action.replace_newest,
13691 Some(Autoscroll::newest()),
13692 window,
13693 cx,
13694 );
13695 } else {
13696 select_prev_state.done = true;
13697 }
13698 }
13699
13700 self.select_prev_state = Some(select_prev_state);
13701 } else {
13702 let mut only_carets = true;
13703 let mut same_text_selected = true;
13704 let mut selected_text = None;
13705
13706 let mut selections_iter = selections.iter().peekable();
13707 while let Some(selection) = selections_iter.next() {
13708 if selection.start != selection.end {
13709 only_carets = false;
13710 }
13711
13712 if same_text_selected {
13713 if selected_text.is_none() {
13714 selected_text =
13715 Some(buffer.text_for_range(selection.range()).collect::<String>());
13716 }
13717
13718 if let Some(next_selection) = selections_iter.peek() {
13719 if next_selection.range().len() == selection.range().len() {
13720 let next_selected_text = buffer
13721 .text_for_range(next_selection.range())
13722 .collect::<String>();
13723 if Some(next_selected_text) != selected_text {
13724 same_text_selected = false;
13725 selected_text = None;
13726 }
13727 } else {
13728 same_text_selected = false;
13729 selected_text = None;
13730 }
13731 }
13732 }
13733 }
13734
13735 if only_carets {
13736 for selection in &mut selections {
13737 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13738 selection.start = word_range.start;
13739 selection.end = word_range.end;
13740 selection.goal = SelectionGoal::None;
13741 selection.reversed = false;
13742 self.select_match_ranges(
13743 selection.start..selection.end,
13744 selection.reversed,
13745 action.replace_newest,
13746 Some(Autoscroll::newest()),
13747 window,
13748 cx,
13749 );
13750 }
13751 if selections.len() == 1 {
13752 let selection = selections
13753 .last()
13754 .expect("ensured that there's only one selection");
13755 let query = buffer
13756 .text_for_range(selection.start..selection.end)
13757 .collect::<String>();
13758 let is_empty = query.is_empty();
13759 let select_state = SelectNextState {
13760 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13761 wordwise: true,
13762 done: is_empty,
13763 };
13764 self.select_prev_state = Some(select_state);
13765 } else {
13766 self.select_prev_state = None;
13767 }
13768 } else if let Some(selected_text) = selected_text {
13769 self.select_prev_state = Some(SelectNextState {
13770 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13771 wordwise: false,
13772 done: false,
13773 });
13774 self.select_previous(action, window, cx)?;
13775 }
13776 }
13777 Ok(())
13778 }
13779
13780 pub fn find_next_match(
13781 &mut self,
13782 _: &FindNextMatch,
13783 window: &mut Window,
13784 cx: &mut Context<Self>,
13785 ) -> Result<()> {
13786 let selections = self.selections.disjoint_anchors();
13787 match selections.first() {
13788 Some(first) if selections.len() >= 2 => {
13789 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13790 s.select_ranges([first.range()]);
13791 });
13792 }
13793 _ => self.select_next(
13794 &SelectNext {
13795 replace_newest: true,
13796 },
13797 window,
13798 cx,
13799 )?,
13800 }
13801 Ok(())
13802 }
13803
13804 pub fn find_previous_match(
13805 &mut self,
13806 _: &FindPreviousMatch,
13807 window: &mut Window,
13808 cx: &mut Context<Self>,
13809 ) -> Result<()> {
13810 let selections = self.selections.disjoint_anchors();
13811 match selections.last() {
13812 Some(last) if selections.len() >= 2 => {
13813 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13814 s.select_ranges([last.range()]);
13815 });
13816 }
13817 _ => self.select_previous(
13818 &SelectPrevious {
13819 replace_newest: true,
13820 },
13821 window,
13822 cx,
13823 )?,
13824 }
13825 Ok(())
13826 }
13827
13828 pub fn toggle_comments(
13829 &mut self,
13830 action: &ToggleComments,
13831 window: &mut Window,
13832 cx: &mut Context<Self>,
13833 ) {
13834 if self.read_only(cx) {
13835 return;
13836 }
13837 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13838 let text_layout_details = &self.text_layout_details(window);
13839 self.transact(window, cx, |this, window, cx| {
13840 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13841 let mut edits = Vec::new();
13842 let mut selection_edit_ranges = Vec::new();
13843 let mut last_toggled_row = None;
13844 let snapshot = this.buffer.read(cx).read(cx);
13845 let empty_str: Arc<str> = Arc::default();
13846 let mut suffixes_inserted = Vec::new();
13847 let ignore_indent = action.ignore_indent;
13848
13849 fn comment_prefix_range(
13850 snapshot: &MultiBufferSnapshot,
13851 row: MultiBufferRow,
13852 comment_prefix: &str,
13853 comment_prefix_whitespace: &str,
13854 ignore_indent: bool,
13855 ) -> Range<Point> {
13856 let indent_size = if ignore_indent {
13857 0
13858 } else {
13859 snapshot.indent_size_for_line(row).len
13860 };
13861
13862 let start = Point::new(row.0, indent_size);
13863
13864 let mut line_bytes = snapshot
13865 .bytes_in_range(start..snapshot.max_point())
13866 .flatten()
13867 .copied();
13868
13869 // If this line currently begins with the line comment prefix, then record
13870 // the range containing the prefix.
13871 if line_bytes
13872 .by_ref()
13873 .take(comment_prefix.len())
13874 .eq(comment_prefix.bytes())
13875 {
13876 // Include any whitespace that matches the comment prefix.
13877 let matching_whitespace_len = line_bytes
13878 .zip(comment_prefix_whitespace.bytes())
13879 .take_while(|(a, b)| a == b)
13880 .count() as u32;
13881 let end = Point::new(
13882 start.row,
13883 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13884 );
13885 start..end
13886 } else {
13887 start..start
13888 }
13889 }
13890
13891 fn comment_suffix_range(
13892 snapshot: &MultiBufferSnapshot,
13893 row: MultiBufferRow,
13894 comment_suffix: &str,
13895 comment_suffix_has_leading_space: bool,
13896 ) -> Range<Point> {
13897 let end = Point::new(row.0, snapshot.line_len(row));
13898 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13899
13900 let mut line_end_bytes = snapshot
13901 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13902 .flatten()
13903 .copied();
13904
13905 let leading_space_len = if suffix_start_column > 0
13906 && line_end_bytes.next() == Some(b' ')
13907 && comment_suffix_has_leading_space
13908 {
13909 1
13910 } else {
13911 0
13912 };
13913
13914 // If this line currently begins with the line comment prefix, then record
13915 // the range containing the prefix.
13916 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13917 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13918 start..end
13919 } else {
13920 end..end
13921 }
13922 }
13923
13924 // TODO: Handle selections that cross excerpts
13925 for selection in &mut selections {
13926 let start_column = snapshot
13927 .indent_size_for_line(MultiBufferRow(selection.start.row))
13928 .len;
13929 let language = if let Some(language) =
13930 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13931 {
13932 language
13933 } else {
13934 continue;
13935 };
13936
13937 selection_edit_ranges.clear();
13938
13939 // If multiple selections contain a given row, avoid processing that
13940 // row more than once.
13941 let mut start_row = MultiBufferRow(selection.start.row);
13942 if last_toggled_row == Some(start_row) {
13943 start_row = start_row.next_row();
13944 }
13945 let end_row =
13946 if selection.end.row > selection.start.row && selection.end.column == 0 {
13947 MultiBufferRow(selection.end.row - 1)
13948 } else {
13949 MultiBufferRow(selection.end.row)
13950 };
13951 last_toggled_row = Some(end_row);
13952
13953 if start_row > end_row {
13954 continue;
13955 }
13956
13957 // If the language has line comments, toggle those.
13958 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13959
13960 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13961 if ignore_indent {
13962 full_comment_prefixes = full_comment_prefixes
13963 .into_iter()
13964 .map(|s| Arc::from(s.trim_end()))
13965 .collect();
13966 }
13967
13968 if !full_comment_prefixes.is_empty() {
13969 let first_prefix = full_comment_prefixes
13970 .first()
13971 .expect("prefixes is non-empty");
13972 let prefix_trimmed_lengths = full_comment_prefixes
13973 .iter()
13974 .map(|p| p.trim_end_matches(' ').len())
13975 .collect::<SmallVec<[usize; 4]>>();
13976
13977 let mut all_selection_lines_are_comments = true;
13978
13979 for row in start_row.0..=end_row.0 {
13980 let row = MultiBufferRow(row);
13981 if start_row < end_row && snapshot.is_line_blank(row) {
13982 continue;
13983 }
13984
13985 let prefix_range = full_comment_prefixes
13986 .iter()
13987 .zip(prefix_trimmed_lengths.iter().copied())
13988 .map(|(prefix, trimmed_prefix_len)| {
13989 comment_prefix_range(
13990 snapshot.deref(),
13991 row,
13992 &prefix[..trimmed_prefix_len],
13993 &prefix[trimmed_prefix_len..],
13994 ignore_indent,
13995 )
13996 })
13997 .max_by_key(|range| range.end.column - range.start.column)
13998 .expect("prefixes is non-empty");
13999
14000 if prefix_range.is_empty() {
14001 all_selection_lines_are_comments = false;
14002 }
14003
14004 selection_edit_ranges.push(prefix_range);
14005 }
14006
14007 if all_selection_lines_are_comments {
14008 edits.extend(
14009 selection_edit_ranges
14010 .iter()
14011 .cloned()
14012 .map(|range| (range, empty_str.clone())),
14013 );
14014 } else {
14015 let min_column = selection_edit_ranges
14016 .iter()
14017 .map(|range| range.start.column)
14018 .min()
14019 .unwrap_or(0);
14020 edits.extend(selection_edit_ranges.iter().map(|range| {
14021 let position = Point::new(range.start.row, min_column);
14022 (position..position, first_prefix.clone())
14023 }));
14024 }
14025 } else if let Some((full_comment_prefix, comment_suffix)) =
14026 language.block_comment_delimiters()
14027 {
14028 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14029 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14030 let prefix_range = comment_prefix_range(
14031 snapshot.deref(),
14032 start_row,
14033 comment_prefix,
14034 comment_prefix_whitespace,
14035 ignore_indent,
14036 );
14037 let suffix_range = comment_suffix_range(
14038 snapshot.deref(),
14039 end_row,
14040 comment_suffix.trim_start_matches(' '),
14041 comment_suffix.starts_with(' '),
14042 );
14043
14044 if prefix_range.is_empty() || suffix_range.is_empty() {
14045 edits.push((
14046 prefix_range.start..prefix_range.start,
14047 full_comment_prefix.clone(),
14048 ));
14049 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14050 suffixes_inserted.push((end_row, comment_suffix.len()));
14051 } else {
14052 edits.push((prefix_range, empty_str.clone()));
14053 edits.push((suffix_range, empty_str.clone()));
14054 }
14055 } else {
14056 continue;
14057 }
14058 }
14059
14060 drop(snapshot);
14061 this.buffer.update(cx, |buffer, cx| {
14062 buffer.edit(edits, None, cx);
14063 });
14064
14065 // Adjust selections so that they end before any comment suffixes that
14066 // were inserted.
14067 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14068 let mut selections = this.selections.all::<Point>(cx);
14069 let snapshot = this.buffer.read(cx).read(cx);
14070 for selection in &mut selections {
14071 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14072 match row.cmp(&MultiBufferRow(selection.end.row)) {
14073 Ordering::Less => {
14074 suffixes_inserted.next();
14075 continue;
14076 }
14077 Ordering::Greater => break,
14078 Ordering::Equal => {
14079 if selection.end.column == snapshot.line_len(row) {
14080 if selection.is_empty() {
14081 selection.start.column -= suffix_len as u32;
14082 }
14083 selection.end.column -= suffix_len as u32;
14084 }
14085 break;
14086 }
14087 }
14088 }
14089 }
14090
14091 drop(snapshot);
14092 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14093 s.select(selections)
14094 });
14095
14096 let selections = this.selections.all::<Point>(cx);
14097 let selections_on_single_row = selections.windows(2).all(|selections| {
14098 selections[0].start.row == selections[1].start.row
14099 && selections[0].end.row == selections[1].end.row
14100 && selections[0].start.row == selections[0].end.row
14101 });
14102 let selections_selecting = selections
14103 .iter()
14104 .any(|selection| selection.start != selection.end);
14105 let advance_downwards = action.advance_downwards
14106 && selections_on_single_row
14107 && !selections_selecting
14108 && !matches!(this.mode, EditorMode::SingleLine { .. });
14109
14110 if advance_downwards {
14111 let snapshot = this.buffer.read(cx).snapshot(cx);
14112
14113 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14114 s.move_cursors_with(|display_snapshot, display_point, _| {
14115 let mut point = display_point.to_point(display_snapshot);
14116 point.row += 1;
14117 point = snapshot.clip_point(point, Bias::Left);
14118 let display_point = point.to_display_point(display_snapshot);
14119 let goal = SelectionGoal::HorizontalPosition(
14120 display_snapshot
14121 .x_for_display_point(display_point, text_layout_details)
14122 .into(),
14123 );
14124 (display_point, goal)
14125 })
14126 });
14127 }
14128 });
14129 }
14130
14131 pub fn select_enclosing_symbol(
14132 &mut self,
14133 _: &SelectEnclosingSymbol,
14134 window: &mut Window,
14135 cx: &mut Context<Self>,
14136 ) {
14137 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14138
14139 let buffer = self.buffer.read(cx).snapshot(cx);
14140 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14141
14142 fn update_selection(
14143 selection: &Selection<usize>,
14144 buffer_snap: &MultiBufferSnapshot,
14145 ) -> Option<Selection<usize>> {
14146 let cursor = selection.head();
14147 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14148 for symbol in symbols.iter().rev() {
14149 let start = symbol.range.start.to_offset(buffer_snap);
14150 let end = symbol.range.end.to_offset(buffer_snap);
14151 let new_range = start..end;
14152 if start < selection.start || end > selection.end {
14153 return Some(Selection {
14154 id: selection.id,
14155 start: new_range.start,
14156 end: new_range.end,
14157 goal: SelectionGoal::None,
14158 reversed: selection.reversed,
14159 });
14160 }
14161 }
14162 None
14163 }
14164
14165 let mut selected_larger_symbol = false;
14166 let new_selections = old_selections
14167 .iter()
14168 .map(|selection| match update_selection(selection, &buffer) {
14169 Some(new_selection) => {
14170 if new_selection.range() != selection.range() {
14171 selected_larger_symbol = true;
14172 }
14173 new_selection
14174 }
14175 None => selection.clone(),
14176 })
14177 .collect::<Vec<_>>();
14178
14179 if selected_larger_symbol {
14180 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14181 s.select(new_selections);
14182 });
14183 }
14184 }
14185
14186 pub fn select_larger_syntax_node(
14187 &mut self,
14188 _: &SelectLargerSyntaxNode,
14189 window: &mut Window,
14190 cx: &mut Context<Self>,
14191 ) {
14192 let Some(visible_row_count) = self.visible_row_count() else {
14193 return;
14194 };
14195 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14196 if old_selections.is_empty() {
14197 return;
14198 }
14199
14200 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14201
14202 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14203 let buffer = self.buffer.read(cx).snapshot(cx);
14204
14205 let mut selected_larger_node = false;
14206 let mut new_selections = old_selections
14207 .iter()
14208 .map(|selection| {
14209 let old_range = selection.start..selection.end;
14210
14211 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14212 // manually select word at selection
14213 if ["string_content", "inline"].contains(&node.kind()) {
14214 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14215 // ignore if word is already selected
14216 if !word_range.is_empty() && old_range != word_range {
14217 let (last_word_range, _) =
14218 buffer.surrounding_word(old_range.end, false);
14219 // only select word if start and end point belongs to same word
14220 if word_range == last_word_range {
14221 selected_larger_node = true;
14222 return Selection {
14223 id: selection.id,
14224 start: word_range.start,
14225 end: word_range.end,
14226 goal: SelectionGoal::None,
14227 reversed: selection.reversed,
14228 };
14229 }
14230 }
14231 }
14232 }
14233
14234 let mut new_range = old_range.clone();
14235 while let Some((_node, containing_range)) =
14236 buffer.syntax_ancestor(new_range.clone())
14237 {
14238 new_range = match containing_range {
14239 MultiOrSingleBufferOffsetRange::Single(_) => break,
14240 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14241 };
14242 if !display_map.intersects_fold(new_range.start)
14243 && !display_map.intersects_fold(new_range.end)
14244 {
14245 break;
14246 }
14247 }
14248
14249 selected_larger_node |= new_range != old_range;
14250 Selection {
14251 id: selection.id,
14252 start: new_range.start,
14253 end: new_range.end,
14254 goal: SelectionGoal::None,
14255 reversed: selection.reversed,
14256 }
14257 })
14258 .collect::<Vec<_>>();
14259
14260 if !selected_larger_node {
14261 return; // don't put this call in the history
14262 }
14263
14264 // scroll based on transformation done to the last selection created by the user
14265 let (last_old, last_new) = old_selections
14266 .last()
14267 .zip(new_selections.last().cloned())
14268 .expect("old_selections isn't empty");
14269
14270 // revert selection
14271 let is_selection_reversed = {
14272 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14273 new_selections.last_mut().expect("checked above").reversed =
14274 should_newest_selection_be_reversed;
14275 should_newest_selection_be_reversed
14276 };
14277
14278 if selected_larger_node {
14279 self.select_syntax_node_history.disable_clearing = true;
14280 self.change_selections(None, window, cx, |s| {
14281 s.select(new_selections.clone());
14282 });
14283 self.select_syntax_node_history.disable_clearing = false;
14284 }
14285
14286 let start_row = last_new.start.to_display_point(&display_map).row().0;
14287 let end_row = last_new.end.to_display_point(&display_map).row().0;
14288 let selection_height = end_row - start_row + 1;
14289 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14290
14291 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14292 let scroll_behavior = if fits_on_the_screen {
14293 self.request_autoscroll(Autoscroll::fit(), cx);
14294 SelectSyntaxNodeScrollBehavior::FitSelection
14295 } else if is_selection_reversed {
14296 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14297 SelectSyntaxNodeScrollBehavior::CursorTop
14298 } else {
14299 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14300 SelectSyntaxNodeScrollBehavior::CursorBottom
14301 };
14302
14303 self.select_syntax_node_history.push((
14304 old_selections,
14305 scroll_behavior,
14306 is_selection_reversed,
14307 ));
14308 }
14309
14310 pub fn select_smaller_syntax_node(
14311 &mut self,
14312 _: &SelectSmallerSyntaxNode,
14313 window: &mut Window,
14314 cx: &mut Context<Self>,
14315 ) {
14316 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14317
14318 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14319 self.select_syntax_node_history.pop()
14320 {
14321 if let Some(selection) = selections.last_mut() {
14322 selection.reversed = is_selection_reversed;
14323 }
14324
14325 self.select_syntax_node_history.disable_clearing = true;
14326 self.change_selections(None, window, cx, |s| {
14327 s.select(selections.to_vec());
14328 });
14329 self.select_syntax_node_history.disable_clearing = false;
14330
14331 match scroll_behavior {
14332 SelectSyntaxNodeScrollBehavior::CursorTop => {
14333 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14334 }
14335 SelectSyntaxNodeScrollBehavior::FitSelection => {
14336 self.request_autoscroll(Autoscroll::fit(), cx);
14337 }
14338 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14339 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14340 }
14341 }
14342 }
14343 }
14344
14345 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14346 if !EditorSettings::get_global(cx).gutter.runnables {
14347 self.clear_tasks();
14348 return Task::ready(());
14349 }
14350 let project = self.project.as_ref().map(Entity::downgrade);
14351 let task_sources = self.lsp_task_sources(cx);
14352 let multi_buffer = self.buffer.downgrade();
14353 cx.spawn_in(window, async move |editor, cx| {
14354 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14355 let Some(project) = project.and_then(|p| p.upgrade()) else {
14356 return;
14357 };
14358 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14359 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14360 }) else {
14361 return;
14362 };
14363
14364 let hide_runnables = project
14365 .update(cx, |project, cx| {
14366 // Do not display any test indicators in non-dev server remote projects.
14367 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14368 })
14369 .unwrap_or(true);
14370 if hide_runnables {
14371 return;
14372 }
14373 let new_rows =
14374 cx.background_spawn({
14375 let snapshot = display_snapshot.clone();
14376 async move {
14377 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14378 }
14379 })
14380 .await;
14381 let Ok(lsp_tasks) =
14382 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14383 else {
14384 return;
14385 };
14386 let lsp_tasks = lsp_tasks.await;
14387
14388 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14389 lsp_tasks
14390 .into_iter()
14391 .flat_map(|(kind, tasks)| {
14392 tasks.into_iter().filter_map(move |(location, task)| {
14393 Some((kind.clone(), location?, task))
14394 })
14395 })
14396 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14397 let buffer = location.target.buffer;
14398 let buffer_snapshot = buffer.read(cx).snapshot();
14399 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14400 |(excerpt_id, snapshot, _)| {
14401 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14402 display_snapshot
14403 .buffer_snapshot
14404 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14405 } else {
14406 None
14407 }
14408 },
14409 );
14410 if let Some(offset) = offset {
14411 let task_buffer_range =
14412 location.target.range.to_point(&buffer_snapshot);
14413 let context_buffer_range =
14414 task_buffer_range.to_offset(&buffer_snapshot);
14415 let context_range = BufferOffset(context_buffer_range.start)
14416 ..BufferOffset(context_buffer_range.end);
14417
14418 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14419 .or_insert_with(|| RunnableTasks {
14420 templates: Vec::new(),
14421 offset,
14422 column: task_buffer_range.start.column,
14423 extra_variables: HashMap::default(),
14424 context_range,
14425 })
14426 .templates
14427 .push((kind, task.original_task().clone()));
14428 }
14429
14430 acc
14431 })
14432 }) else {
14433 return;
14434 };
14435
14436 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14437 buffer.language_settings(cx).tasks.prefer_lsp
14438 }) else {
14439 return;
14440 };
14441
14442 let rows = Self::runnable_rows(
14443 project,
14444 display_snapshot,
14445 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14446 new_rows,
14447 cx.clone(),
14448 )
14449 .await;
14450 editor
14451 .update(cx, |editor, _| {
14452 editor.clear_tasks();
14453 for (key, mut value) in rows {
14454 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14455 value.templates.extend(lsp_tasks.templates);
14456 }
14457
14458 editor.insert_tasks(key, value);
14459 }
14460 for (key, value) in lsp_tasks_by_rows {
14461 editor.insert_tasks(key, value);
14462 }
14463 })
14464 .ok();
14465 })
14466 }
14467 fn fetch_runnable_ranges(
14468 snapshot: &DisplaySnapshot,
14469 range: Range<Anchor>,
14470 ) -> Vec<language::RunnableRange> {
14471 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14472 }
14473
14474 fn runnable_rows(
14475 project: Entity<Project>,
14476 snapshot: DisplaySnapshot,
14477 prefer_lsp: bool,
14478 runnable_ranges: Vec<RunnableRange>,
14479 cx: AsyncWindowContext,
14480 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14481 cx.spawn(async move |cx| {
14482 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14483 for mut runnable in runnable_ranges {
14484 let Some(tasks) = cx
14485 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14486 .ok()
14487 else {
14488 continue;
14489 };
14490 let mut tasks = tasks.await;
14491
14492 if prefer_lsp {
14493 tasks.retain(|(task_kind, _)| {
14494 !matches!(task_kind, TaskSourceKind::Language { .. })
14495 });
14496 }
14497 if tasks.is_empty() {
14498 continue;
14499 }
14500
14501 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14502 let Some(row) = snapshot
14503 .buffer_snapshot
14504 .buffer_line_for_row(MultiBufferRow(point.row))
14505 .map(|(_, range)| range.start.row)
14506 else {
14507 continue;
14508 };
14509
14510 let context_range =
14511 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14512 runnable_rows.push((
14513 (runnable.buffer_id, row),
14514 RunnableTasks {
14515 templates: tasks,
14516 offset: snapshot
14517 .buffer_snapshot
14518 .anchor_before(runnable.run_range.start),
14519 context_range,
14520 column: point.column,
14521 extra_variables: runnable.extra_captures,
14522 },
14523 ));
14524 }
14525 runnable_rows
14526 })
14527 }
14528
14529 fn templates_with_tags(
14530 project: &Entity<Project>,
14531 runnable: &mut Runnable,
14532 cx: &mut App,
14533 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14534 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14535 let (worktree_id, file) = project
14536 .buffer_for_id(runnable.buffer, cx)
14537 .and_then(|buffer| buffer.read(cx).file())
14538 .map(|file| (file.worktree_id(cx), file.clone()))
14539 .unzip();
14540
14541 (
14542 project.task_store().read(cx).task_inventory().cloned(),
14543 worktree_id,
14544 file,
14545 )
14546 });
14547
14548 let tags = mem::take(&mut runnable.tags);
14549 let language = runnable.language.clone();
14550 cx.spawn(async move |cx| {
14551 let mut templates_with_tags = Vec::new();
14552 if let Some(inventory) = inventory {
14553 for RunnableTag(tag) in tags {
14554 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14555 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14556 }) else {
14557 return templates_with_tags;
14558 };
14559 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14560 move |(_, template)| {
14561 template.tags.iter().any(|source_tag| source_tag == &tag)
14562 },
14563 ));
14564 }
14565 }
14566 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14567
14568 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14569 // Strongest source wins; if we have worktree tag binding, prefer that to
14570 // global and language bindings;
14571 // if we have a global binding, prefer that to language binding.
14572 let first_mismatch = templates_with_tags
14573 .iter()
14574 .position(|(tag_source, _)| tag_source != leading_tag_source);
14575 if let Some(index) = first_mismatch {
14576 templates_with_tags.truncate(index);
14577 }
14578 }
14579
14580 templates_with_tags
14581 })
14582 }
14583
14584 pub fn move_to_enclosing_bracket(
14585 &mut self,
14586 _: &MoveToEnclosingBracket,
14587 window: &mut Window,
14588 cx: &mut Context<Self>,
14589 ) {
14590 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14591 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14592 s.move_offsets_with(|snapshot, selection| {
14593 let Some(enclosing_bracket_ranges) =
14594 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14595 else {
14596 return;
14597 };
14598
14599 let mut best_length = usize::MAX;
14600 let mut best_inside = false;
14601 let mut best_in_bracket_range = false;
14602 let mut best_destination = None;
14603 for (open, close) in enclosing_bracket_ranges {
14604 let close = close.to_inclusive();
14605 let length = close.end() - open.start;
14606 let inside = selection.start >= open.end && selection.end <= *close.start();
14607 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14608 || close.contains(&selection.head());
14609
14610 // If best is next to a bracket and current isn't, skip
14611 if !in_bracket_range && best_in_bracket_range {
14612 continue;
14613 }
14614
14615 // Prefer smaller lengths unless best is inside and current isn't
14616 if length > best_length && (best_inside || !inside) {
14617 continue;
14618 }
14619
14620 best_length = length;
14621 best_inside = inside;
14622 best_in_bracket_range = in_bracket_range;
14623 best_destination = Some(
14624 if close.contains(&selection.start) && close.contains(&selection.end) {
14625 if inside { open.end } else { open.start }
14626 } else if inside {
14627 *close.start()
14628 } else {
14629 *close.end()
14630 },
14631 );
14632 }
14633
14634 if let Some(destination) = best_destination {
14635 selection.collapse_to(destination, SelectionGoal::None);
14636 }
14637 })
14638 });
14639 }
14640
14641 pub fn undo_selection(
14642 &mut self,
14643 _: &UndoSelection,
14644 window: &mut Window,
14645 cx: &mut Context<Self>,
14646 ) {
14647 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14648 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14649 self.selection_history.mode = SelectionHistoryMode::Undoing;
14650 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14651 this.end_selection(window, cx);
14652 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14653 s.select_anchors(entry.selections.to_vec())
14654 });
14655 });
14656 self.selection_history.mode = SelectionHistoryMode::Normal;
14657
14658 self.select_next_state = entry.select_next_state;
14659 self.select_prev_state = entry.select_prev_state;
14660 self.add_selections_state = entry.add_selections_state;
14661 }
14662 }
14663
14664 pub fn redo_selection(
14665 &mut self,
14666 _: &RedoSelection,
14667 window: &mut Window,
14668 cx: &mut Context<Self>,
14669 ) {
14670 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14671 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14672 self.selection_history.mode = SelectionHistoryMode::Redoing;
14673 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14674 this.end_selection(window, cx);
14675 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14676 s.select_anchors(entry.selections.to_vec())
14677 });
14678 });
14679 self.selection_history.mode = SelectionHistoryMode::Normal;
14680
14681 self.select_next_state = entry.select_next_state;
14682 self.select_prev_state = entry.select_prev_state;
14683 self.add_selections_state = entry.add_selections_state;
14684 }
14685 }
14686
14687 pub fn expand_excerpts(
14688 &mut self,
14689 action: &ExpandExcerpts,
14690 _: &mut Window,
14691 cx: &mut Context<Self>,
14692 ) {
14693 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14694 }
14695
14696 pub fn expand_excerpts_down(
14697 &mut self,
14698 action: &ExpandExcerptsDown,
14699 _: &mut Window,
14700 cx: &mut Context<Self>,
14701 ) {
14702 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14703 }
14704
14705 pub fn expand_excerpts_up(
14706 &mut self,
14707 action: &ExpandExcerptsUp,
14708 _: &mut Window,
14709 cx: &mut Context<Self>,
14710 ) {
14711 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14712 }
14713
14714 pub fn expand_excerpts_for_direction(
14715 &mut self,
14716 lines: u32,
14717 direction: ExpandExcerptDirection,
14718
14719 cx: &mut Context<Self>,
14720 ) {
14721 let selections = self.selections.disjoint_anchors();
14722
14723 let lines = if lines == 0 {
14724 EditorSettings::get_global(cx).expand_excerpt_lines
14725 } else {
14726 lines
14727 };
14728
14729 self.buffer.update(cx, |buffer, cx| {
14730 let snapshot = buffer.snapshot(cx);
14731 let mut excerpt_ids = selections
14732 .iter()
14733 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14734 .collect::<Vec<_>>();
14735 excerpt_ids.sort();
14736 excerpt_ids.dedup();
14737 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14738 })
14739 }
14740
14741 pub fn expand_excerpt(
14742 &mut self,
14743 excerpt: ExcerptId,
14744 direction: ExpandExcerptDirection,
14745 window: &mut Window,
14746 cx: &mut Context<Self>,
14747 ) {
14748 let current_scroll_position = self.scroll_position(cx);
14749 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14750 let mut should_scroll_up = false;
14751
14752 if direction == ExpandExcerptDirection::Down {
14753 let multi_buffer = self.buffer.read(cx);
14754 let snapshot = multi_buffer.snapshot(cx);
14755 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14756 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14757 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14758 let buffer_snapshot = buffer.read(cx).snapshot();
14759 let excerpt_end_row =
14760 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14761 let last_row = buffer_snapshot.max_point().row;
14762 let lines_below = last_row.saturating_sub(excerpt_end_row);
14763 should_scroll_up = lines_below >= lines_to_expand;
14764 }
14765 }
14766 }
14767 }
14768
14769 self.buffer.update(cx, |buffer, cx| {
14770 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14771 });
14772
14773 if should_scroll_up {
14774 let new_scroll_position =
14775 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14776 self.set_scroll_position(new_scroll_position, window, cx);
14777 }
14778 }
14779
14780 pub fn go_to_singleton_buffer_point(
14781 &mut self,
14782 point: Point,
14783 window: &mut Window,
14784 cx: &mut Context<Self>,
14785 ) {
14786 self.go_to_singleton_buffer_range(point..point, window, cx);
14787 }
14788
14789 pub fn go_to_singleton_buffer_range(
14790 &mut self,
14791 range: Range<Point>,
14792 window: &mut Window,
14793 cx: &mut Context<Self>,
14794 ) {
14795 let multibuffer = self.buffer().read(cx);
14796 let Some(buffer) = multibuffer.as_singleton() else {
14797 return;
14798 };
14799 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14800 return;
14801 };
14802 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14803 return;
14804 };
14805 self.change_selections(
14806 SelectionEffects::default().nav_history(true),
14807 window,
14808 cx,
14809 |s| s.select_anchor_ranges([start..end]),
14810 );
14811 }
14812
14813 pub fn go_to_diagnostic(
14814 &mut self,
14815 _: &GoToDiagnostic,
14816 window: &mut Window,
14817 cx: &mut Context<Self>,
14818 ) {
14819 if !self.diagnostics_enabled() {
14820 return;
14821 }
14822 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14823 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14824 }
14825
14826 pub fn go_to_prev_diagnostic(
14827 &mut self,
14828 _: &GoToPreviousDiagnostic,
14829 window: &mut Window,
14830 cx: &mut Context<Self>,
14831 ) {
14832 if !self.diagnostics_enabled() {
14833 return;
14834 }
14835 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14836 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14837 }
14838
14839 pub fn go_to_diagnostic_impl(
14840 &mut self,
14841 direction: Direction,
14842 window: &mut Window,
14843 cx: &mut Context<Self>,
14844 ) {
14845 let buffer = self.buffer.read(cx).snapshot(cx);
14846 let selection = self.selections.newest::<usize>(cx);
14847
14848 let mut active_group_id = None;
14849 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14850 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14851 active_group_id = Some(active_group.group_id);
14852 }
14853 }
14854
14855 fn filtered(
14856 snapshot: EditorSnapshot,
14857 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14858 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14859 diagnostics
14860 .filter(|entry| entry.range.start != entry.range.end)
14861 .filter(|entry| !entry.diagnostic.is_unnecessary)
14862 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14863 }
14864
14865 let snapshot = self.snapshot(window, cx);
14866 let before = filtered(
14867 snapshot.clone(),
14868 buffer
14869 .diagnostics_in_range(0..selection.start)
14870 .filter(|entry| entry.range.start <= selection.start),
14871 );
14872 let after = filtered(
14873 snapshot,
14874 buffer
14875 .diagnostics_in_range(selection.start..buffer.len())
14876 .filter(|entry| entry.range.start >= selection.start),
14877 );
14878
14879 let mut found: Option<DiagnosticEntry<usize>> = None;
14880 if direction == Direction::Prev {
14881 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14882 {
14883 for diagnostic in prev_diagnostics.into_iter().rev() {
14884 if diagnostic.range.start != selection.start
14885 || active_group_id
14886 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14887 {
14888 found = Some(diagnostic);
14889 break 'outer;
14890 }
14891 }
14892 }
14893 } else {
14894 for diagnostic in after.chain(before) {
14895 if diagnostic.range.start != selection.start
14896 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14897 {
14898 found = Some(diagnostic);
14899 break;
14900 }
14901 }
14902 }
14903 let Some(next_diagnostic) = found else {
14904 return;
14905 };
14906
14907 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14908 return;
14909 };
14910 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14911 s.select_ranges(vec![
14912 next_diagnostic.range.start..next_diagnostic.range.start,
14913 ])
14914 });
14915 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14916 self.refresh_inline_completion(false, true, window, cx);
14917 }
14918
14919 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14920 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14921 let snapshot = self.snapshot(window, cx);
14922 let selection = self.selections.newest::<Point>(cx);
14923 self.go_to_hunk_before_or_after_position(
14924 &snapshot,
14925 selection.head(),
14926 Direction::Next,
14927 window,
14928 cx,
14929 );
14930 }
14931
14932 pub fn go_to_hunk_before_or_after_position(
14933 &mut self,
14934 snapshot: &EditorSnapshot,
14935 position: Point,
14936 direction: Direction,
14937 window: &mut Window,
14938 cx: &mut Context<Editor>,
14939 ) {
14940 let row = if direction == Direction::Next {
14941 self.hunk_after_position(snapshot, position)
14942 .map(|hunk| hunk.row_range.start)
14943 } else {
14944 self.hunk_before_position(snapshot, position)
14945 };
14946
14947 if let Some(row) = row {
14948 let destination = Point::new(row.0, 0);
14949 let autoscroll = Autoscroll::center();
14950
14951 self.unfold_ranges(&[destination..destination], false, false, cx);
14952 self.change_selections(Some(autoscroll), window, cx, |s| {
14953 s.select_ranges([destination..destination]);
14954 });
14955 }
14956 }
14957
14958 fn hunk_after_position(
14959 &mut self,
14960 snapshot: &EditorSnapshot,
14961 position: Point,
14962 ) -> Option<MultiBufferDiffHunk> {
14963 snapshot
14964 .buffer_snapshot
14965 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14966 .find(|hunk| hunk.row_range.start.0 > position.row)
14967 .or_else(|| {
14968 snapshot
14969 .buffer_snapshot
14970 .diff_hunks_in_range(Point::zero()..position)
14971 .find(|hunk| hunk.row_range.end.0 < position.row)
14972 })
14973 }
14974
14975 fn go_to_prev_hunk(
14976 &mut self,
14977 _: &GoToPreviousHunk,
14978 window: &mut Window,
14979 cx: &mut Context<Self>,
14980 ) {
14981 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14982 let snapshot = self.snapshot(window, cx);
14983 let selection = self.selections.newest::<Point>(cx);
14984 self.go_to_hunk_before_or_after_position(
14985 &snapshot,
14986 selection.head(),
14987 Direction::Prev,
14988 window,
14989 cx,
14990 );
14991 }
14992
14993 fn hunk_before_position(
14994 &mut self,
14995 snapshot: &EditorSnapshot,
14996 position: Point,
14997 ) -> Option<MultiBufferRow> {
14998 snapshot
14999 .buffer_snapshot
15000 .diff_hunk_before(position)
15001 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15002 }
15003
15004 fn go_to_next_change(
15005 &mut self,
15006 _: &GoToNextChange,
15007 window: &mut Window,
15008 cx: &mut Context<Self>,
15009 ) {
15010 if let Some(selections) = self
15011 .change_list
15012 .next_change(1, Direction::Next)
15013 .map(|s| s.to_vec())
15014 {
15015 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15016 let map = s.display_map();
15017 s.select_display_ranges(selections.iter().map(|a| {
15018 let point = a.to_display_point(&map);
15019 point..point
15020 }))
15021 })
15022 }
15023 }
15024
15025 fn go_to_previous_change(
15026 &mut self,
15027 _: &GoToPreviousChange,
15028 window: &mut Window,
15029 cx: &mut Context<Self>,
15030 ) {
15031 if let Some(selections) = self
15032 .change_list
15033 .next_change(1, Direction::Prev)
15034 .map(|s| s.to_vec())
15035 {
15036 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15037 let map = s.display_map();
15038 s.select_display_ranges(selections.iter().map(|a| {
15039 let point = a.to_display_point(&map);
15040 point..point
15041 }))
15042 })
15043 }
15044 }
15045
15046 fn go_to_line<T: 'static>(
15047 &mut self,
15048 position: Anchor,
15049 highlight_color: Option<Hsla>,
15050 window: &mut Window,
15051 cx: &mut Context<Self>,
15052 ) {
15053 let snapshot = self.snapshot(window, cx).display_snapshot;
15054 let position = position.to_point(&snapshot.buffer_snapshot);
15055 let start = snapshot
15056 .buffer_snapshot
15057 .clip_point(Point::new(position.row, 0), Bias::Left);
15058 let end = start + Point::new(1, 0);
15059 let start = snapshot.buffer_snapshot.anchor_before(start);
15060 let end = snapshot.buffer_snapshot.anchor_before(end);
15061
15062 self.highlight_rows::<T>(
15063 start..end,
15064 highlight_color
15065 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15066 Default::default(),
15067 cx,
15068 );
15069
15070 if self.buffer.read(cx).is_singleton() {
15071 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15072 }
15073 }
15074
15075 pub fn go_to_definition(
15076 &mut self,
15077 _: &GoToDefinition,
15078 window: &mut Window,
15079 cx: &mut Context<Self>,
15080 ) -> Task<Result<Navigated>> {
15081 let definition =
15082 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15083 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15084 cx.spawn_in(window, async move |editor, cx| {
15085 if definition.await? == Navigated::Yes {
15086 return Ok(Navigated::Yes);
15087 }
15088 match fallback_strategy {
15089 GoToDefinitionFallback::None => Ok(Navigated::No),
15090 GoToDefinitionFallback::FindAllReferences => {
15091 match editor.update_in(cx, |editor, window, cx| {
15092 editor.find_all_references(&FindAllReferences, window, cx)
15093 })? {
15094 Some(references) => references.await,
15095 None => Ok(Navigated::No),
15096 }
15097 }
15098 }
15099 })
15100 }
15101
15102 pub fn go_to_declaration(
15103 &mut self,
15104 _: &GoToDeclaration,
15105 window: &mut Window,
15106 cx: &mut Context<Self>,
15107 ) -> Task<Result<Navigated>> {
15108 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15109 }
15110
15111 pub fn go_to_declaration_split(
15112 &mut self,
15113 _: &GoToDeclaration,
15114 window: &mut Window,
15115 cx: &mut Context<Self>,
15116 ) -> Task<Result<Navigated>> {
15117 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15118 }
15119
15120 pub fn go_to_implementation(
15121 &mut self,
15122 _: &GoToImplementation,
15123 window: &mut Window,
15124 cx: &mut Context<Self>,
15125 ) -> Task<Result<Navigated>> {
15126 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15127 }
15128
15129 pub fn go_to_implementation_split(
15130 &mut self,
15131 _: &GoToImplementationSplit,
15132 window: &mut Window,
15133 cx: &mut Context<Self>,
15134 ) -> Task<Result<Navigated>> {
15135 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15136 }
15137
15138 pub fn go_to_type_definition(
15139 &mut self,
15140 _: &GoToTypeDefinition,
15141 window: &mut Window,
15142 cx: &mut Context<Self>,
15143 ) -> Task<Result<Navigated>> {
15144 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15145 }
15146
15147 pub fn go_to_definition_split(
15148 &mut self,
15149 _: &GoToDefinitionSplit,
15150 window: &mut Window,
15151 cx: &mut Context<Self>,
15152 ) -> Task<Result<Navigated>> {
15153 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15154 }
15155
15156 pub fn go_to_type_definition_split(
15157 &mut self,
15158 _: &GoToTypeDefinitionSplit,
15159 window: &mut Window,
15160 cx: &mut Context<Self>,
15161 ) -> Task<Result<Navigated>> {
15162 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15163 }
15164
15165 fn go_to_definition_of_kind(
15166 &mut self,
15167 kind: GotoDefinitionKind,
15168 split: bool,
15169 window: &mut Window,
15170 cx: &mut Context<Self>,
15171 ) -> Task<Result<Navigated>> {
15172 let Some(provider) = self.semantics_provider.clone() else {
15173 return Task::ready(Ok(Navigated::No));
15174 };
15175 let head = self.selections.newest::<usize>(cx).head();
15176 let buffer = self.buffer.read(cx);
15177 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
15178 text_anchor
15179 } else {
15180 return Task::ready(Ok(Navigated::No));
15181 };
15182
15183 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15184 return Task::ready(Ok(Navigated::No));
15185 };
15186
15187 cx.spawn_in(window, async move |editor, cx| {
15188 let definitions = definitions.await?;
15189 let navigated = editor
15190 .update_in(cx, |editor, window, cx| {
15191 editor.navigate_to_hover_links(
15192 Some(kind),
15193 definitions
15194 .into_iter()
15195 .filter(|location| {
15196 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15197 })
15198 .map(HoverLink::Text)
15199 .collect::<Vec<_>>(),
15200 split,
15201 window,
15202 cx,
15203 )
15204 })?
15205 .await?;
15206 anyhow::Ok(navigated)
15207 })
15208 }
15209
15210 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15211 let selection = self.selections.newest_anchor();
15212 let head = selection.head();
15213 let tail = selection.tail();
15214
15215 let Some((buffer, start_position)) =
15216 self.buffer.read(cx).text_anchor_for_position(head, cx)
15217 else {
15218 return;
15219 };
15220
15221 let end_position = if head != tail {
15222 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15223 return;
15224 };
15225 Some(pos)
15226 } else {
15227 None
15228 };
15229
15230 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15231 let url = if let Some(end_pos) = end_position {
15232 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15233 } else {
15234 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15235 };
15236
15237 if let Some(url) = url {
15238 editor.update(cx, |_, cx| {
15239 cx.open_url(&url);
15240 })
15241 } else {
15242 Ok(())
15243 }
15244 });
15245
15246 url_finder.detach();
15247 }
15248
15249 pub fn open_selected_filename(
15250 &mut self,
15251 _: &OpenSelectedFilename,
15252 window: &mut Window,
15253 cx: &mut Context<Self>,
15254 ) {
15255 let Some(workspace) = self.workspace() else {
15256 return;
15257 };
15258
15259 let position = self.selections.newest_anchor().head();
15260
15261 let Some((buffer, buffer_position)) =
15262 self.buffer.read(cx).text_anchor_for_position(position, cx)
15263 else {
15264 return;
15265 };
15266
15267 let project = self.project.clone();
15268
15269 cx.spawn_in(window, async move |_, cx| {
15270 let result = find_file(&buffer, project, buffer_position, cx).await;
15271
15272 if let Some((_, path)) = result {
15273 workspace
15274 .update_in(cx, |workspace, window, cx| {
15275 workspace.open_resolved_path(path, window, cx)
15276 })?
15277 .await?;
15278 }
15279 anyhow::Ok(())
15280 })
15281 .detach();
15282 }
15283
15284 pub(crate) fn navigate_to_hover_links(
15285 &mut self,
15286 kind: Option<GotoDefinitionKind>,
15287 mut definitions: Vec<HoverLink>,
15288 split: bool,
15289 window: &mut Window,
15290 cx: &mut Context<Editor>,
15291 ) -> Task<Result<Navigated>> {
15292 // If there is one definition, just open it directly
15293 if definitions.len() == 1 {
15294 let definition = definitions.pop().unwrap();
15295
15296 enum TargetTaskResult {
15297 Location(Option<Location>),
15298 AlreadyNavigated,
15299 }
15300
15301 let target_task = match definition {
15302 HoverLink::Text(link) => {
15303 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
15304 }
15305 HoverLink::InlayHint(lsp_location, server_id) => {
15306 let computation =
15307 self.compute_target_location(lsp_location, server_id, window, cx);
15308 cx.background_spawn(async move {
15309 let location = computation.await?;
15310 Ok(TargetTaskResult::Location(location))
15311 })
15312 }
15313 HoverLink::Url(url) => {
15314 cx.open_url(&url);
15315 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
15316 }
15317 HoverLink::File(path) => {
15318 if let Some(workspace) = self.workspace() {
15319 cx.spawn_in(window, async move |_, cx| {
15320 workspace
15321 .update_in(cx, |workspace, window, cx| {
15322 workspace.open_resolved_path(path, window, cx)
15323 })?
15324 .await
15325 .map(|_| TargetTaskResult::AlreadyNavigated)
15326 })
15327 } else {
15328 Task::ready(Ok(TargetTaskResult::Location(None)))
15329 }
15330 }
15331 };
15332 cx.spawn_in(window, async move |editor, cx| {
15333 let target = match target_task.await.context("target resolution task")? {
15334 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
15335 TargetTaskResult::Location(None) => return Ok(Navigated::No),
15336 TargetTaskResult::Location(Some(target)) => target,
15337 };
15338
15339 editor.update_in(cx, |editor, window, cx| {
15340 let Some(workspace) = editor.workspace() else {
15341 return Navigated::No;
15342 };
15343 let pane = workspace.read(cx).active_pane().clone();
15344
15345 let range = target.range.to_point(target.buffer.read(cx));
15346 let range = editor.range_for_match(&range);
15347 let range = collapse_multiline_range(range);
15348
15349 if !split
15350 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15351 {
15352 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15353 } else {
15354 window.defer(cx, move |window, cx| {
15355 let target_editor: Entity<Self> =
15356 workspace.update(cx, |workspace, cx| {
15357 let pane = if split {
15358 workspace.adjacent_pane(window, cx)
15359 } else {
15360 workspace.active_pane().clone()
15361 };
15362
15363 workspace.open_project_item(
15364 pane,
15365 target.buffer.clone(),
15366 true,
15367 true,
15368 window,
15369 cx,
15370 )
15371 });
15372 target_editor.update(cx, |target_editor, cx| {
15373 // When selecting a definition in a different buffer, disable the nav history
15374 // to avoid creating a history entry at the previous cursor location.
15375 pane.update(cx, |pane, _| pane.disable_history());
15376 target_editor.go_to_singleton_buffer_range(range, window, cx);
15377 pane.update(cx, |pane, _| pane.enable_history());
15378 });
15379 });
15380 }
15381 Navigated::Yes
15382 })
15383 })
15384 } else if !definitions.is_empty() {
15385 cx.spawn_in(window, async move |editor, cx| {
15386 let (title, location_tasks, workspace) = editor
15387 .update_in(cx, |editor, window, cx| {
15388 let tab_kind = match kind {
15389 Some(GotoDefinitionKind::Implementation) => "Implementations",
15390 _ => "Definitions",
15391 };
15392 let title = definitions
15393 .iter()
15394 .find_map(|definition| match definition {
15395 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15396 let buffer = origin.buffer.read(cx);
15397 format!(
15398 "{} for {}",
15399 tab_kind,
15400 buffer
15401 .text_for_range(origin.range.clone())
15402 .collect::<String>()
15403 )
15404 }),
15405 HoverLink::InlayHint(_, _) => None,
15406 HoverLink::Url(_) => None,
15407 HoverLink::File(_) => None,
15408 })
15409 .unwrap_or(tab_kind.to_string());
15410 let location_tasks = definitions
15411 .into_iter()
15412 .map(|definition| match definition {
15413 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15414 HoverLink::InlayHint(lsp_location, server_id) => editor
15415 .compute_target_location(lsp_location, server_id, window, cx),
15416 HoverLink::Url(_) => Task::ready(Ok(None)),
15417 HoverLink::File(_) => Task::ready(Ok(None)),
15418 })
15419 .collect::<Vec<_>>();
15420 (title, location_tasks, editor.workspace().clone())
15421 })
15422 .context("location tasks preparation")?;
15423
15424 let locations: Vec<Location> = future::join_all(location_tasks)
15425 .await
15426 .into_iter()
15427 .filter_map(|location| location.transpose())
15428 .collect::<Result<_>>()
15429 .context("location tasks")?;
15430
15431 if locations.is_empty() {
15432 return Ok(Navigated::No);
15433 }
15434
15435 let Some(workspace) = workspace else {
15436 return Ok(Navigated::No);
15437 };
15438
15439 let opened = workspace
15440 .update_in(cx, |workspace, window, cx| {
15441 Self::open_locations_in_multibuffer(
15442 workspace,
15443 locations,
15444 title,
15445 split,
15446 MultibufferSelectionMode::First,
15447 window,
15448 cx,
15449 )
15450 })
15451 .ok();
15452
15453 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15454 })
15455 } else {
15456 Task::ready(Ok(Navigated::No))
15457 }
15458 }
15459
15460 fn compute_target_location(
15461 &self,
15462 lsp_location: lsp::Location,
15463 server_id: LanguageServerId,
15464 window: &mut Window,
15465 cx: &mut Context<Self>,
15466 ) -> Task<anyhow::Result<Option<Location>>> {
15467 let Some(project) = self.project.clone() else {
15468 return Task::ready(Ok(None));
15469 };
15470
15471 cx.spawn_in(window, async move |editor, cx| {
15472 let location_task = editor.update(cx, |_, cx| {
15473 project.update(cx, |project, cx| {
15474 let language_server_name = project
15475 .language_server_statuses(cx)
15476 .find(|(id, _)| server_id == *id)
15477 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15478 language_server_name.map(|language_server_name| {
15479 project.open_local_buffer_via_lsp(
15480 lsp_location.uri.clone(),
15481 server_id,
15482 language_server_name,
15483 cx,
15484 )
15485 })
15486 })
15487 })?;
15488 let location = match location_task {
15489 Some(task) => Some({
15490 let target_buffer_handle = task.await.context("open local buffer")?;
15491 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15492 let target_start = target_buffer
15493 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15494 let target_end = target_buffer
15495 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15496 target_buffer.anchor_after(target_start)
15497 ..target_buffer.anchor_before(target_end)
15498 })?;
15499 Location {
15500 buffer: target_buffer_handle,
15501 range,
15502 }
15503 }),
15504 None => None,
15505 };
15506 Ok(location)
15507 })
15508 }
15509
15510 pub fn find_all_references(
15511 &mut self,
15512 _: &FindAllReferences,
15513 window: &mut Window,
15514 cx: &mut Context<Self>,
15515 ) -> Option<Task<Result<Navigated>>> {
15516 let selection = self.selections.newest::<usize>(cx);
15517 let multi_buffer = self.buffer.read(cx);
15518 let head = selection.head();
15519
15520 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15521 let head_anchor = multi_buffer_snapshot.anchor_at(
15522 head,
15523 if head < selection.tail() {
15524 Bias::Right
15525 } else {
15526 Bias::Left
15527 },
15528 );
15529
15530 match self
15531 .find_all_references_task_sources
15532 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15533 {
15534 Ok(_) => {
15535 log::info!(
15536 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15537 );
15538 return None;
15539 }
15540 Err(i) => {
15541 self.find_all_references_task_sources.insert(i, head_anchor);
15542 }
15543 }
15544
15545 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15546 let workspace = self.workspace()?;
15547 let project = workspace.read(cx).project().clone();
15548 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15549 Some(cx.spawn_in(window, async move |editor, cx| {
15550 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15551 if let Ok(i) = editor
15552 .find_all_references_task_sources
15553 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15554 {
15555 editor.find_all_references_task_sources.remove(i);
15556 }
15557 });
15558
15559 let locations = references.await?;
15560 if locations.is_empty() {
15561 return anyhow::Ok(Navigated::No);
15562 }
15563
15564 workspace.update_in(cx, |workspace, window, cx| {
15565 let title = locations
15566 .first()
15567 .as_ref()
15568 .map(|location| {
15569 let buffer = location.buffer.read(cx);
15570 format!(
15571 "References to `{}`",
15572 buffer
15573 .text_for_range(location.range.clone())
15574 .collect::<String>()
15575 )
15576 })
15577 .unwrap();
15578 Self::open_locations_in_multibuffer(
15579 workspace,
15580 locations,
15581 title,
15582 false,
15583 MultibufferSelectionMode::First,
15584 window,
15585 cx,
15586 );
15587 Navigated::Yes
15588 })
15589 }))
15590 }
15591
15592 /// Opens a multibuffer with the given project locations in it
15593 pub fn open_locations_in_multibuffer(
15594 workspace: &mut Workspace,
15595 mut locations: Vec<Location>,
15596 title: String,
15597 split: bool,
15598 multibuffer_selection_mode: MultibufferSelectionMode,
15599 window: &mut Window,
15600 cx: &mut Context<Workspace>,
15601 ) {
15602 if locations.is_empty() {
15603 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
15604 return;
15605 }
15606
15607 // If there are multiple definitions, open them in a multibuffer
15608 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15609 let mut locations = locations.into_iter().peekable();
15610 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15611 let capability = workspace.project().read(cx).capability();
15612
15613 let excerpt_buffer = cx.new(|cx| {
15614 let mut multibuffer = MultiBuffer::new(capability);
15615 while let Some(location) = locations.next() {
15616 let buffer = location.buffer.read(cx);
15617 let mut ranges_for_buffer = Vec::new();
15618 let range = location.range.to_point(buffer);
15619 ranges_for_buffer.push(range.clone());
15620
15621 while let Some(next_location) = locations.peek() {
15622 if next_location.buffer == location.buffer {
15623 ranges_for_buffer.push(next_location.range.to_point(buffer));
15624 locations.next();
15625 } else {
15626 break;
15627 }
15628 }
15629
15630 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15631 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15632 PathKey::for_buffer(&location.buffer, cx),
15633 location.buffer.clone(),
15634 ranges_for_buffer,
15635 DEFAULT_MULTIBUFFER_CONTEXT,
15636 cx,
15637 );
15638 ranges.extend(new_ranges)
15639 }
15640
15641 multibuffer.with_title(title)
15642 });
15643
15644 let editor = cx.new(|cx| {
15645 Editor::for_multibuffer(
15646 excerpt_buffer,
15647 Some(workspace.project().clone()),
15648 window,
15649 cx,
15650 )
15651 });
15652 editor.update(cx, |editor, cx| {
15653 match multibuffer_selection_mode {
15654 MultibufferSelectionMode::First => {
15655 if let Some(first_range) = ranges.first() {
15656 editor.change_selections(None, window, cx, |selections| {
15657 selections.clear_disjoint();
15658 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
15659 });
15660 }
15661 editor.highlight_background::<Self>(
15662 &ranges,
15663 |theme| theme.colors().editor_highlighted_line_background,
15664 cx,
15665 );
15666 }
15667 MultibufferSelectionMode::All => {
15668 editor.change_selections(None, window, cx, |selections| {
15669 selections.clear_disjoint();
15670 selections.select_anchor_ranges(ranges);
15671 });
15672 }
15673 }
15674 editor.register_buffers_with_language_servers(cx);
15675 });
15676
15677 let item = Box::new(editor);
15678 let item_id = item.item_id();
15679
15680 if split {
15681 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15682 } else {
15683 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15684 let (preview_item_id, preview_item_idx) =
15685 workspace.active_pane().read_with(cx, |pane, _| {
15686 (pane.preview_item_id(), pane.preview_item_idx())
15687 });
15688
15689 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15690
15691 if let Some(preview_item_id) = preview_item_id {
15692 workspace.active_pane().update(cx, |pane, cx| {
15693 pane.remove_item(preview_item_id, false, false, window, cx);
15694 });
15695 }
15696 } else {
15697 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15698 }
15699 }
15700 workspace.active_pane().update(cx, |pane, cx| {
15701 pane.set_preview_item_id(Some(item_id), cx);
15702 });
15703 }
15704
15705 pub fn rename(
15706 &mut self,
15707 _: &Rename,
15708 window: &mut Window,
15709 cx: &mut Context<Self>,
15710 ) -> Option<Task<Result<()>>> {
15711 use language::ToOffset as _;
15712
15713 let provider = self.semantics_provider.clone()?;
15714 let selection = self.selections.newest_anchor().clone();
15715 let (cursor_buffer, cursor_buffer_position) = self
15716 .buffer
15717 .read(cx)
15718 .text_anchor_for_position(selection.head(), cx)?;
15719 let (tail_buffer, cursor_buffer_position_end) = self
15720 .buffer
15721 .read(cx)
15722 .text_anchor_for_position(selection.tail(), cx)?;
15723 if tail_buffer != cursor_buffer {
15724 return None;
15725 }
15726
15727 let snapshot = cursor_buffer.read(cx).snapshot();
15728 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15729 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15730 let prepare_rename = provider
15731 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15732 .unwrap_or_else(|| Task::ready(Ok(None)));
15733 drop(snapshot);
15734
15735 Some(cx.spawn_in(window, async move |this, cx| {
15736 let rename_range = if let Some(range) = prepare_rename.await? {
15737 Some(range)
15738 } else {
15739 this.update(cx, |this, cx| {
15740 let buffer = this.buffer.read(cx).snapshot(cx);
15741 let mut buffer_highlights = this
15742 .document_highlights_for_position(selection.head(), &buffer)
15743 .filter(|highlight| {
15744 highlight.start.excerpt_id == selection.head().excerpt_id
15745 && highlight.end.excerpt_id == selection.head().excerpt_id
15746 });
15747 buffer_highlights
15748 .next()
15749 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15750 })?
15751 };
15752 if let Some(rename_range) = rename_range {
15753 this.update_in(cx, |this, window, cx| {
15754 let snapshot = cursor_buffer.read(cx).snapshot();
15755 let rename_buffer_range = rename_range.to_offset(&snapshot);
15756 let cursor_offset_in_rename_range =
15757 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15758 let cursor_offset_in_rename_range_end =
15759 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15760
15761 this.take_rename(false, window, cx);
15762 let buffer = this.buffer.read(cx).read(cx);
15763 let cursor_offset = selection.head().to_offset(&buffer);
15764 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15765 let rename_end = rename_start + rename_buffer_range.len();
15766 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15767 let mut old_highlight_id = None;
15768 let old_name: Arc<str> = buffer
15769 .chunks(rename_start..rename_end, true)
15770 .map(|chunk| {
15771 if old_highlight_id.is_none() {
15772 old_highlight_id = chunk.syntax_highlight_id;
15773 }
15774 chunk.text
15775 })
15776 .collect::<String>()
15777 .into();
15778
15779 drop(buffer);
15780
15781 // Position the selection in the rename editor so that it matches the current selection.
15782 this.show_local_selections = false;
15783 let rename_editor = cx.new(|cx| {
15784 let mut editor = Editor::single_line(window, cx);
15785 editor.buffer.update(cx, |buffer, cx| {
15786 buffer.edit([(0..0, old_name.clone())], None, cx)
15787 });
15788 let rename_selection_range = match cursor_offset_in_rename_range
15789 .cmp(&cursor_offset_in_rename_range_end)
15790 {
15791 Ordering::Equal => {
15792 editor.select_all(&SelectAll, window, cx);
15793 return editor;
15794 }
15795 Ordering::Less => {
15796 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15797 }
15798 Ordering::Greater => {
15799 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15800 }
15801 };
15802 if rename_selection_range.end > old_name.len() {
15803 editor.select_all(&SelectAll, window, cx);
15804 } else {
15805 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15806 s.select_ranges([rename_selection_range]);
15807 });
15808 }
15809 editor
15810 });
15811 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15812 if e == &EditorEvent::Focused {
15813 cx.emit(EditorEvent::FocusedIn)
15814 }
15815 })
15816 .detach();
15817
15818 let write_highlights =
15819 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
15820 let read_highlights =
15821 this.clear_background_highlights::<DocumentHighlightRead>(cx);
15822 let ranges = write_highlights
15823 .iter()
15824 .flat_map(|(_, ranges)| ranges.iter())
15825 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
15826 .cloned()
15827 .collect();
15828
15829 this.highlight_text::<Rename>(
15830 ranges,
15831 HighlightStyle {
15832 fade_out: Some(0.6),
15833 ..Default::default()
15834 },
15835 cx,
15836 );
15837 let rename_focus_handle = rename_editor.focus_handle(cx);
15838 window.focus(&rename_focus_handle);
15839 let block_id = this.insert_blocks(
15840 [BlockProperties {
15841 style: BlockStyle::Flex,
15842 placement: BlockPlacement::Below(range.start),
15843 height: Some(1),
15844 render: Arc::new({
15845 let rename_editor = rename_editor.clone();
15846 move |cx: &mut BlockContext| {
15847 let mut text_style = cx.editor_style.text.clone();
15848 if let Some(highlight_style) = old_highlight_id
15849 .and_then(|h| h.style(&cx.editor_style.syntax))
15850 {
15851 text_style = text_style.highlight(highlight_style);
15852 }
15853 div()
15854 .block_mouse_except_scroll()
15855 .pl(cx.anchor_x)
15856 .child(EditorElement::new(
15857 &rename_editor,
15858 EditorStyle {
15859 background: cx.theme().system().transparent,
15860 local_player: cx.editor_style.local_player,
15861 text: text_style,
15862 scrollbar_width: cx.editor_style.scrollbar_width,
15863 syntax: cx.editor_style.syntax.clone(),
15864 status: cx.editor_style.status.clone(),
15865 inlay_hints_style: HighlightStyle {
15866 font_weight: Some(FontWeight::BOLD),
15867 ..make_inlay_hints_style(cx.app)
15868 },
15869 inline_completion_styles: make_suggestion_styles(
15870 cx.app,
15871 ),
15872 ..EditorStyle::default()
15873 },
15874 ))
15875 .into_any_element()
15876 }
15877 }),
15878 priority: 0,
15879 render_in_minimap: true,
15880 }],
15881 Some(Autoscroll::fit()),
15882 cx,
15883 )[0];
15884 this.pending_rename = Some(RenameState {
15885 range,
15886 old_name,
15887 editor: rename_editor,
15888 block_id,
15889 });
15890 })?;
15891 }
15892
15893 Ok(())
15894 }))
15895 }
15896
15897 pub fn confirm_rename(
15898 &mut self,
15899 _: &ConfirmRename,
15900 window: &mut Window,
15901 cx: &mut Context<Self>,
15902 ) -> Option<Task<Result<()>>> {
15903 let rename = self.take_rename(false, window, cx)?;
15904 let workspace = self.workspace()?.downgrade();
15905 let (buffer, start) = self
15906 .buffer
15907 .read(cx)
15908 .text_anchor_for_position(rename.range.start, cx)?;
15909 let (end_buffer, _) = self
15910 .buffer
15911 .read(cx)
15912 .text_anchor_for_position(rename.range.end, cx)?;
15913 if buffer != end_buffer {
15914 return None;
15915 }
15916
15917 let old_name = rename.old_name;
15918 let new_name = rename.editor.read(cx).text(cx);
15919
15920 let rename = self.semantics_provider.as_ref()?.perform_rename(
15921 &buffer,
15922 start,
15923 new_name.clone(),
15924 cx,
15925 )?;
15926
15927 Some(cx.spawn_in(window, async move |editor, cx| {
15928 let project_transaction = rename.await?;
15929 Self::open_project_transaction(
15930 &editor,
15931 workspace,
15932 project_transaction,
15933 format!("Rename: {} → {}", old_name, new_name),
15934 cx,
15935 )
15936 .await?;
15937
15938 editor.update(cx, |editor, cx| {
15939 editor.refresh_document_highlights(cx);
15940 })?;
15941 Ok(())
15942 }))
15943 }
15944
15945 fn take_rename(
15946 &mut self,
15947 moving_cursor: bool,
15948 window: &mut Window,
15949 cx: &mut Context<Self>,
15950 ) -> Option<RenameState> {
15951 let rename = self.pending_rename.take()?;
15952 if rename.editor.focus_handle(cx).is_focused(window) {
15953 window.focus(&self.focus_handle);
15954 }
15955
15956 self.remove_blocks(
15957 [rename.block_id].into_iter().collect(),
15958 Some(Autoscroll::fit()),
15959 cx,
15960 );
15961 self.clear_highlights::<Rename>(cx);
15962 self.show_local_selections = true;
15963
15964 if moving_cursor {
15965 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15966 editor.selections.newest::<usize>(cx).head()
15967 });
15968
15969 // Update the selection to match the position of the selection inside
15970 // the rename editor.
15971 let snapshot = self.buffer.read(cx).read(cx);
15972 let rename_range = rename.range.to_offset(&snapshot);
15973 let cursor_in_editor = snapshot
15974 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15975 .min(rename_range.end);
15976 drop(snapshot);
15977
15978 self.change_selections(None, window, cx, |s| {
15979 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15980 });
15981 } else {
15982 self.refresh_document_highlights(cx);
15983 }
15984
15985 Some(rename)
15986 }
15987
15988 pub fn pending_rename(&self) -> Option<&RenameState> {
15989 self.pending_rename.as_ref()
15990 }
15991
15992 fn format(
15993 &mut self,
15994 _: &Format,
15995 window: &mut Window,
15996 cx: &mut Context<Self>,
15997 ) -> Option<Task<Result<()>>> {
15998 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15999
16000 let project = match &self.project {
16001 Some(project) => project.clone(),
16002 None => return None,
16003 };
16004
16005 Some(self.perform_format(
16006 project,
16007 FormatTrigger::Manual,
16008 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16009 window,
16010 cx,
16011 ))
16012 }
16013
16014 fn format_selections(
16015 &mut self,
16016 _: &FormatSelections,
16017 window: &mut Window,
16018 cx: &mut Context<Self>,
16019 ) -> Option<Task<Result<()>>> {
16020 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16021
16022 let project = match &self.project {
16023 Some(project) => project.clone(),
16024 None => return None,
16025 };
16026
16027 let ranges = self
16028 .selections
16029 .all_adjusted(cx)
16030 .into_iter()
16031 .map(|selection| selection.range())
16032 .collect_vec();
16033
16034 Some(self.perform_format(
16035 project,
16036 FormatTrigger::Manual,
16037 FormatTarget::Ranges(ranges),
16038 window,
16039 cx,
16040 ))
16041 }
16042
16043 fn perform_format(
16044 &mut self,
16045 project: Entity<Project>,
16046 trigger: FormatTrigger,
16047 target: FormatTarget,
16048 window: &mut Window,
16049 cx: &mut Context<Self>,
16050 ) -> Task<Result<()>> {
16051 let buffer = self.buffer.clone();
16052 let (buffers, target) = match target {
16053 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16054 FormatTarget::Ranges(selection_ranges) => {
16055 let multi_buffer = buffer.read(cx);
16056 let snapshot = multi_buffer.read(cx);
16057 let mut buffers = HashSet::default();
16058 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16059 BTreeMap::new();
16060 for selection_range in selection_ranges {
16061 for (buffer, buffer_range, _) in
16062 snapshot.range_to_buffer_ranges(selection_range)
16063 {
16064 let buffer_id = buffer.remote_id();
16065 let start = buffer.anchor_before(buffer_range.start);
16066 let end = buffer.anchor_after(buffer_range.end);
16067 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16068 buffer_id_to_ranges
16069 .entry(buffer_id)
16070 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16071 .or_insert_with(|| vec![start..end]);
16072 }
16073 }
16074 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16075 }
16076 };
16077
16078 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16079 let selections_prev = transaction_id_prev
16080 .and_then(|transaction_id_prev| {
16081 // default to selections as they were after the last edit, if we have them,
16082 // instead of how they are now.
16083 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16084 // will take you back to where you made the last edit, instead of staying where you scrolled
16085 self.selection_history
16086 .transaction(transaction_id_prev)
16087 .map(|t| t.0.clone())
16088 })
16089 .unwrap_or_else(|| {
16090 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16091 self.selections.disjoint_anchors()
16092 });
16093
16094 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16095 let format = project.update(cx, |project, cx| {
16096 project.format(buffers, target, true, trigger, cx)
16097 });
16098
16099 cx.spawn_in(window, async move |editor, cx| {
16100 let transaction = futures::select_biased! {
16101 transaction = format.log_err().fuse() => transaction,
16102 () = timeout => {
16103 log::warn!("timed out waiting for formatting");
16104 None
16105 }
16106 };
16107
16108 buffer
16109 .update(cx, |buffer, cx| {
16110 if let Some(transaction) = transaction {
16111 if !buffer.is_singleton() {
16112 buffer.push_transaction(&transaction.0, cx);
16113 }
16114 }
16115 cx.notify();
16116 })
16117 .ok();
16118
16119 if let Some(transaction_id_now) =
16120 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16121 {
16122 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16123 if has_new_transaction {
16124 _ = editor.update(cx, |editor, _| {
16125 editor
16126 .selection_history
16127 .insert_transaction(transaction_id_now, selections_prev);
16128 });
16129 }
16130 }
16131
16132 Ok(())
16133 })
16134 }
16135
16136 fn organize_imports(
16137 &mut self,
16138 _: &OrganizeImports,
16139 window: &mut Window,
16140 cx: &mut Context<Self>,
16141 ) -> Option<Task<Result<()>>> {
16142 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16143 let project = match &self.project {
16144 Some(project) => project.clone(),
16145 None => return None,
16146 };
16147 Some(self.perform_code_action_kind(
16148 project,
16149 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16150 window,
16151 cx,
16152 ))
16153 }
16154
16155 fn perform_code_action_kind(
16156 &mut self,
16157 project: Entity<Project>,
16158 kind: CodeActionKind,
16159 window: &mut Window,
16160 cx: &mut Context<Self>,
16161 ) -> Task<Result<()>> {
16162 let buffer = self.buffer.clone();
16163 let buffers = buffer.read(cx).all_buffers();
16164 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16165 let apply_action = project.update(cx, |project, cx| {
16166 project.apply_code_action_kind(buffers, kind, true, cx)
16167 });
16168 cx.spawn_in(window, async move |_, cx| {
16169 let transaction = futures::select_biased! {
16170 () = timeout => {
16171 log::warn!("timed out waiting for executing code action");
16172 None
16173 }
16174 transaction = apply_action.log_err().fuse() => transaction,
16175 };
16176 buffer
16177 .update(cx, |buffer, cx| {
16178 // check if we need this
16179 if let Some(transaction) = transaction {
16180 if !buffer.is_singleton() {
16181 buffer.push_transaction(&transaction.0, cx);
16182 }
16183 }
16184 cx.notify();
16185 })
16186 .ok();
16187 Ok(())
16188 })
16189 }
16190
16191 pub fn restart_language_server(
16192 &mut self,
16193 _: &RestartLanguageServer,
16194 _: &mut Window,
16195 cx: &mut Context<Self>,
16196 ) {
16197 if let Some(project) = self.project.clone() {
16198 self.buffer.update(cx, |multi_buffer, cx| {
16199 project.update(cx, |project, cx| {
16200 project.restart_language_servers_for_buffers(
16201 multi_buffer.all_buffers().into_iter().collect(),
16202 HashSet::default(),
16203 cx,
16204 );
16205 });
16206 })
16207 }
16208 }
16209
16210 pub fn stop_language_server(
16211 &mut self,
16212 _: &StopLanguageServer,
16213 _: &mut Window,
16214 cx: &mut Context<Self>,
16215 ) {
16216 if let Some(project) = self.project.clone() {
16217 self.buffer.update(cx, |multi_buffer, cx| {
16218 project.update(cx, |project, cx| {
16219 project.stop_language_servers_for_buffers(
16220 multi_buffer.all_buffers().into_iter().collect(),
16221 HashSet::default(),
16222 cx,
16223 );
16224 cx.emit(project::Event::RefreshInlayHints);
16225 });
16226 });
16227 }
16228 }
16229
16230 fn cancel_language_server_work(
16231 workspace: &mut Workspace,
16232 _: &actions::CancelLanguageServerWork,
16233 _: &mut Window,
16234 cx: &mut Context<Workspace>,
16235 ) {
16236 let project = workspace.project();
16237 let buffers = workspace
16238 .active_item(cx)
16239 .and_then(|item| item.act_as::<Editor>(cx))
16240 .map_or(HashSet::default(), |editor| {
16241 editor.read(cx).buffer.read(cx).all_buffers()
16242 });
16243 project.update(cx, |project, cx| {
16244 project.cancel_language_server_work_for_buffers(buffers, cx);
16245 });
16246 }
16247
16248 fn show_character_palette(
16249 &mut self,
16250 _: &ShowCharacterPalette,
16251 window: &mut Window,
16252 _: &mut Context<Self>,
16253 ) {
16254 window.show_character_palette();
16255 }
16256
16257 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16258 if !self.diagnostics_enabled() {
16259 return;
16260 }
16261
16262 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16263 let buffer = self.buffer.read(cx).snapshot(cx);
16264 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16265 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16266 let is_valid = buffer
16267 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16268 .any(|entry| {
16269 entry.diagnostic.is_primary
16270 && !entry.range.is_empty()
16271 && entry.range.start == primary_range_start
16272 && entry.diagnostic.message == active_diagnostics.active_message
16273 });
16274
16275 if !is_valid {
16276 self.dismiss_diagnostics(cx);
16277 }
16278 }
16279 }
16280
16281 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16282 match &self.active_diagnostics {
16283 ActiveDiagnostic::Group(group) => Some(group),
16284 _ => None,
16285 }
16286 }
16287
16288 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16289 if !self.diagnostics_enabled() {
16290 return;
16291 }
16292 self.dismiss_diagnostics(cx);
16293 self.active_diagnostics = ActiveDiagnostic::All;
16294 }
16295
16296 fn activate_diagnostics(
16297 &mut self,
16298 buffer_id: BufferId,
16299 diagnostic: DiagnosticEntry<usize>,
16300 window: &mut Window,
16301 cx: &mut Context<Self>,
16302 ) {
16303 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16304 return;
16305 }
16306 self.dismiss_diagnostics(cx);
16307 let snapshot = self.snapshot(window, cx);
16308 let buffer = self.buffer.read(cx).snapshot(cx);
16309 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16310 return;
16311 };
16312
16313 let diagnostic_group = buffer
16314 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16315 .collect::<Vec<_>>();
16316
16317 let blocks =
16318 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16319
16320 let blocks = self.display_map.update(cx, |display_map, cx| {
16321 display_map.insert_blocks(blocks, cx).into_iter().collect()
16322 });
16323 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16324 active_range: buffer.anchor_before(diagnostic.range.start)
16325 ..buffer.anchor_after(diagnostic.range.end),
16326 active_message: diagnostic.diagnostic.message.clone(),
16327 group_id: diagnostic.diagnostic.group_id,
16328 blocks,
16329 });
16330 cx.notify();
16331 }
16332
16333 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16334 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16335 return;
16336 };
16337
16338 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16339 if let ActiveDiagnostic::Group(group) = prev {
16340 self.display_map.update(cx, |display_map, cx| {
16341 display_map.remove_blocks(group.blocks, cx);
16342 });
16343 cx.notify();
16344 }
16345 }
16346
16347 /// Disable inline diagnostics rendering for this editor.
16348 pub fn disable_inline_diagnostics(&mut self) {
16349 self.inline_diagnostics_enabled = false;
16350 self.inline_diagnostics_update = Task::ready(());
16351 self.inline_diagnostics.clear();
16352 }
16353
16354 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16355 self.diagnostics_enabled = false;
16356 self.dismiss_diagnostics(cx);
16357 self.inline_diagnostics_update = Task::ready(());
16358 self.inline_diagnostics.clear();
16359 }
16360
16361 pub fn diagnostics_enabled(&self) -> bool {
16362 self.diagnostics_enabled && self.mode.is_full()
16363 }
16364
16365 pub fn inline_diagnostics_enabled(&self) -> bool {
16366 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16367 }
16368
16369 pub fn show_inline_diagnostics(&self) -> bool {
16370 self.show_inline_diagnostics
16371 }
16372
16373 pub fn toggle_inline_diagnostics(
16374 &mut self,
16375 _: &ToggleInlineDiagnostics,
16376 window: &mut Window,
16377 cx: &mut Context<Editor>,
16378 ) {
16379 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16380 self.refresh_inline_diagnostics(false, window, cx);
16381 }
16382
16383 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16384 self.diagnostics_max_severity = severity;
16385 self.display_map.update(cx, |display_map, _| {
16386 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16387 });
16388 }
16389
16390 pub fn toggle_diagnostics(
16391 &mut self,
16392 _: &ToggleDiagnostics,
16393 window: &mut Window,
16394 cx: &mut Context<Editor>,
16395 ) {
16396 if !self.diagnostics_enabled() {
16397 return;
16398 }
16399
16400 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16401 EditorSettings::get_global(cx)
16402 .diagnostics_max_severity
16403 .filter(|severity| severity != &DiagnosticSeverity::Off)
16404 .unwrap_or(DiagnosticSeverity::Hint)
16405 } else {
16406 DiagnosticSeverity::Off
16407 };
16408 self.set_max_diagnostics_severity(new_severity, cx);
16409 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16410 self.active_diagnostics = ActiveDiagnostic::None;
16411 self.inline_diagnostics_update = Task::ready(());
16412 self.inline_diagnostics.clear();
16413 } else {
16414 self.refresh_inline_diagnostics(false, window, cx);
16415 }
16416
16417 cx.notify();
16418 }
16419
16420 pub fn toggle_minimap(
16421 &mut self,
16422 _: &ToggleMinimap,
16423 window: &mut Window,
16424 cx: &mut Context<Editor>,
16425 ) {
16426 if self.supports_minimap(cx) {
16427 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16428 }
16429 }
16430
16431 fn refresh_inline_diagnostics(
16432 &mut self,
16433 debounce: bool,
16434 window: &mut Window,
16435 cx: &mut Context<Self>,
16436 ) {
16437 let max_severity = ProjectSettings::get_global(cx)
16438 .diagnostics
16439 .inline
16440 .max_severity
16441 .unwrap_or(self.diagnostics_max_severity);
16442
16443 if !self.inline_diagnostics_enabled()
16444 || !self.show_inline_diagnostics
16445 || max_severity == DiagnosticSeverity::Off
16446 {
16447 self.inline_diagnostics_update = Task::ready(());
16448 self.inline_diagnostics.clear();
16449 return;
16450 }
16451
16452 let debounce_ms = ProjectSettings::get_global(cx)
16453 .diagnostics
16454 .inline
16455 .update_debounce_ms;
16456 let debounce = if debounce && debounce_ms > 0 {
16457 Some(Duration::from_millis(debounce_ms))
16458 } else {
16459 None
16460 };
16461 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16462 if let Some(debounce) = debounce {
16463 cx.background_executor().timer(debounce).await;
16464 }
16465 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16466 editor
16467 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16468 .ok()
16469 }) else {
16470 return;
16471 };
16472
16473 let new_inline_diagnostics = cx
16474 .background_spawn(async move {
16475 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16476 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16477 let message = diagnostic_entry
16478 .diagnostic
16479 .message
16480 .split_once('\n')
16481 .map(|(line, _)| line)
16482 .map(SharedString::new)
16483 .unwrap_or_else(|| {
16484 SharedString::from(diagnostic_entry.diagnostic.message)
16485 });
16486 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16487 let (Ok(i) | Err(i)) = inline_diagnostics
16488 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16489 inline_diagnostics.insert(
16490 i,
16491 (
16492 start_anchor,
16493 InlineDiagnostic {
16494 message,
16495 group_id: diagnostic_entry.diagnostic.group_id,
16496 start: diagnostic_entry.range.start.to_point(&snapshot),
16497 is_primary: diagnostic_entry.diagnostic.is_primary,
16498 severity: diagnostic_entry.diagnostic.severity,
16499 },
16500 ),
16501 );
16502 }
16503 inline_diagnostics
16504 })
16505 .await;
16506
16507 editor
16508 .update(cx, |editor, cx| {
16509 editor.inline_diagnostics = new_inline_diagnostics;
16510 cx.notify();
16511 })
16512 .ok();
16513 });
16514 }
16515
16516 fn pull_diagnostics(
16517 &mut self,
16518 buffer_id: Option<BufferId>,
16519 window: &Window,
16520 cx: &mut Context<Self>,
16521 ) -> Option<()> {
16522 if !self.mode().is_full() {
16523 return None;
16524 }
16525 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16526 .diagnostics
16527 .lsp_pull_diagnostics;
16528 if !pull_diagnostics_settings.enabled {
16529 return None;
16530 }
16531 let project = self.project.as_ref()?.downgrade();
16532 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16533 let mut buffers = self.buffer.read(cx).all_buffers();
16534 if let Some(buffer_id) = buffer_id {
16535 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16536 }
16537
16538 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16539 cx.background_executor().timer(debounce).await;
16540
16541 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16542 buffers
16543 .into_iter()
16544 .filter_map(|buffer| {
16545 project
16546 .update(cx, |project, cx| {
16547 project.lsp_store().update(cx, |lsp_store, cx| {
16548 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
16549 })
16550 })
16551 .ok()
16552 })
16553 .collect::<FuturesUnordered<_>>()
16554 }) else {
16555 return;
16556 };
16557
16558 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16559 match pull_task {
16560 Ok(()) => {
16561 if editor
16562 .update_in(cx, |editor, window, cx| {
16563 editor.update_diagnostics_state(window, cx);
16564 })
16565 .is_err()
16566 {
16567 return;
16568 }
16569 }
16570 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16571 }
16572 }
16573 });
16574
16575 Some(())
16576 }
16577
16578 pub fn set_selections_from_remote(
16579 &mut self,
16580 selections: Vec<Selection<Anchor>>,
16581 pending_selection: Option<Selection<Anchor>>,
16582 window: &mut Window,
16583 cx: &mut Context<Self>,
16584 ) {
16585 let old_cursor_position = self.selections.newest_anchor().head();
16586 self.selections.change_with(cx, |s| {
16587 s.select_anchors(selections);
16588 if let Some(pending_selection) = pending_selection {
16589 s.set_pending(pending_selection, SelectMode::Character);
16590 } else {
16591 s.clear_pending();
16592 }
16593 });
16594 self.selections_did_change(
16595 false,
16596 &old_cursor_position,
16597 SelectionEffects::default(),
16598 window,
16599 cx,
16600 );
16601 }
16602
16603 pub fn transact(
16604 &mut self,
16605 window: &mut Window,
16606 cx: &mut Context<Self>,
16607 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16608 ) -> Option<TransactionId> {
16609 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16610 this.start_transaction_at(Instant::now(), window, cx);
16611 update(this, window, cx);
16612 this.end_transaction_at(Instant::now(), cx)
16613 })
16614 }
16615
16616 pub fn start_transaction_at(
16617 &mut self,
16618 now: Instant,
16619 window: &mut Window,
16620 cx: &mut Context<Self>,
16621 ) {
16622 self.end_selection(window, cx);
16623 if let Some(tx_id) = self
16624 .buffer
16625 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16626 {
16627 self.selection_history
16628 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16629 cx.emit(EditorEvent::TransactionBegun {
16630 transaction_id: tx_id,
16631 })
16632 }
16633 }
16634
16635 pub fn end_transaction_at(
16636 &mut self,
16637 now: Instant,
16638 cx: &mut Context<Self>,
16639 ) -> Option<TransactionId> {
16640 if let Some(transaction_id) = self
16641 .buffer
16642 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16643 {
16644 if let Some((_, end_selections)) =
16645 self.selection_history.transaction_mut(transaction_id)
16646 {
16647 *end_selections = Some(self.selections.disjoint_anchors());
16648 } else {
16649 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16650 }
16651
16652 cx.emit(EditorEvent::Edited { transaction_id });
16653 Some(transaction_id)
16654 } else {
16655 None
16656 }
16657 }
16658
16659 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16660 if self.selection_mark_mode {
16661 self.change_selections(None, window, cx, |s| {
16662 s.move_with(|_, sel| {
16663 sel.collapse_to(sel.head(), SelectionGoal::None);
16664 });
16665 })
16666 }
16667 self.selection_mark_mode = true;
16668 cx.notify();
16669 }
16670
16671 pub fn swap_selection_ends(
16672 &mut self,
16673 _: &actions::SwapSelectionEnds,
16674 window: &mut Window,
16675 cx: &mut Context<Self>,
16676 ) {
16677 self.change_selections(None, window, cx, |s| {
16678 s.move_with(|_, sel| {
16679 if sel.start != sel.end {
16680 sel.reversed = !sel.reversed
16681 }
16682 });
16683 });
16684 self.request_autoscroll(Autoscroll::newest(), cx);
16685 cx.notify();
16686 }
16687
16688 pub fn toggle_fold(
16689 &mut self,
16690 _: &actions::ToggleFold,
16691 window: &mut Window,
16692 cx: &mut Context<Self>,
16693 ) {
16694 if self.is_singleton(cx) {
16695 let selection = self.selections.newest::<Point>(cx);
16696
16697 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16698 let range = if selection.is_empty() {
16699 let point = selection.head().to_display_point(&display_map);
16700 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16701 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16702 .to_point(&display_map);
16703 start..end
16704 } else {
16705 selection.range()
16706 };
16707 if display_map.folds_in_range(range).next().is_some() {
16708 self.unfold_lines(&Default::default(), window, cx)
16709 } else {
16710 self.fold(&Default::default(), window, cx)
16711 }
16712 } else {
16713 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16714 let buffer_ids: HashSet<_> = self
16715 .selections
16716 .disjoint_anchor_ranges()
16717 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16718 .collect();
16719
16720 let should_unfold = buffer_ids
16721 .iter()
16722 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16723
16724 for buffer_id in buffer_ids {
16725 if should_unfold {
16726 self.unfold_buffer(buffer_id, cx);
16727 } else {
16728 self.fold_buffer(buffer_id, cx);
16729 }
16730 }
16731 }
16732 }
16733
16734 pub fn toggle_fold_recursive(
16735 &mut self,
16736 _: &actions::ToggleFoldRecursive,
16737 window: &mut Window,
16738 cx: &mut Context<Self>,
16739 ) {
16740 let selection = self.selections.newest::<Point>(cx);
16741
16742 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16743 let range = if selection.is_empty() {
16744 let point = selection.head().to_display_point(&display_map);
16745 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16746 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16747 .to_point(&display_map);
16748 start..end
16749 } else {
16750 selection.range()
16751 };
16752 if display_map.folds_in_range(range).next().is_some() {
16753 self.unfold_recursive(&Default::default(), window, cx)
16754 } else {
16755 self.fold_recursive(&Default::default(), window, cx)
16756 }
16757 }
16758
16759 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16760 if self.is_singleton(cx) {
16761 let mut to_fold = Vec::new();
16762 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16763 let selections = self.selections.all_adjusted(cx);
16764
16765 for selection in selections {
16766 let range = selection.range().sorted();
16767 let buffer_start_row = range.start.row;
16768
16769 if range.start.row != range.end.row {
16770 let mut found = false;
16771 let mut row = range.start.row;
16772 while row <= range.end.row {
16773 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16774 {
16775 found = true;
16776 row = crease.range().end.row + 1;
16777 to_fold.push(crease);
16778 } else {
16779 row += 1
16780 }
16781 }
16782 if found {
16783 continue;
16784 }
16785 }
16786
16787 for row in (0..=range.start.row).rev() {
16788 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16789 if crease.range().end.row >= buffer_start_row {
16790 to_fold.push(crease);
16791 if row <= range.start.row {
16792 break;
16793 }
16794 }
16795 }
16796 }
16797 }
16798
16799 self.fold_creases(to_fold, true, window, cx);
16800 } else {
16801 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16802 let buffer_ids = self
16803 .selections
16804 .disjoint_anchor_ranges()
16805 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16806 .collect::<HashSet<_>>();
16807 for buffer_id in buffer_ids {
16808 self.fold_buffer(buffer_id, cx);
16809 }
16810 }
16811 }
16812
16813 fn fold_at_level(
16814 &mut self,
16815 fold_at: &FoldAtLevel,
16816 window: &mut Window,
16817 cx: &mut Context<Self>,
16818 ) {
16819 if !self.buffer.read(cx).is_singleton() {
16820 return;
16821 }
16822
16823 let fold_at_level = fold_at.0;
16824 let snapshot = self.buffer.read(cx).snapshot(cx);
16825 let mut to_fold = Vec::new();
16826 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16827
16828 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16829 while start_row < end_row {
16830 match self
16831 .snapshot(window, cx)
16832 .crease_for_buffer_row(MultiBufferRow(start_row))
16833 {
16834 Some(crease) => {
16835 let nested_start_row = crease.range().start.row + 1;
16836 let nested_end_row = crease.range().end.row;
16837
16838 if current_level < fold_at_level {
16839 stack.push((nested_start_row, nested_end_row, current_level + 1));
16840 } else if current_level == fold_at_level {
16841 to_fold.push(crease);
16842 }
16843
16844 start_row = nested_end_row + 1;
16845 }
16846 None => start_row += 1,
16847 }
16848 }
16849 }
16850
16851 self.fold_creases(to_fold, true, window, cx);
16852 }
16853
16854 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16855 if self.buffer.read(cx).is_singleton() {
16856 let mut fold_ranges = Vec::new();
16857 let snapshot = self.buffer.read(cx).snapshot(cx);
16858
16859 for row in 0..snapshot.max_row().0 {
16860 if let Some(foldable_range) = self
16861 .snapshot(window, cx)
16862 .crease_for_buffer_row(MultiBufferRow(row))
16863 {
16864 fold_ranges.push(foldable_range);
16865 }
16866 }
16867
16868 self.fold_creases(fold_ranges, true, window, cx);
16869 } else {
16870 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16871 editor
16872 .update_in(cx, |editor, _, cx| {
16873 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16874 editor.fold_buffer(buffer_id, cx);
16875 }
16876 })
16877 .ok();
16878 });
16879 }
16880 }
16881
16882 pub fn fold_function_bodies(
16883 &mut self,
16884 _: &actions::FoldFunctionBodies,
16885 window: &mut Window,
16886 cx: &mut Context<Self>,
16887 ) {
16888 let snapshot = self.buffer.read(cx).snapshot(cx);
16889
16890 let ranges = snapshot
16891 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16892 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16893 .collect::<Vec<_>>();
16894
16895 let creases = ranges
16896 .into_iter()
16897 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16898 .collect();
16899
16900 self.fold_creases(creases, true, window, cx);
16901 }
16902
16903 pub fn fold_recursive(
16904 &mut self,
16905 _: &actions::FoldRecursive,
16906 window: &mut Window,
16907 cx: &mut Context<Self>,
16908 ) {
16909 let mut to_fold = Vec::new();
16910 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16911 let selections = self.selections.all_adjusted(cx);
16912
16913 for selection in selections {
16914 let range = selection.range().sorted();
16915 let buffer_start_row = range.start.row;
16916
16917 if range.start.row != range.end.row {
16918 let mut found = false;
16919 for row in range.start.row..=range.end.row {
16920 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16921 found = true;
16922 to_fold.push(crease);
16923 }
16924 }
16925 if found {
16926 continue;
16927 }
16928 }
16929
16930 for row in (0..=range.start.row).rev() {
16931 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16932 if crease.range().end.row >= buffer_start_row {
16933 to_fold.push(crease);
16934 } else {
16935 break;
16936 }
16937 }
16938 }
16939 }
16940
16941 self.fold_creases(to_fold, true, window, cx);
16942 }
16943
16944 pub fn fold_at(
16945 &mut self,
16946 buffer_row: MultiBufferRow,
16947 window: &mut Window,
16948 cx: &mut Context<Self>,
16949 ) {
16950 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16951
16952 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16953 let autoscroll = self
16954 .selections
16955 .all::<Point>(cx)
16956 .iter()
16957 .any(|selection| crease.range().overlaps(&selection.range()));
16958
16959 self.fold_creases(vec![crease], autoscroll, window, cx);
16960 }
16961 }
16962
16963 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16964 if self.is_singleton(cx) {
16965 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16966 let buffer = &display_map.buffer_snapshot;
16967 let selections = self.selections.all::<Point>(cx);
16968 let ranges = selections
16969 .iter()
16970 .map(|s| {
16971 let range = s.display_range(&display_map).sorted();
16972 let mut start = range.start.to_point(&display_map);
16973 let mut end = range.end.to_point(&display_map);
16974 start.column = 0;
16975 end.column = buffer.line_len(MultiBufferRow(end.row));
16976 start..end
16977 })
16978 .collect::<Vec<_>>();
16979
16980 self.unfold_ranges(&ranges, true, true, cx);
16981 } else {
16982 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16983 let buffer_ids = self
16984 .selections
16985 .disjoint_anchor_ranges()
16986 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16987 .collect::<HashSet<_>>();
16988 for buffer_id in buffer_ids {
16989 self.unfold_buffer(buffer_id, cx);
16990 }
16991 }
16992 }
16993
16994 pub fn unfold_recursive(
16995 &mut self,
16996 _: &UnfoldRecursive,
16997 _window: &mut Window,
16998 cx: &mut Context<Self>,
16999 ) {
17000 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17001 let selections = self.selections.all::<Point>(cx);
17002 let ranges = selections
17003 .iter()
17004 .map(|s| {
17005 let mut range = s.display_range(&display_map).sorted();
17006 *range.start.column_mut() = 0;
17007 *range.end.column_mut() = display_map.line_len(range.end.row());
17008 let start = range.start.to_point(&display_map);
17009 let end = range.end.to_point(&display_map);
17010 start..end
17011 })
17012 .collect::<Vec<_>>();
17013
17014 self.unfold_ranges(&ranges, true, true, cx);
17015 }
17016
17017 pub fn unfold_at(
17018 &mut self,
17019 buffer_row: MultiBufferRow,
17020 _window: &mut Window,
17021 cx: &mut Context<Self>,
17022 ) {
17023 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17024
17025 let intersection_range = Point::new(buffer_row.0, 0)
17026 ..Point::new(
17027 buffer_row.0,
17028 display_map.buffer_snapshot.line_len(buffer_row),
17029 );
17030
17031 let autoscroll = self
17032 .selections
17033 .all::<Point>(cx)
17034 .iter()
17035 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17036
17037 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17038 }
17039
17040 pub fn unfold_all(
17041 &mut self,
17042 _: &actions::UnfoldAll,
17043 _window: &mut Window,
17044 cx: &mut Context<Self>,
17045 ) {
17046 if self.buffer.read(cx).is_singleton() {
17047 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17048 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17049 } else {
17050 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17051 editor
17052 .update(cx, |editor, cx| {
17053 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17054 editor.unfold_buffer(buffer_id, cx);
17055 }
17056 })
17057 .ok();
17058 });
17059 }
17060 }
17061
17062 pub fn fold_selected_ranges(
17063 &mut self,
17064 _: &FoldSelectedRanges,
17065 window: &mut Window,
17066 cx: &mut Context<Self>,
17067 ) {
17068 let selections = self.selections.all_adjusted(cx);
17069 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17070 let ranges = selections
17071 .into_iter()
17072 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17073 .collect::<Vec<_>>();
17074 self.fold_creases(ranges, true, window, cx);
17075 }
17076
17077 pub fn fold_ranges<T: ToOffset + Clone>(
17078 &mut self,
17079 ranges: Vec<Range<T>>,
17080 auto_scroll: bool,
17081 window: &mut Window,
17082 cx: &mut Context<Self>,
17083 ) {
17084 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17085 let ranges = ranges
17086 .into_iter()
17087 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17088 .collect::<Vec<_>>();
17089 self.fold_creases(ranges, auto_scroll, window, cx);
17090 }
17091
17092 pub fn fold_creases<T: ToOffset + Clone>(
17093 &mut self,
17094 creases: Vec<Crease<T>>,
17095 auto_scroll: bool,
17096 _window: &mut Window,
17097 cx: &mut Context<Self>,
17098 ) {
17099 if creases.is_empty() {
17100 return;
17101 }
17102
17103 let mut buffers_affected = HashSet::default();
17104 let multi_buffer = self.buffer().read(cx);
17105 for crease in &creases {
17106 if let Some((_, buffer, _)) =
17107 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
17108 {
17109 buffers_affected.insert(buffer.read(cx).remote_id());
17110 };
17111 }
17112
17113 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17114
17115 if auto_scroll {
17116 self.request_autoscroll(Autoscroll::fit(), cx);
17117 }
17118
17119 cx.notify();
17120
17121 self.scrollbar_marker_state.dirty = true;
17122 self.folds_did_change(cx);
17123 }
17124
17125 /// Removes any folds whose ranges intersect any of the given ranges.
17126 pub fn unfold_ranges<T: ToOffset + Clone>(
17127 &mut self,
17128 ranges: &[Range<T>],
17129 inclusive: bool,
17130 auto_scroll: bool,
17131 cx: &mut Context<Self>,
17132 ) {
17133 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17134 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17135 });
17136 self.folds_did_change(cx);
17137 }
17138
17139 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17140 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17141 return;
17142 }
17143 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17144 self.display_map.update(cx, |display_map, cx| {
17145 display_map.fold_buffers([buffer_id], cx)
17146 });
17147 cx.emit(EditorEvent::BufferFoldToggled {
17148 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17149 folded: true,
17150 });
17151 cx.notify();
17152 }
17153
17154 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17155 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17156 return;
17157 }
17158 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17159 self.display_map.update(cx, |display_map, cx| {
17160 display_map.unfold_buffers([buffer_id], cx);
17161 });
17162 cx.emit(EditorEvent::BufferFoldToggled {
17163 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17164 folded: false,
17165 });
17166 cx.notify();
17167 }
17168
17169 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17170 self.display_map.read(cx).is_buffer_folded(buffer)
17171 }
17172
17173 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17174 self.display_map.read(cx).folded_buffers()
17175 }
17176
17177 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17178 self.display_map.update(cx, |display_map, cx| {
17179 display_map.disable_header_for_buffer(buffer_id, cx);
17180 });
17181 cx.notify();
17182 }
17183
17184 /// Removes any folds with the given ranges.
17185 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17186 &mut self,
17187 ranges: &[Range<T>],
17188 type_id: TypeId,
17189 auto_scroll: bool,
17190 cx: &mut Context<Self>,
17191 ) {
17192 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17193 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17194 });
17195 self.folds_did_change(cx);
17196 }
17197
17198 fn remove_folds_with<T: ToOffset + Clone>(
17199 &mut self,
17200 ranges: &[Range<T>],
17201 auto_scroll: bool,
17202 cx: &mut Context<Self>,
17203 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17204 ) {
17205 if ranges.is_empty() {
17206 return;
17207 }
17208
17209 let mut buffers_affected = HashSet::default();
17210 let multi_buffer = self.buffer().read(cx);
17211 for range in ranges {
17212 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17213 buffers_affected.insert(buffer.read(cx).remote_id());
17214 };
17215 }
17216
17217 self.display_map.update(cx, update);
17218
17219 if auto_scroll {
17220 self.request_autoscroll(Autoscroll::fit(), cx);
17221 }
17222
17223 cx.notify();
17224 self.scrollbar_marker_state.dirty = true;
17225 self.active_indent_guides_state.dirty = true;
17226 }
17227
17228 pub fn update_fold_widths(
17229 &mut self,
17230 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
17231 cx: &mut Context<Self>,
17232 ) -> bool {
17233 self.display_map
17234 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17235 }
17236
17237 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17238 self.display_map.read(cx).fold_placeholder.clone()
17239 }
17240
17241 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17242 self.buffer.update(cx, |buffer, cx| {
17243 buffer.set_all_diff_hunks_expanded(cx);
17244 });
17245 }
17246
17247 pub fn expand_all_diff_hunks(
17248 &mut self,
17249 _: &ExpandAllDiffHunks,
17250 _window: &mut Window,
17251 cx: &mut Context<Self>,
17252 ) {
17253 self.buffer.update(cx, |buffer, cx| {
17254 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17255 });
17256 }
17257
17258 pub fn toggle_selected_diff_hunks(
17259 &mut self,
17260 _: &ToggleSelectedDiffHunks,
17261 _window: &mut Window,
17262 cx: &mut Context<Self>,
17263 ) {
17264 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17265 self.toggle_diff_hunks_in_ranges(ranges, cx);
17266 }
17267
17268 pub fn diff_hunks_in_ranges<'a>(
17269 &'a self,
17270 ranges: &'a [Range<Anchor>],
17271 buffer: &'a MultiBufferSnapshot,
17272 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17273 ranges.iter().flat_map(move |range| {
17274 let end_excerpt_id = range.end.excerpt_id;
17275 let range = range.to_point(buffer);
17276 let mut peek_end = range.end;
17277 if range.end.row < buffer.max_row().0 {
17278 peek_end = Point::new(range.end.row + 1, 0);
17279 }
17280 buffer
17281 .diff_hunks_in_range(range.start..peek_end)
17282 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17283 })
17284 }
17285
17286 pub fn has_stageable_diff_hunks_in_ranges(
17287 &self,
17288 ranges: &[Range<Anchor>],
17289 snapshot: &MultiBufferSnapshot,
17290 ) -> bool {
17291 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17292 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17293 }
17294
17295 pub fn toggle_staged_selected_diff_hunks(
17296 &mut self,
17297 _: &::git::ToggleStaged,
17298 _: &mut Window,
17299 cx: &mut Context<Self>,
17300 ) {
17301 let snapshot = self.buffer.read(cx).snapshot(cx);
17302 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17303 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17304 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17305 }
17306
17307 pub fn set_render_diff_hunk_controls(
17308 &mut self,
17309 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17310 cx: &mut Context<Self>,
17311 ) {
17312 self.render_diff_hunk_controls = render_diff_hunk_controls;
17313 cx.notify();
17314 }
17315
17316 pub fn stage_and_next(
17317 &mut self,
17318 _: &::git::StageAndNext,
17319 window: &mut Window,
17320 cx: &mut Context<Self>,
17321 ) {
17322 self.do_stage_or_unstage_and_next(true, window, cx);
17323 }
17324
17325 pub fn unstage_and_next(
17326 &mut self,
17327 _: &::git::UnstageAndNext,
17328 window: &mut Window,
17329 cx: &mut Context<Self>,
17330 ) {
17331 self.do_stage_or_unstage_and_next(false, window, cx);
17332 }
17333
17334 pub fn stage_or_unstage_diff_hunks(
17335 &mut self,
17336 stage: bool,
17337 ranges: Vec<Range<Anchor>>,
17338 cx: &mut Context<Self>,
17339 ) {
17340 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17341 cx.spawn(async move |this, cx| {
17342 task.await?;
17343 this.update(cx, |this, cx| {
17344 let snapshot = this.buffer.read(cx).snapshot(cx);
17345 let chunk_by = this
17346 .diff_hunks_in_ranges(&ranges, &snapshot)
17347 .chunk_by(|hunk| hunk.buffer_id);
17348 for (buffer_id, hunks) in &chunk_by {
17349 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17350 }
17351 })
17352 })
17353 .detach_and_log_err(cx);
17354 }
17355
17356 fn save_buffers_for_ranges_if_needed(
17357 &mut self,
17358 ranges: &[Range<Anchor>],
17359 cx: &mut Context<Editor>,
17360 ) -> Task<Result<()>> {
17361 let multibuffer = self.buffer.read(cx);
17362 let snapshot = multibuffer.read(cx);
17363 let buffer_ids: HashSet<_> = ranges
17364 .iter()
17365 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17366 .collect();
17367 drop(snapshot);
17368
17369 let mut buffers = HashSet::default();
17370 for buffer_id in buffer_ids {
17371 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17372 let buffer = buffer_entity.read(cx);
17373 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17374 {
17375 buffers.insert(buffer_entity);
17376 }
17377 }
17378 }
17379
17380 if let Some(project) = &self.project {
17381 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17382 } else {
17383 Task::ready(Ok(()))
17384 }
17385 }
17386
17387 fn do_stage_or_unstage_and_next(
17388 &mut self,
17389 stage: bool,
17390 window: &mut Window,
17391 cx: &mut Context<Self>,
17392 ) {
17393 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17394
17395 if ranges.iter().any(|range| range.start != range.end) {
17396 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17397 return;
17398 }
17399
17400 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17401 let snapshot = self.snapshot(window, cx);
17402 let position = self.selections.newest::<Point>(cx).head();
17403 let mut row = snapshot
17404 .buffer_snapshot
17405 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17406 .find(|hunk| hunk.row_range.start.0 > position.row)
17407 .map(|hunk| hunk.row_range.start);
17408
17409 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17410 // Outside of the project diff editor, wrap around to the beginning.
17411 if !all_diff_hunks_expanded {
17412 row = row.or_else(|| {
17413 snapshot
17414 .buffer_snapshot
17415 .diff_hunks_in_range(Point::zero()..position)
17416 .find(|hunk| hunk.row_range.end.0 < position.row)
17417 .map(|hunk| hunk.row_range.start)
17418 });
17419 }
17420
17421 if let Some(row) = row {
17422 let destination = Point::new(row.0, 0);
17423 let autoscroll = Autoscroll::center();
17424
17425 self.unfold_ranges(&[destination..destination], false, false, cx);
17426 self.change_selections(Some(autoscroll), window, cx, |s| {
17427 s.select_ranges([destination..destination]);
17428 });
17429 }
17430 }
17431
17432 fn do_stage_or_unstage(
17433 &self,
17434 stage: bool,
17435 buffer_id: BufferId,
17436 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17437 cx: &mut App,
17438 ) -> Option<()> {
17439 let project = self.project.as_ref()?;
17440 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17441 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17442 let buffer_snapshot = buffer.read(cx).snapshot();
17443 let file_exists = buffer_snapshot
17444 .file()
17445 .is_some_and(|file| file.disk_state().exists());
17446 diff.update(cx, |diff, cx| {
17447 diff.stage_or_unstage_hunks(
17448 stage,
17449 &hunks
17450 .map(|hunk| buffer_diff::DiffHunk {
17451 buffer_range: hunk.buffer_range,
17452 diff_base_byte_range: hunk.diff_base_byte_range,
17453 secondary_status: hunk.secondary_status,
17454 range: Point::zero()..Point::zero(), // unused
17455 })
17456 .collect::<Vec<_>>(),
17457 &buffer_snapshot,
17458 file_exists,
17459 cx,
17460 )
17461 });
17462 None
17463 }
17464
17465 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17466 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17467 self.buffer
17468 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17469 }
17470
17471 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17472 self.buffer.update(cx, |buffer, cx| {
17473 let ranges = vec![Anchor::min()..Anchor::max()];
17474 if !buffer.all_diff_hunks_expanded()
17475 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17476 {
17477 buffer.collapse_diff_hunks(ranges, cx);
17478 true
17479 } else {
17480 false
17481 }
17482 })
17483 }
17484
17485 fn toggle_diff_hunks_in_ranges(
17486 &mut self,
17487 ranges: Vec<Range<Anchor>>,
17488 cx: &mut Context<Editor>,
17489 ) {
17490 self.buffer.update(cx, |buffer, cx| {
17491 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17492 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17493 })
17494 }
17495
17496 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17497 self.buffer.update(cx, |buffer, cx| {
17498 let snapshot = buffer.snapshot(cx);
17499 let excerpt_id = range.end.excerpt_id;
17500 let point_range = range.to_point(&snapshot);
17501 let expand = !buffer.single_hunk_is_expanded(range, cx);
17502 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17503 })
17504 }
17505
17506 pub(crate) fn apply_all_diff_hunks(
17507 &mut self,
17508 _: &ApplyAllDiffHunks,
17509 window: &mut Window,
17510 cx: &mut Context<Self>,
17511 ) {
17512 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17513
17514 let buffers = self.buffer.read(cx).all_buffers();
17515 for branch_buffer in buffers {
17516 branch_buffer.update(cx, |branch_buffer, cx| {
17517 branch_buffer.merge_into_base(Vec::new(), cx);
17518 });
17519 }
17520
17521 if let Some(project) = self.project.clone() {
17522 self.save(
17523 SaveOptions {
17524 format: true,
17525 autosave: false,
17526 },
17527 project,
17528 window,
17529 cx,
17530 )
17531 .detach_and_log_err(cx);
17532 }
17533 }
17534
17535 pub(crate) fn apply_selected_diff_hunks(
17536 &mut self,
17537 _: &ApplyDiffHunk,
17538 window: &mut Window,
17539 cx: &mut Context<Self>,
17540 ) {
17541 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17542 let snapshot = self.snapshot(window, cx);
17543 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17544 let mut ranges_by_buffer = HashMap::default();
17545 self.transact(window, cx, |editor, _window, cx| {
17546 for hunk in hunks {
17547 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17548 ranges_by_buffer
17549 .entry(buffer.clone())
17550 .or_insert_with(Vec::new)
17551 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17552 }
17553 }
17554
17555 for (buffer, ranges) in ranges_by_buffer {
17556 buffer.update(cx, |buffer, cx| {
17557 buffer.merge_into_base(ranges, cx);
17558 });
17559 }
17560 });
17561
17562 if let Some(project) = self.project.clone() {
17563 self.save(
17564 SaveOptions {
17565 format: true,
17566 autosave: false,
17567 },
17568 project,
17569 window,
17570 cx,
17571 )
17572 .detach_and_log_err(cx);
17573 }
17574 }
17575
17576 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17577 if hovered != self.gutter_hovered {
17578 self.gutter_hovered = hovered;
17579 cx.notify();
17580 }
17581 }
17582
17583 pub fn insert_blocks(
17584 &mut self,
17585 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17586 autoscroll: Option<Autoscroll>,
17587 cx: &mut Context<Self>,
17588 ) -> Vec<CustomBlockId> {
17589 let blocks = self
17590 .display_map
17591 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17592 if let Some(autoscroll) = autoscroll {
17593 self.request_autoscroll(autoscroll, cx);
17594 }
17595 cx.notify();
17596 blocks
17597 }
17598
17599 pub fn resize_blocks(
17600 &mut self,
17601 heights: HashMap<CustomBlockId, u32>,
17602 autoscroll: Option<Autoscroll>,
17603 cx: &mut Context<Self>,
17604 ) {
17605 self.display_map
17606 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17607 if let Some(autoscroll) = autoscroll {
17608 self.request_autoscroll(autoscroll, cx);
17609 }
17610 cx.notify();
17611 }
17612
17613 pub fn replace_blocks(
17614 &mut self,
17615 renderers: HashMap<CustomBlockId, RenderBlock>,
17616 autoscroll: Option<Autoscroll>,
17617 cx: &mut Context<Self>,
17618 ) {
17619 self.display_map
17620 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17621 if let Some(autoscroll) = autoscroll {
17622 self.request_autoscroll(autoscroll, cx);
17623 }
17624 cx.notify();
17625 }
17626
17627 pub fn remove_blocks(
17628 &mut self,
17629 block_ids: HashSet<CustomBlockId>,
17630 autoscroll: Option<Autoscroll>,
17631 cx: &mut Context<Self>,
17632 ) {
17633 self.display_map.update(cx, |display_map, cx| {
17634 display_map.remove_blocks(block_ids, cx)
17635 });
17636 if let Some(autoscroll) = autoscroll {
17637 self.request_autoscroll(autoscroll, cx);
17638 }
17639 cx.notify();
17640 }
17641
17642 pub fn row_for_block(
17643 &self,
17644 block_id: CustomBlockId,
17645 cx: &mut Context<Self>,
17646 ) -> Option<DisplayRow> {
17647 self.display_map
17648 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17649 }
17650
17651 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17652 self.focused_block = Some(focused_block);
17653 }
17654
17655 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17656 self.focused_block.take()
17657 }
17658
17659 pub fn insert_creases(
17660 &mut self,
17661 creases: impl IntoIterator<Item = Crease<Anchor>>,
17662 cx: &mut Context<Self>,
17663 ) -> Vec<CreaseId> {
17664 self.display_map
17665 .update(cx, |map, cx| map.insert_creases(creases, cx))
17666 }
17667
17668 pub fn remove_creases(
17669 &mut self,
17670 ids: impl IntoIterator<Item = CreaseId>,
17671 cx: &mut Context<Self>,
17672 ) -> Vec<(CreaseId, Range<Anchor>)> {
17673 self.display_map
17674 .update(cx, |map, cx| map.remove_creases(ids, cx))
17675 }
17676
17677 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17678 self.display_map
17679 .update(cx, |map, cx| map.snapshot(cx))
17680 .longest_row()
17681 }
17682
17683 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17684 self.display_map
17685 .update(cx, |map, cx| map.snapshot(cx))
17686 .max_point()
17687 }
17688
17689 pub fn text(&self, cx: &App) -> String {
17690 self.buffer.read(cx).read(cx).text()
17691 }
17692
17693 pub fn is_empty(&self, cx: &App) -> bool {
17694 self.buffer.read(cx).read(cx).is_empty()
17695 }
17696
17697 pub fn text_option(&self, cx: &App) -> Option<String> {
17698 let text = self.text(cx);
17699 let text = text.trim();
17700
17701 if text.is_empty() {
17702 return None;
17703 }
17704
17705 Some(text.to_string())
17706 }
17707
17708 pub fn set_text(
17709 &mut self,
17710 text: impl Into<Arc<str>>,
17711 window: &mut Window,
17712 cx: &mut Context<Self>,
17713 ) {
17714 self.transact(window, cx, |this, _, cx| {
17715 this.buffer
17716 .read(cx)
17717 .as_singleton()
17718 .expect("you can only call set_text on editors for singleton buffers")
17719 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17720 });
17721 }
17722
17723 pub fn display_text(&self, cx: &mut App) -> String {
17724 self.display_map
17725 .update(cx, |map, cx| map.snapshot(cx))
17726 .text()
17727 }
17728
17729 fn create_minimap(
17730 &self,
17731 minimap_settings: MinimapSettings,
17732 window: &mut Window,
17733 cx: &mut Context<Self>,
17734 ) -> Option<Entity<Self>> {
17735 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17736 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17737 }
17738
17739 fn initialize_new_minimap(
17740 &self,
17741 minimap_settings: MinimapSettings,
17742 window: &mut Window,
17743 cx: &mut Context<Self>,
17744 ) -> Entity<Self> {
17745 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17746
17747 let mut minimap = Editor::new_internal(
17748 EditorMode::Minimap {
17749 parent: cx.weak_entity(),
17750 },
17751 self.buffer.clone(),
17752 self.project.clone(),
17753 Some(self.display_map.clone()),
17754 window,
17755 cx,
17756 );
17757 minimap.scroll_manager.clone_state(&self.scroll_manager);
17758 minimap.set_text_style_refinement(TextStyleRefinement {
17759 font_size: Some(MINIMAP_FONT_SIZE),
17760 font_weight: Some(MINIMAP_FONT_WEIGHT),
17761 ..Default::default()
17762 });
17763 minimap.update_minimap_configuration(minimap_settings, cx);
17764 cx.new(|_| minimap)
17765 }
17766
17767 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17768 let current_line_highlight = minimap_settings
17769 .current_line_highlight
17770 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17771 self.set_current_line_highlight(Some(current_line_highlight));
17772 }
17773
17774 pub fn minimap(&self) -> Option<&Entity<Self>> {
17775 self.minimap
17776 .as_ref()
17777 .filter(|_| self.minimap_visibility.visible())
17778 }
17779
17780 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17781 let mut wrap_guides = smallvec![];
17782
17783 if self.show_wrap_guides == Some(false) {
17784 return wrap_guides;
17785 }
17786
17787 let settings = self.buffer.read(cx).language_settings(cx);
17788 if settings.show_wrap_guides {
17789 match self.soft_wrap_mode(cx) {
17790 SoftWrap::Column(soft_wrap) => {
17791 wrap_guides.push((soft_wrap as usize, true));
17792 }
17793 SoftWrap::Bounded(soft_wrap) => {
17794 wrap_guides.push((soft_wrap as usize, true));
17795 }
17796 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17797 }
17798 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17799 }
17800
17801 wrap_guides
17802 }
17803
17804 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17805 let settings = self.buffer.read(cx).language_settings(cx);
17806 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17807 match mode {
17808 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17809 SoftWrap::None
17810 }
17811 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17812 language_settings::SoftWrap::PreferredLineLength => {
17813 SoftWrap::Column(settings.preferred_line_length)
17814 }
17815 language_settings::SoftWrap::Bounded => {
17816 SoftWrap::Bounded(settings.preferred_line_length)
17817 }
17818 }
17819 }
17820
17821 pub fn set_soft_wrap_mode(
17822 &mut self,
17823 mode: language_settings::SoftWrap,
17824
17825 cx: &mut Context<Self>,
17826 ) {
17827 self.soft_wrap_mode_override = Some(mode);
17828 cx.notify();
17829 }
17830
17831 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17832 self.hard_wrap = hard_wrap;
17833 cx.notify();
17834 }
17835
17836 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17837 self.text_style_refinement = Some(style);
17838 }
17839
17840 /// called by the Element so we know what style we were most recently rendered with.
17841 pub(crate) fn set_style(
17842 &mut self,
17843 style: EditorStyle,
17844 window: &mut Window,
17845 cx: &mut Context<Self>,
17846 ) {
17847 // We intentionally do not inform the display map about the minimap style
17848 // so that wrapping is not recalculated and stays consistent for the editor
17849 // and its linked minimap.
17850 if !self.mode.is_minimap() {
17851 let rem_size = window.rem_size();
17852 self.display_map.update(cx, |map, cx| {
17853 map.set_font(
17854 style.text.font(),
17855 style.text.font_size.to_pixels(rem_size),
17856 cx,
17857 )
17858 });
17859 }
17860 self.style = Some(style);
17861 }
17862
17863 pub fn style(&self) -> Option<&EditorStyle> {
17864 self.style.as_ref()
17865 }
17866
17867 // Called by the element. This method is not designed to be called outside of the editor
17868 // element's layout code because it does not notify when rewrapping is computed synchronously.
17869 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17870 self.display_map
17871 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17872 }
17873
17874 pub fn set_soft_wrap(&mut self) {
17875 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17876 }
17877
17878 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17879 if self.soft_wrap_mode_override.is_some() {
17880 self.soft_wrap_mode_override.take();
17881 } else {
17882 let soft_wrap = match self.soft_wrap_mode(cx) {
17883 SoftWrap::GitDiff => return,
17884 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17885 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17886 language_settings::SoftWrap::None
17887 }
17888 };
17889 self.soft_wrap_mode_override = Some(soft_wrap);
17890 }
17891 cx.notify();
17892 }
17893
17894 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17895 let Some(workspace) = self.workspace() else {
17896 return;
17897 };
17898 let fs = workspace.read(cx).app_state().fs.clone();
17899 let current_show = TabBarSettings::get_global(cx).show;
17900 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17901 setting.show = Some(!current_show);
17902 });
17903 }
17904
17905 pub fn toggle_indent_guides(
17906 &mut self,
17907 _: &ToggleIndentGuides,
17908 _: &mut Window,
17909 cx: &mut Context<Self>,
17910 ) {
17911 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17912 self.buffer
17913 .read(cx)
17914 .language_settings(cx)
17915 .indent_guides
17916 .enabled
17917 });
17918 self.show_indent_guides = Some(!currently_enabled);
17919 cx.notify();
17920 }
17921
17922 fn should_show_indent_guides(&self) -> Option<bool> {
17923 self.show_indent_guides
17924 }
17925
17926 pub fn toggle_line_numbers(
17927 &mut self,
17928 _: &ToggleLineNumbers,
17929 _: &mut Window,
17930 cx: &mut Context<Self>,
17931 ) {
17932 let mut editor_settings = EditorSettings::get_global(cx).clone();
17933 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17934 EditorSettings::override_global(editor_settings, cx);
17935 }
17936
17937 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17938 if let Some(show_line_numbers) = self.show_line_numbers {
17939 return show_line_numbers;
17940 }
17941 EditorSettings::get_global(cx).gutter.line_numbers
17942 }
17943
17944 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17945 self.use_relative_line_numbers
17946 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17947 }
17948
17949 pub fn toggle_relative_line_numbers(
17950 &mut self,
17951 _: &ToggleRelativeLineNumbers,
17952 _: &mut Window,
17953 cx: &mut Context<Self>,
17954 ) {
17955 let is_relative = self.should_use_relative_line_numbers(cx);
17956 self.set_relative_line_number(Some(!is_relative), cx)
17957 }
17958
17959 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17960 self.use_relative_line_numbers = is_relative;
17961 cx.notify();
17962 }
17963
17964 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17965 self.show_gutter = show_gutter;
17966 cx.notify();
17967 }
17968
17969 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17970 self.show_scrollbars = ScrollbarAxes {
17971 horizontal: show,
17972 vertical: show,
17973 };
17974 cx.notify();
17975 }
17976
17977 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17978 self.show_scrollbars.vertical = show;
17979 cx.notify();
17980 }
17981
17982 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17983 self.show_scrollbars.horizontal = show;
17984 cx.notify();
17985 }
17986
17987 pub fn set_minimap_visibility(
17988 &mut self,
17989 minimap_visibility: MinimapVisibility,
17990 window: &mut Window,
17991 cx: &mut Context<Self>,
17992 ) {
17993 if self.minimap_visibility != minimap_visibility {
17994 if minimap_visibility.visible() && self.minimap.is_none() {
17995 let minimap_settings = EditorSettings::get_global(cx).minimap;
17996 self.minimap =
17997 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17998 }
17999 self.minimap_visibility = minimap_visibility;
18000 cx.notify();
18001 }
18002 }
18003
18004 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18005 self.set_show_scrollbars(false, cx);
18006 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18007 }
18008
18009 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18010 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18011 }
18012
18013 /// Normally the text in full mode and auto height editors is padded on the
18014 /// left side by roughly half a character width for improved hit testing.
18015 ///
18016 /// Use this method to disable this for cases where this is not wanted (e.g.
18017 /// if you want to align the editor text with some other text above or below)
18018 /// or if you want to add this padding to single-line editors.
18019 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18020 self.offset_content = offset_content;
18021 cx.notify();
18022 }
18023
18024 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18025 self.show_line_numbers = Some(show_line_numbers);
18026 cx.notify();
18027 }
18028
18029 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18030 self.disable_expand_excerpt_buttons = true;
18031 cx.notify();
18032 }
18033
18034 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18035 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18036 cx.notify();
18037 }
18038
18039 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18040 self.show_code_actions = Some(show_code_actions);
18041 cx.notify();
18042 }
18043
18044 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18045 self.show_runnables = Some(show_runnables);
18046 cx.notify();
18047 }
18048
18049 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18050 self.show_breakpoints = Some(show_breakpoints);
18051 cx.notify();
18052 }
18053
18054 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18055 if self.display_map.read(cx).masked != masked {
18056 self.display_map.update(cx, |map, _| map.masked = masked);
18057 }
18058 cx.notify()
18059 }
18060
18061 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18062 self.show_wrap_guides = Some(show_wrap_guides);
18063 cx.notify();
18064 }
18065
18066 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18067 self.show_indent_guides = Some(show_indent_guides);
18068 cx.notify();
18069 }
18070
18071 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18072 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18073 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
18074 if let Some(dir) = file.abs_path(cx).parent() {
18075 return Some(dir.to_owned());
18076 }
18077 }
18078
18079 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18080 return Some(project_path.path.to_path_buf());
18081 }
18082 }
18083
18084 None
18085 }
18086
18087 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18088 self.active_excerpt(cx)?
18089 .1
18090 .read(cx)
18091 .file()
18092 .and_then(|f| f.as_local())
18093 }
18094
18095 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18096 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18097 let buffer = buffer.read(cx);
18098 if let Some(project_path) = buffer.project_path(cx) {
18099 let project = self.project.as_ref()?.read(cx);
18100 project.absolute_path(&project_path, cx)
18101 } else {
18102 buffer
18103 .file()
18104 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18105 }
18106 })
18107 }
18108
18109 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18110 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18111 let project_path = buffer.read(cx).project_path(cx)?;
18112 let project = self.project.as_ref()?.read(cx);
18113 let entry = project.entry_for_path(&project_path, cx)?;
18114 let path = entry.path.to_path_buf();
18115 Some(path)
18116 })
18117 }
18118
18119 pub fn reveal_in_finder(
18120 &mut self,
18121 _: &RevealInFileManager,
18122 _window: &mut Window,
18123 cx: &mut Context<Self>,
18124 ) {
18125 if let Some(target) = self.target_file(cx) {
18126 cx.reveal_path(&target.abs_path(cx));
18127 }
18128 }
18129
18130 pub fn copy_path(
18131 &mut self,
18132 _: &zed_actions::workspace::CopyPath,
18133 _window: &mut Window,
18134 cx: &mut Context<Self>,
18135 ) {
18136 if let Some(path) = self.target_file_abs_path(cx) {
18137 if let Some(path) = path.to_str() {
18138 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18139 }
18140 }
18141 }
18142
18143 pub fn copy_relative_path(
18144 &mut self,
18145 _: &zed_actions::workspace::CopyRelativePath,
18146 _window: &mut Window,
18147 cx: &mut Context<Self>,
18148 ) {
18149 if let Some(path) = self.target_file_path(cx) {
18150 if let Some(path) = path.to_str() {
18151 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18152 }
18153 }
18154 }
18155
18156 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18157 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18158 buffer.read(cx).project_path(cx)
18159 } else {
18160 None
18161 }
18162 }
18163
18164 // Returns true if the editor handled a go-to-line request
18165 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18166 maybe!({
18167 let breakpoint_store = self.breakpoint_store.as_ref()?;
18168
18169 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18170 else {
18171 self.clear_row_highlights::<ActiveDebugLine>();
18172 return None;
18173 };
18174
18175 let position = active_stack_frame.position;
18176 let buffer_id = position.buffer_id?;
18177 let snapshot = self
18178 .project
18179 .as_ref()?
18180 .read(cx)
18181 .buffer_for_id(buffer_id, cx)?
18182 .read(cx)
18183 .snapshot();
18184
18185 let mut handled = false;
18186 for (id, ExcerptRange { context, .. }) in
18187 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18188 {
18189 if context.start.cmp(&position, &snapshot).is_ge()
18190 || context.end.cmp(&position, &snapshot).is_lt()
18191 {
18192 continue;
18193 }
18194 let snapshot = self.buffer.read(cx).snapshot(cx);
18195 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18196
18197 handled = true;
18198 self.clear_row_highlights::<ActiveDebugLine>();
18199
18200 self.go_to_line::<ActiveDebugLine>(
18201 multibuffer_anchor,
18202 Some(cx.theme().colors().editor_debugger_active_line_background),
18203 window,
18204 cx,
18205 );
18206
18207 cx.notify();
18208 }
18209
18210 handled.then_some(())
18211 })
18212 .is_some()
18213 }
18214
18215 pub fn copy_file_name_without_extension(
18216 &mut self,
18217 _: &CopyFileNameWithoutExtension,
18218 _: &mut Window,
18219 cx: &mut Context<Self>,
18220 ) {
18221 if let Some(file) = self.target_file(cx) {
18222 if let Some(file_stem) = file.path().file_stem() {
18223 if let Some(name) = file_stem.to_str() {
18224 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18225 }
18226 }
18227 }
18228 }
18229
18230 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18231 if let Some(file) = self.target_file(cx) {
18232 if let Some(file_name) = file.path().file_name() {
18233 if let Some(name) = file_name.to_str() {
18234 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18235 }
18236 }
18237 }
18238 }
18239
18240 pub fn toggle_git_blame(
18241 &mut self,
18242 _: &::git::Blame,
18243 window: &mut Window,
18244 cx: &mut Context<Self>,
18245 ) {
18246 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18247
18248 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18249 self.start_git_blame(true, window, cx);
18250 }
18251
18252 cx.notify();
18253 }
18254
18255 pub fn toggle_git_blame_inline(
18256 &mut self,
18257 _: &ToggleGitBlameInline,
18258 window: &mut Window,
18259 cx: &mut Context<Self>,
18260 ) {
18261 self.toggle_git_blame_inline_internal(true, window, cx);
18262 cx.notify();
18263 }
18264
18265 pub fn open_git_blame_commit(
18266 &mut self,
18267 _: &OpenGitBlameCommit,
18268 window: &mut Window,
18269 cx: &mut Context<Self>,
18270 ) {
18271 self.open_git_blame_commit_internal(window, cx);
18272 }
18273
18274 fn open_git_blame_commit_internal(
18275 &mut self,
18276 window: &mut Window,
18277 cx: &mut Context<Self>,
18278 ) -> Option<()> {
18279 let blame = self.blame.as_ref()?;
18280 let snapshot = self.snapshot(window, cx);
18281 let cursor = self.selections.newest::<Point>(cx).head();
18282 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18283 let blame_entry = blame
18284 .update(cx, |blame, cx| {
18285 blame
18286 .blame_for_rows(
18287 &[RowInfo {
18288 buffer_id: Some(buffer.remote_id()),
18289 buffer_row: Some(point.row),
18290 ..Default::default()
18291 }],
18292 cx,
18293 )
18294 .next()
18295 })
18296 .flatten()?;
18297 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18298 let repo = blame.read(cx).repository(cx)?;
18299 let workspace = self.workspace()?.downgrade();
18300 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18301 None
18302 }
18303
18304 pub fn git_blame_inline_enabled(&self) -> bool {
18305 self.git_blame_inline_enabled
18306 }
18307
18308 pub fn toggle_selection_menu(
18309 &mut self,
18310 _: &ToggleSelectionMenu,
18311 _: &mut Window,
18312 cx: &mut Context<Self>,
18313 ) {
18314 self.show_selection_menu = self
18315 .show_selection_menu
18316 .map(|show_selections_menu| !show_selections_menu)
18317 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18318
18319 cx.notify();
18320 }
18321
18322 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18323 self.show_selection_menu
18324 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18325 }
18326
18327 fn start_git_blame(
18328 &mut self,
18329 user_triggered: bool,
18330 window: &mut Window,
18331 cx: &mut Context<Self>,
18332 ) {
18333 if let Some(project) = self.project.as_ref() {
18334 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18335 return;
18336 };
18337
18338 if buffer.read(cx).file().is_none() {
18339 return;
18340 }
18341
18342 let focused = self.focus_handle(cx).contains_focused(window, cx);
18343
18344 let project = project.clone();
18345 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18346 self.blame_subscription =
18347 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18348 self.blame = Some(blame);
18349 }
18350 }
18351
18352 fn toggle_git_blame_inline_internal(
18353 &mut self,
18354 user_triggered: bool,
18355 window: &mut Window,
18356 cx: &mut Context<Self>,
18357 ) {
18358 if self.git_blame_inline_enabled {
18359 self.git_blame_inline_enabled = false;
18360 self.show_git_blame_inline = false;
18361 self.show_git_blame_inline_delay_task.take();
18362 } else {
18363 self.git_blame_inline_enabled = true;
18364 self.start_git_blame_inline(user_triggered, window, cx);
18365 }
18366
18367 cx.notify();
18368 }
18369
18370 fn start_git_blame_inline(
18371 &mut self,
18372 user_triggered: bool,
18373 window: &mut Window,
18374 cx: &mut Context<Self>,
18375 ) {
18376 self.start_git_blame(user_triggered, window, cx);
18377
18378 if ProjectSettings::get_global(cx)
18379 .git
18380 .inline_blame_delay()
18381 .is_some()
18382 {
18383 self.start_inline_blame_timer(window, cx);
18384 } else {
18385 self.show_git_blame_inline = true
18386 }
18387 }
18388
18389 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18390 self.blame.as_ref()
18391 }
18392
18393 pub fn show_git_blame_gutter(&self) -> bool {
18394 self.show_git_blame_gutter
18395 }
18396
18397 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18398 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18399 }
18400
18401 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18402 self.show_git_blame_inline
18403 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18404 && !self.newest_selection_head_on_empty_line(cx)
18405 && self.has_blame_entries(cx)
18406 }
18407
18408 fn has_blame_entries(&self, cx: &App) -> bool {
18409 self.blame()
18410 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18411 }
18412
18413 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18414 let cursor_anchor = self.selections.newest_anchor().head();
18415
18416 let snapshot = self.buffer.read(cx).snapshot(cx);
18417 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18418
18419 snapshot.line_len(buffer_row) == 0
18420 }
18421
18422 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18423 let buffer_and_selection = maybe!({
18424 let selection = self.selections.newest::<Point>(cx);
18425 let selection_range = selection.range();
18426
18427 let multi_buffer = self.buffer().read(cx);
18428 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18429 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18430
18431 let (buffer, range, _) = if selection.reversed {
18432 buffer_ranges.first()
18433 } else {
18434 buffer_ranges.last()
18435 }?;
18436
18437 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18438 ..text::ToPoint::to_point(&range.end, &buffer).row;
18439 Some((
18440 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18441 selection,
18442 ))
18443 });
18444
18445 let Some((buffer, selection)) = buffer_and_selection else {
18446 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18447 };
18448
18449 let Some(project) = self.project.as_ref() else {
18450 return Task::ready(Err(anyhow!("editor does not have project")));
18451 };
18452
18453 project.update(cx, |project, cx| {
18454 project.get_permalink_to_line(&buffer, selection, cx)
18455 })
18456 }
18457
18458 pub fn copy_permalink_to_line(
18459 &mut self,
18460 _: &CopyPermalinkToLine,
18461 window: &mut Window,
18462 cx: &mut Context<Self>,
18463 ) {
18464 let permalink_task = self.get_permalink_to_line(cx);
18465 let workspace = self.workspace();
18466
18467 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18468 Ok(permalink) => {
18469 cx.update(|_, cx| {
18470 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18471 })
18472 .ok();
18473 }
18474 Err(err) => {
18475 let message = format!("Failed to copy permalink: {err}");
18476
18477 anyhow::Result::<()>::Err(err).log_err();
18478
18479 if let Some(workspace) = workspace {
18480 workspace
18481 .update_in(cx, |workspace, _, cx| {
18482 struct CopyPermalinkToLine;
18483
18484 workspace.show_toast(
18485 Toast::new(
18486 NotificationId::unique::<CopyPermalinkToLine>(),
18487 message,
18488 ),
18489 cx,
18490 )
18491 })
18492 .ok();
18493 }
18494 }
18495 })
18496 .detach();
18497 }
18498
18499 pub fn copy_file_location(
18500 &mut self,
18501 _: &CopyFileLocation,
18502 _: &mut Window,
18503 cx: &mut Context<Self>,
18504 ) {
18505 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18506 if let Some(file) = self.target_file(cx) {
18507 if let Some(path) = file.path().to_str() {
18508 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18509 }
18510 }
18511 }
18512
18513 pub fn open_permalink_to_line(
18514 &mut self,
18515 _: &OpenPermalinkToLine,
18516 window: &mut Window,
18517 cx: &mut Context<Self>,
18518 ) {
18519 let permalink_task = self.get_permalink_to_line(cx);
18520 let workspace = self.workspace();
18521
18522 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18523 Ok(permalink) => {
18524 cx.update(|_, cx| {
18525 cx.open_url(permalink.as_ref());
18526 })
18527 .ok();
18528 }
18529 Err(err) => {
18530 let message = format!("Failed to open permalink: {err}");
18531
18532 anyhow::Result::<()>::Err(err).log_err();
18533
18534 if let Some(workspace) = workspace {
18535 workspace
18536 .update(cx, |workspace, cx| {
18537 struct OpenPermalinkToLine;
18538
18539 workspace.show_toast(
18540 Toast::new(
18541 NotificationId::unique::<OpenPermalinkToLine>(),
18542 message,
18543 ),
18544 cx,
18545 )
18546 })
18547 .ok();
18548 }
18549 }
18550 })
18551 .detach();
18552 }
18553
18554 pub fn insert_uuid_v4(
18555 &mut self,
18556 _: &InsertUuidV4,
18557 window: &mut Window,
18558 cx: &mut Context<Self>,
18559 ) {
18560 self.insert_uuid(UuidVersion::V4, window, cx);
18561 }
18562
18563 pub fn insert_uuid_v7(
18564 &mut self,
18565 _: &InsertUuidV7,
18566 window: &mut Window,
18567 cx: &mut Context<Self>,
18568 ) {
18569 self.insert_uuid(UuidVersion::V7, window, cx);
18570 }
18571
18572 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18573 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18574 self.transact(window, cx, |this, window, cx| {
18575 let edits = this
18576 .selections
18577 .all::<Point>(cx)
18578 .into_iter()
18579 .map(|selection| {
18580 let uuid = match version {
18581 UuidVersion::V4 => uuid::Uuid::new_v4(),
18582 UuidVersion::V7 => uuid::Uuid::now_v7(),
18583 };
18584
18585 (selection.range(), uuid.to_string())
18586 });
18587 this.edit(edits, cx);
18588 this.refresh_inline_completion(true, false, window, cx);
18589 });
18590 }
18591
18592 pub fn open_selections_in_multibuffer(
18593 &mut self,
18594 _: &OpenSelectionsInMultibuffer,
18595 window: &mut Window,
18596 cx: &mut Context<Self>,
18597 ) {
18598 let multibuffer = self.buffer.read(cx);
18599
18600 let Some(buffer) = multibuffer.as_singleton() else {
18601 return;
18602 };
18603
18604 let Some(workspace) = self.workspace() else {
18605 return;
18606 };
18607
18608 let title = multibuffer.title(cx).to_string();
18609
18610 let locations = self
18611 .selections
18612 .all_anchors(cx)
18613 .into_iter()
18614 .map(|selection| Location {
18615 buffer: buffer.clone(),
18616 range: selection.start.text_anchor..selection.end.text_anchor,
18617 })
18618 .collect::<Vec<_>>();
18619
18620 cx.spawn_in(window, async move |_, cx| {
18621 workspace.update_in(cx, |workspace, window, cx| {
18622 Self::open_locations_in_multibuffer(
18623 workspace,
18624 locations,
18625 format!("Selections for '{title}'"),
18626 false,
18627 MultibufferSelectionMode::All,
18628 window,
18629 cx,
18630 );
18631 })
18632 })
18633 .detach();
18634 }
18635
18636 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18637 /// last highlight added will be used.
18638 ///
18639 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18640 pub fn highlight_rows<T: 'static>(
18641 &mut self,
18642 range: Range<Anchor>,
18643 color: Hsla,
18644 options: RowHighlightOptions,
18645 cx: &mut Context<Self>,
18646 ) {
18647 let snapshot = self.buffer().read(cx).snapshot(cx);
18648 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18649 let ix = row_highlights.binary_search_by(|highlight| {
18650 Ordering::Equal
18651 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18652 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18653 });
18654
18655 if let Err(mut ix) = ix {
18656 let index = post_inc(&mut self.highlight_order);
18657
18658 // If this range intersects with the preceding highlight, then merge it with
18659 // the preceding highlight. Otherwise insert a new highlight.
18660 let mut merged = false;
18661 if ix > 0 {
18662 let prev_highlight = &mut row_highlights[ix - 1];
18663 if prev_highlight
18664 .range
18665 .end
18666 .cmp(&range.start, &snapshot)
18667 .is_ge()
18668 {
18669 ix -= 1;
18670 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18671 prev_highlight.range.end = range.end;
18672 }
18673 merged = true;
18674 prev_highlight.index = index;
18675 prev_highlight.color = color;
18676 prev_highlight.options = options;
18677 }
18678 }
18679
18680 if !merged {
18681 row_highlights.insert(
18682 ix,
18683 RowHighlight {
18684 range: range.clone(),
18685 index,
18686 color,
18687 options,
18688 type_id: TypeId::of::<T>(),
18689 },
18690 );
18691 }
18692
18693 // If any of the following highlights intersect with this one, merge them.
18694 while let Some(next_highlight) = row_highlights.get(ix + 1) {
18695 let highlight = &row_highlights[ix];
18696 if next_highlight
18697 .range
18698 .start
18699 .cmp(&highlight.range.end, &snapshot)
18700 .is_le()
18701 {
18702 if next_highlight
18703 .range
18704 .end
18705 .cmp(&highlight.range.end, &snapshot)
18706 .is_gt()
18707 {
18708 row_highlights[ix].range.end = next_highlight.range.end;
18709 }
18710 row_highlights.remove(ix + 1);
18711 } else {
18712 break;
18713 }
18714 }
18715 }
18716 }
18717
18718 /// Remove any highlighted row ranges of the given type that intersect the
18719 /// given ranges.
18720 pub fn remove_highlighted_rows<T: 'static>(
18721 &mut self,
18722 ranges_to_remove: Vec<Range<Anchor>>,
18723 cx: &mut Context<Self>,
18724 ) {
18725 let snapshot = self.buffer().read(cx).snapshot(cx);
18726 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18727 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18728 row_highlights.retain(|highlight| {
18729 while let Some(range_to_remove) = ranges_to_remove.peek() {
18730 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18731 Ordering::Less | Ordering::Equal => {
18732 ranges_to_remove.next();
18733 }
18734 Ordering::Greater => {
18735 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18736 Ordering::Less | Ordering::Equal => {
18737 return false;
18738 }
18739 Ordering::Greater => break,
18740 }
18741 }
18742 }
18743 }
18744
18745 true
18746 })
18747 }
18748
18749 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18750 pub fn clear_row_highlights<T: 'static>(&mut self) {
18751 self.highlighted_rows.remove(&TypeId::of::<T>());
18752 }
18753
18754 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18755 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18756 self.highlighted_rows
18757 .get(&TypeId::of::<T>())
18758 .map_or(&[] as &[_], |vec| vec.as_slice())
18759 .iter()
18760 .map(|highlight| (highlight.range.clone(), highlight.color))
18761 }
18762
18763 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18764 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18765 /// Allows to ignore certain kinds of highlights.
18766 pub fn highlighted_display_rows(
18767 &self,
18768 window: &mut Window,
18769 cx: &mut App,
18770 ) -> BTreeMap<DisplayRow, LineHighlight> {
18771 let snapshot = self.snapshot(window, cx);
18772 let mut used_highlight_orders = HashMap::default();
18773 self.highlighted_rows
18774 .iter()
18775 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18776 .fold(
18777 BTreeMap::<DisplayRow, LineHighlight>::new(),
18778 |mut unique_rows, highlight| {
18779 let start = highlight.range.start.to_display_point(&snapshot);
18780 let end = highlight.range.end.to_display_point(&snapshot);
18781 let start_row = start.row().0;
18782 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18783 && end.column() == 0
18784 {
18785 end.row().0.saturating_sub(1)
18786 } else {
18787 end.row().0
18788 };
18789 for row in start_row..=end_row {
18790 let used_index =
18791 used_highlight_orders.entry(row).or_insert(highlight.index);
18792 if highlight.index >= *used_index {
18793 *used_index = highlight.index;
18794 unique_rows.insert(
18795 DisplayRow(row),
18796 LineHighlight {
18797 include_gutter: highlight.options.include_gutter,
18798 border: None,
18799 background: highlight.color.into(),
18800 type_id: Some(highlight.type_id),
18801 },
18802 );
18803 }
18804 }
18805 unique_rows
18806 },
18807 )
18808 }
18809
18810 pub fn highlighted_display_row_for_autoscroll(
18811 &self,
18812 snapshot: &DisplaySnapshot,
18813 ) -> Option<DisplayRow> {
18814 self.highlighted_rows
18815 .values()
18816 .flat_map(|highlighted_rows| highlighted_rows.iter())
18817 .filter_map(|highlight| {
18818 if highlight.options.autoscroll {
18819 Some(highlight.range.start.to_display_point(snapshot).row())
18820 } else {
18821 None
18822 }
18823 })
18824 .min()
18825 }
18826
18827 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18828 self.highlight_background::<SearchWithinRange>(
18829 ranges,
18830 |colors| colors.colors().editor_document_highlight_read_background,
18831 cx,
18832 )
18833 }
18834
18835 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18836 self.breadcrumb_header = Some(new_header);
18837 }
18838
18839 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18840 self.clear_background_highlights::<SearchWithinRange>(cx);
18841 }
18842
18843 pub fn highlight_background<T: 'static>(
18844 &mut self,
18845 ranges: &[Range<Anchor>],
18846 color_fetcher: fn(&Theme) -> Hsla,
18847 cx: &mut Context<Self>,
18848 ) {
18849 self.background_highlights.insert(
18850 HighlightKey::Type(TypeId::of::<T>()),
18851 (color_fetcher, Arc::from(ranges)),
18852 );
18853 self.scrollbar_marker_state.dirty = true;
18854 cx.notify();
18855 }
18856
18857 pub fn highlight_background_key<T: 'static>(
18858 &mut self,
18859 key: usize,
18860 ranges: &[Range<Anchor>],
18861 color_fetcher: fn(&Theme) -> Hsla,
18862 cx: &mut Context<Self>,
18863 ) {
18864 self.background_highlights.insert(
18865 HighlightKey::TypePlus(TypeId::of::<T>(), key),
18866 (color_fetcher, Arc::from(ranges)),
18867 );
18868 self.scrollbar_marker_state.dirty = true;
18869 cx.notify();
18870 }
18871
18872 pub fn clear_background_highlights<T: 'static>(
18873 &mut self,
18874 cx: &mut Context<Self>,
18875 ) -> Option<BackgroundHighlight> {
18876 let text_highlights = self
18877 .background_highlights
18878 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
18879 if !text_highlights.1.is_empty() {
18880 self.scrollbar_marker_state.dirty = true;
18881 cx.notify();
18882 }
18883 Some(text_highlights)
18884 }
18885
18886 pub fn highlight_gutter<T: 'static>(
18887 &mut self,
18888 ranges: impl Into<Vec<Range<Anchor>>>,
18889 color_fetcher: fn(&App) -> Hsla,
18890 cx: &mut Context<Self>,
18891 ) {
18892 self.gutter_highlights
18893 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
18894 cx.notify();
18895 }
18896
18897 pub fn clear_gutter_highlights<T: 'static>(
18898 &mut self,
18899 cx: &mut Context<Self>,
18900 ) -> Option<GutterHighlight> {
18901 cx.notify();
18902 self.gutter_highlights.remove(&TypeId::of::<T>())
18903 }
18904
18905 pub fn insert_gutter_highlight<T: 'static>(
18906 &mut self,
18907 range: Range<Anchor>,
18908 color_fetcher: fn(&App) -> Hsla,
18909 cx: &mut Context<Self>,
18910 ) {
18911 let snapshot = self.buffer().read(cx).snapshot(cx);
18912 let mut highlights = self
18913 .gutter_highlights
18914 .remove(&TypeId::of::<T>())
18915 .map(|(_, highlights)| highlights)
18916 .unwrap_or_default();
18917 let ix = highlights.binary_search_by(|highlight| {
18918 Ordering::Equal
18919 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
18920 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
18921 });
18922 if let Err(ix) = ix {
18923 highlights.insert(ix, range);
18924 }
18925 self.gutter_highlights
18926 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
18927 }
18928
18929 pub fn remove_gutter_highlights<T: 'static>(
18930 &mut self,
18931 ranges_to_remove: Vec<Range<Anchor>>,
18932 cx: &mut Context<Self>,
18933 ) {
18934 let snapshot = self.buffer().read(cx).snapshot(cx);
18935 let Some((color_fetcher, mut gutter_highlights)) =
18936 self.gutter_highlights.remove(&TypeId::of::<T>())
18937 else {
18938 return;
18939 };
18940 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18941 gutter_highlights.retain(|highlight| {
18942 while let Some(range_to_remove) = ranges_to_remove.peek() {
18943 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
18944 Ordering::Less | Ordering::Equal => {
18945 ranges_to_remove.next();
18946 }
18947 Ordering::Greater => {
18948 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
18949 Ordering::Less | Ordering::Equal => {
18950 return false;
18951 }
18952 Ordering::Greater => break,
18953 }
18954 }
18955 }
18956 }
18957
18958 true
18959 });
18960 self.gutter_highlights
18961 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
18962 }
18963
18964 #[cfg(feature = "test-support")]
18965 pub fn all_text_highlights(
18966 &self,
18967 window: &mut Window,
18968 cx: &mut Context<Self>,
18969 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
18970 let snapshot = self.snapshot(window, cx);
18971 self.display_map.update(cx, |display_map, _| {
18972 display_map
18973 .all_text_highlights()
18974 .map(|highlight| {
18975 let (style, ranges) = highlight.as_ref();
18976 (
18977 *style,
18978 ranges
18979 .iter()
18980 .map(|range| range.clone().to_display_points(&snapshot))
18981 .collect(),
18982 )
18983 })
18984 .collect()
18985 })
18986 }
18987
18988 #[cfg(feature = "test-support")]
18989 pub fn all_text_background_highlights(
18990 &self,
18991 window: &mut Window,
18992 cx: &mut Context<Self>,
18993 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18994 let snapshot = self.snapshot(window, cx);
18995 let buffer = &snapshot.buffer_snapshot;
18996 let start = buffer.anchor_before(0);
18997 let end = buffer.anchor_after(buffer.len());
18998 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
18999 }
19000
19001 #[cfg(feature = "test-support")]
19002 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19003 let snapshot = self.buffer().read(cx).snapshot(cx);
19004
19005 let highlights = self
19006 .background_highlights
19007 .get(&HighlightKey::Type(TypeId::of::<
19008 items::BufferSearchHighlights,
19009 >()));
19010
19011 if let Some((_color, ranges)) = highlights {
19012 ranges
19013 .iter()
19014 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19015 .collect_vec()
19016 } else {
19017 vec![]
19018 }
19019 }
19020
19021 fn document_highlights_for_position<'a>(
19022 &'a self,
19023 position: Anchor,
19024 buffer: &'a MultiBufferSnapshot,
19025 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19026 let read_highlights = self
19027 .background_highlights
19028 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19029 .map(|h| &h.1);
19030 let write_highlights = self
19031 .background_highlights
19032 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19033 .map(|h| &h.1);
19034 let left_position = position.bias_left(buffer);
19035 let right_position = position.bias_right(buffer);
19036 read_highlights
19037 .into_iter()
19038 .chain(write_highlights)
19039 .flat_map(move |ranges| {
19040 let start_ix = match ranges.binary_search_by(|probe| {
19041 let cmp = probe.end.cmp(&left_position, buffer);
19042 if cmp.is_ge() {
19043 Ordering::Greater
19044 } else {
19045 Ordering::Less
19046 }
19047 }) {
19048 Ok(i) | Err(i) => i,
19049 };
19050
19051 ranges[start_ix..]
19052 .iter()
19053 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19054 })
19055 }
19056
19057 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19058 self.background_highlights
19059 .get(&HighlightKey::Type(TypeId::of::<T>()))
19060 .map_or(false, |(_, highlights)| !highlights.is_empty())
19061 }
19062
19063 pub fn background_highlights_in_range(
19064 &self,
19065 search_range: Range<Anchor>,
19066 display_snapshot: &DisplaySnapshot,
19067 theme: &Theme,
19068 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19069 let mut results = Vec::new();
19070 for (color_fetcher, ranges) in self.background_highlights.values() {
19071 let color = color_fetcher(theme);
19072 let start_ix = match ranges.binary_search_by(|probe| {
19073 let cmp = probe
19074 .end
19075 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19076 if cmp.is_gt() {
19077 Ordering::Greater
19078 } else {
19079 Ordering::Less
19080 }
19081 }) {
19082 Ok(i) | Err(i) => i,
19083 };
19084 for range in &ranges[start_ix..] {
19085 if range
19086 .start
19087 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19088 .is_ge()
19089 {
19090 break;
19091 }
19092
19093 let start = range.start.to_display_point(display_snapshot);
19094 let end = range.end.to_display_point(display_snapshot);
19095 results.push((start..end, color))
19096 }
19097 }
19098 results
19099 }
19100
19101 pub fn background_highlight_row_ranges<T: 'static>(
19102 &self,
19103 search_range: Range<Anchor>,
19104 display_snapshot: &DisplaySnapshot,
19105 count: usize,
19106 ) -> Vec<RangeInclusive<DisplayPoint>> {
19107 let mut results = Vec::new();
19108 let Some((_, ranges)) = self
19109 .background_highlights
19110 .get(&HighlightKey::Type(TypeId::of::<T>()))
19111 else {
19112 return vec![];
19113 };
19114
19115 let start_ix = match ranges.binary_search_by(|probe| {
19116 let cmp = probe
19117 .end
19118 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19119 if cmp.is_gt() {
19120 Ordering::Greater
19121 } else {
19122 Ordering::Less
19123 }
19124 }) {
19125 Ok(i) | Err(i) => i,
19126 };
19127 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19128 if let (Some(start_display), Some(end_display)) = (start, end) {
19129 results.push(
19130 start_display.to_display_point(display_snapshot)
19131 ..=end_display.to_display_point(display_snapshot),
19132 );
19133 }
19134 };
19135 let mut start_row: Option<Point> = None;
19136 let mut end_row: Option<Point> = None;
19137 if ranges.len() > count {
19138 return Vec::new();
19139 }
19140 for range in &ranges[start_ix..] {
19141 if range
19142 .start
19143 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19144 .is_ge()
19145 {
19146 break;
19147 }
19148 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19149 if let Some(current_row) = &end_row {
19150 if end.row == current_row.row {
19151 continue;
19152 }
19153 }
19154 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19155 if start_row.is_none() {
19156 assert_eq!(end_row, None);
19157 start_row = Some(start);
19158 end_row = Some(end);
19159 continue;
19160 }
19161 if let Some(current_end) = end_row.as_mut() {
19162 if start.row > current_end.row + 1 {
19163 push_region(start_row, end_row);
19164 start_row = Some(start);
19165 end_row = Some(end);
19166 } else {
19167 // Merge two hunks.
19168 *current_end = end;
19169 }
19170 } else {
19171 unreachable!();
19172 }
19173 }
19174 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19175 push_region(start_row, end_row);
19176 results
19177 }
19178
19179 pub fn gutter_highlights_in_range(
19180 &self,
19181 search_range: Range<Anchor>,
19182 display_snapshot: &DisplaySnapshot,
19183 cx: &App,
19184 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19185 let mut results = Vec::new();
19186 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19187 let color = color_fetcher(cx);
19188 let start_ix = match ranges.binary_search_by(|probe| {
19189 let cmp = probe
19190 .end
19191 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19192 if cmp.is_gt() {
19193 Ordering::Greater
19194 } else {
19195 Ordering::Less
19196 }
19197 }) {
19198 Ok(i) | Err(i) => i,
19199 };
19200 for range in &ranges[start_ix..] {
19201 if range
19202 .start
19203 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19204 .is_ge()
19205 {
19206 break;
19207 }
19208
19209 let start = range.start.to_display_point(display_snapshot);
19210 let end = range.end.to_display_point(display_snapshot);
19211 results.push((start..end, color))
19212 }
19213 }
19214 results
19215 }
19216
19217 /// Get the text ranges corresponding to the redaction query
19218 pub fn redacted_ranges(
19219 &self,
19220 search_range: Range<Anchor>,
19221 display_snapshot: &DisplaySnapshot,
19222 cx: &App,
19223 ) -> Vec<Range<DisplayPoint>> {
19224 display_snapshot
19225 .buffer_snapshot
19226 .redacted_ranges(search_range, |file| {
19227 if let Some(file) = file {
19228 file.is_private()
19229 && EditorSettings::get(
19230 Some(SettingsLocation {
19231 worktree_id: file.worktree_id(cx),
19232 path: file.path().as_ref(),
19233 }),
19234 cx,
19235 )
19236 .redact_private_values
19237 } else {
19238 false
19239 }
19240 })
19241 .map(|range| {
19242 range.start.to_display_point(display_snapshot)
19243 ..range.end.to_display_point(display_snapshot)
19244 })
19245 .collect()
19246 }
19247
19248 pub fn highlight_text_key<T: 'static>(
19249 &mut self,
19250 key: usize,
19251 ranges: Vec<Range<Anchor>>,
19252 style: HighlightStyle,
19253 cx: &mut Context<Self>,
19254 ) {
19255 self.display_map.update(cx, |map, _| {
19256 map.highlight_text(
19257 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19258 ranges,
19259 style,
19260 );
19261 });
19262 cx.notify();
19263 }
19264
19265 pub fn highlight_text<T: 'static>(
19266 &mut self,
19267 ranges: Vec<Range<Anchor>>,
19268 style: HighlightStyle,
19269 cx: &mut Context<Self>,
19270 ) {
19271 self.display_map.update(cx, |map, _| {
19272 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19273 });
19274 cx.notify();
19275 }
19276
19277 pub(crate) fn highlight_inlays<T: 'static>(
19278 &mut self,
19279 highlights: Vec<InlayHighlight>,
19280 style: HighlightStyle,
19281 cx: &mut Context<Self>,
19282 ) {
19283 self.display_map.update(cx, |map, _| {
19284 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19285 });
19286 cx.notify();
19287 }
19288
19289 pub fn text_highlights<'a, T: 'static>(
19290 &'a self,
19291 cx: &'a App,
19292 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19293 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19294 }
19295
19296 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19297 let cleared = self
19298 .display_map
19299 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19300 if cleared {
19301 cx.notify();
19302 }
19303 }
19304
19305 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19306 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19307 && self.focus_handle.is_focused(window)
19308 }
19309
19310 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19311 self.show_cursor_when_unfocused = is_enabled;
19312 cx.notify();
19313 }
19314
19315 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19316 cx.notify();
19317 }
19318
19319 fn on_debug_session_event(
19320 &mut self,
19321 _session: Entity<Session>,
19322 event: &SessionEvent,
19323 cx: &mut Context<Self>,
19324 ) {
19325 match event {
19326 SessionEvent::InvalidateInlineValue => {
19327 self.refresh_inline_values(cx);
19328 }
19329 _ => {}
19330 }
19331 }
19332
19333 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19334 let Some(project) = self.project.clone() else {
19335 return;
19336 };
19337
19338 if !self.inline_value_cache.enabled {
19339 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19340 self.splice_inlays(&inlays, Vec::new(), cx);
19341 return;
19342 }
19343
19344 let current_execution_position = self
19345 .highlighted_rows
19346 .get(&TypeId::of::<ActiveDebugLine>())
19347 .and_then(|lines| lines.last().map(|line| line.range.end));
19348
19349 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19350 let inline_values = editor
19351 .update(cx, |editor, cx| {
19352 let Some(current_execution_position) = current_execution_position else {
19353 return Some(Task::ready(Ok(Vec::new())));
19354 };
19355
19356 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19357 let snapshot = buffer.snapshot(cx);
19358
19359 let excerpt = snapshot.excerpt_containing(
19360 current_execution_position..current_execution_position,
19361 )?;
19362
19363 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19364 })?;
19365
19366 let range =
19367 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19368
19369 project.inline_values(buffer, range, cx)
19370 })
19371 .ok()
19372 .flatten()?
19373 .await
19374 .context("refreshing debugger inlays")
19375 .log_err()?;
19376
19377 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19378
19379 for (buffer_id, inline_value) in inline_values
19380 .into_iter()
19381 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19382 {
19383 buffer_inline_values
19384 .entry(buffer_id)
19385 .or_default()
19386 .push(inline_value);
19387 }
19388
19389 editor
19390 .update(cx, |editor, cx| {
19391 let snapshot = editor.buffer.read(cx).snapshot(cx);
19392 let mut new_inlays = Vec::default();
19393
19394 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19395 let buffer_id = buffer_snapshot.remote_id();
19396 buffer_inline_values
19397 .get(&buffer_id)
19398 .into_iter()
19399 .flatten()
19400 .for_each(|hint| {
19401 let inlay = Inlay::debugger(
19402 post_inc(&mut editor.next_inlay_id),
19403 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19404 hint.text(),
19405 );
19406
19407 new_inlays.push(inlay);
19408 });
19409 }
19410
19411 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19412 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19413
19414 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19415 })
19416 .ok()?;
19417 Some(())
19418 });
19419 }
19420
19421 fn on_buffer_event(
19422 &mut self,
19423 multibuffer: &Entity<MultiBuffer>,
19424 event: &multi_buffer::Event,
19425 window: &mut Window,
19426 cx: &mut Context<Self>,
19427 ) {
19428 match event {
19429 multi_buffer::Event::Edited {
19430 singleton_buffer_edited,
19431 edited_buffer,
19432 } => {
19433 self.scrollbar_marker_state.dirty = true;
19434 self.active_indent_guides_state.dirty = true;
19435 self.refresh_active_diagnostics(cx);
19436 self.refresh_code_actions(window, cx);
19437 self.refresh_selected_text_highlights(true, window, cx);
19438 refresh_matching_bracket_highlights(self, window, cx);
19439 if self.has_active_inline_completion() {
19440 self.update_visible_inline_completion(window, cx);
19441 }
19442 if let Some(project) = self.project.as_ref() {
19443 if let Some(edited_buffer) = edited_buffer {
19444 project.update(cx, |project, cx| {
19445 self.registered_buffers
19446 .entry(edited_buffer.read(cx).remote_id())
19447 .or_insert_with(|| {
19448 project
19449 .register_buffer_with_language_servers(&edited_buffer, cx)
19450 });
19451 });
19452 }
19453 }
19454 cx.emit(EditorEvent::BufferEdited);
19455 cx.emit(SearchEvent::MatchesInvalidated);
19456
19457 if let Some(buffer) = edited_buffer {
19458 self.update_lsp_data(None, Some(buffer.read(cx).remote_id()), window, cx);
19459 }
19460
19461 if *singleton_buffer_edited {
19462 if let Some(buffer) = edited_buffer {
19463 if buffer.read(cx).file().is_none() {
19464 cx.emit(EditorEvent::TitleChanged);
19465 }
19466 }
19467 if let Some(project) = &self.project {
19468 #[allow(clippy::mutable_key_type)]
19469 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19470 multibuffer
19471 .all_buffers()
19472 .into_iter()
19473 .filter_map(|buffer| {
19474 buffer.update(cx, |buffer, cx| {
19475 let language = buffer.language()?;
19476 let should_discard = project.update(cx, |project, cx| {
19477 project.is_local()
19478 && !project.has_language_servers_for(buffer, cx)
19479 });
19480 should_discard.not().then_some(language.clone())
19481 })
19482 })
19483 .collect::<HashSet<_>>()
19484 });
19485 if !languages_affected.is_empty() {
19486 self.refresh_inlay_hints(
19487 InlayHintRefreshReason::BufferEdited(languages_affected),
19488 cx,
19489 );
19490 }
19491 }
19492 }
19493
19494 let Some(project) = &self.project else { return };
19495 let (telemetry, is_via_ssh) = {
19496 let project = project.read(cx);
19497 let telemetry = project.client().telemetry().clone();
19498 let is_via_ssh = project.is_via_ssh();
19499 (telemetry, is_via_ssh)
19500 };
19501 refresh_linked_ranges(self, window, cx);
19502 telemetry.log_edit_event("editor", is_via_ssh);
19503 }
19504 multi_buffer::Event::ExcerptsAdded {
19505 buffer,
19506 predecessor,
19507 excerpts,
19508 } => {
19509 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19510 let buffer_id = buffer.read(cx).remote_id();
19511 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19512 if let Some(project) = &self.project {
19513 update_uncommitted_diff_for_buffer(
19514 cx.entity(),
19515 project,
19516 [buffer.clone()],
19517 self.buffer.clone(),
19518 cx,
19519 )
19520 .detach();
19521 }
19522 }
19523 self.update_lsp_data(None, Some(buffer_id), window, cx);
19524 cx.emit(EditorEvent::ExcerptsAdded {
19525 buffer: buffer.clone(),
19526 predecessor: *predecessor,
19527 excerpts: excerpts.clone(),
19528 });
19529 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19530 }
19531 multi_buffer::Event::ExcerptsRemoved {
19532 ids,
19533 removed_buffer_ids,
19534 } => {
19535 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19536 let buffer = self.buffer.read(cx);
19537 self.registered_buffers
19538 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19539 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19540 cx.emit(EditorEvent::ExcerptsRemoved {
19541 ids: ids.clone(),
19542 removed_buffer_ids: removed_buffer_ids.clone(),
19543 });
19544 }
19545 multi_buffer::Event::ExcerptsEdited {
19546 excerpt_ids,
19547 buffer_ids,
19548 } => {
19549 self.display_map.update(cx, |map, cx| {
19550 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19551 });
19552 cx.emit(EditorEvent::ExcerptsEdited {
19553 ids: excerpt_ids.clone(),
19554 });
19555 }
19556 multi_buffer::Event::ExcerptsExpanded { ids } => {
19557 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19558 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19559 }
19560 multi_buffer::Event::Reparsed(buffer_id) => {
19561 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19562 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19563
19564 cx.emit(EditorEvent::Reparsed(*buffer_id));
19565 }
19566 multi_buffer::Event::DiffHunksToggled => {
19567 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19568 }
19569 multi_buffer::Event::LanguageChanged(buffer_id) => {
19570 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19571 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19572 cx.emit(EditorEvent::Reparsed(*buffer_id));
19573 cx.notify();
19574 }
19575 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19576 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19577 multi_buffer::Event::FileHandleChanged
19578 | multi_buffer::Event::Reloaded
19579 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19580 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19581 multi_buffer::Event::DiagnosticsUpdated => {
19582 self.update_diagnostics_state(window, cx);
19583 }
19584 _ => {}
19585 };
19586 }
19587
19588 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19589 if !self.diagnostics_enabled() {
19590 return;
19591 }
19592 self.refresh_active_diagnostics(cx);
19593 self.refresh_inline_diagnostics(true, window, cx);
19594 self.scrollbar_marker_state.dirty = true;
19595 cx.notify();
19596 }
19597
19598 pub fn start_temporary_diff_override(&mut self) {
19599 self.load_diff_task.take();
19600 self.temporary_diff_override = true;
19601 }
19602
19603 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19604 self.temporary_diff_override = false;
19605 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19606 self.buffer.update(cx, |buffer, cx| {
19607 buffer.set_all_diff_hunks_collapsed(cx);
19608 });
19609
19610 if let Some(project) = self.project.clone() {
19611 self.load_diff_task = Some(
19612 update_uncommitted_diff_for_buffer(
19613 cx.entity(),
19614 &project,
19615 self.buffer.read(cx).all_buffers(),
19616 self.buffer.clone(),
19617 cx,
19618 )
19619 .shared(),
19620 );
19621 }
19622 }
19623
19624 fn on_display_map_changed(
19625 &mut self,
19626 _: Entity<DisplayMap>,
19627 _: &mut Window,
19628 cx: &mut Context<Self>,
19629 ) {
19630 cx.notify();
19631 }
19632
19633 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19634 let new_severity = if self.diagnostics_enabled() {
19635 EditorSettings::get_global(cx)
19636 .diagnostics_max_severity
19637 .unwrap_or(DiagnosticSeverity::Hint)
19638 } else {
19639 DiagnosticSeverity::Off
19640 };
19641 self.set_max_diagnostics_severity(new_severity, cx);
19642 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19643 self.update_edit_prediction_settings(cx);
19644 self.refresh_inline_completion(true, false, window, cx);
19645 self.refresh_inlay_hints(
19646 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19647 self.selections.newest_anchor().head(),
19648 &self.buffer.read(cx).snapshot(cx),
19649 cx,
19650 )),
19651 cx,
19652 );
19653
19654 let old_cursor_shape = self.cursor_shape;
19655
19656 {
19657 let editor_settings = EditorSettings::get_global(cx);
19658 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19659 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19660 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19661 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19662 self.drag_and_drop_selection_enabled = editor_settings.drag_and_drop_selection;
19663 }
19664
19665 if old_cursor_shape != self.cursor_shape {
19666 cx.emit(EditorEvent::CursorShapeChanged);
19667 }
19668
19669 let project_settings = ProjectSettings::get_global(cx);
19670 self.serialize_dirty_buffers =
19671 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19672
19673 if self.mode.is_full() {
19674 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19675 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19676 if self.show_inline_diagnostics != show_inline_diagnostics {
19677 self.show_inline_diagnostics = show_inline_diagnostics;
19678 self.refresh_inline_diagnostics(false, window, cx);
19679 }
19680
19681 if self.git_blame_inline_enabled != inline_blame_enabled {
19682 self.toggle_git_blame_inline_internal(false, window, cx);
19683 }
19684
19685 let minimap_settings = EditorSettings::get_global(cx).minimap;
19686 if self.minimap_visibility != MinimapVisibility::Disabled {
19687 if self.minimap_visibility.settings_visibility()
19688 != minimap_settings.minimap_enabled()
19689 {
19690 self.set_minimap_visibility(
19691 MinimapVisibility::for_mode(self.mode(), cx),
19692 window,
19693 cx,
19694 );
19695 } else if let Some(minimap_entity) = self.minimap.as_ref() {
19696 minimap_entity.update(cx, |minimap_editor, cx| {
19697 minimap_editor.update_minimap_configuration(minimap_settings, cx)
19698 })
19699 }
19700 }
19701 }
19702
19703 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
19704 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
19705 }) {
19706 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
19707 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
19708 }
19709 self.refresh_colors(None, None, window, cx);
19710 }
19711
19712 cx.notify();
19713 }
19714
19715 pub fn set_searchable(&mut self, searchable: bool) {
19716 self.searchable = searchable;
19717 }
19718
19719 pub fn searchable(&self) -> bool {
19720 self.searchable
19721 }
19722
19723 fn open_proposed_changes_editor(
19724 &mut self,
19725 _: &OpenProposedChangesEditor,
19726 window: &mut Window,
19727 cx: &mut Context<Self>,
19728 ) {
19729 let Some(workspace) = self.workspace() else {
19730 cx.propagate();
19731 return;
19732 };
19733
19734 let selections = self.selections.all::<usize>(cx);
19735 let multi_buffer = self.buffer.read(cx);
19736 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19737 let mut new_selections_by_buffer = HashMap::default();
19738 for selection in selections {
19739 for (buffer, range, _) in
19740 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
19741 {
19742 let mut range = range.to_point(buffer);
19743 range.start.column = 0;
19744 range.end.column = buffer.line_len(range.end.row);
19745 new_selections_by_buffer
19746 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
19747 .or_insert(Vec::new())
19748 .push(range)
19749 }
19750 }
19751
19752 let proposed_changes_buffers = new_selections_by_buffer
19753 .into_iter()
19754 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
19755 .collect::<Vec<_>>();
19756 let proposed_changes_editor = cx.new(|cx| {
19757 ProposedChangesEditor::new(
19758 "Proposed changes",
19759 proposed_changes_buffers,
19760 self.project.clone(),
19761 window,
19762 cx,
19763 )
19764 });
19765
19766 window.defer(cx, move |window, cx| {
19767 workspace.update(cx, |workspace, cx| {
19768 workspace.active_pane().update(cx, |pane, cx| {
19769 pane.add_item(
19770 Box::new(proposed_changes_editor),
19771 true,
19772 true,
19773 None,
19774 window,
19775 cx,
19776 );
19777 });
19778 });
19779 });
19780 }
19781
19782 pub fn open_excerpts_in_split(
19783 &mut self,
19784 _: &OpenExcerptsSplit,
19785 window: &mut Window,
19786 cx: &mut Context<Self>,
19787 ) {
19788 self.open_excerpts_common(None, true, window, cx)
19789 }
19790
19791 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
19792 self.open_excerpts_common(None, false, window, cx)
19793 }
19794
19795 fn open_excerpts_common(
19796 &mut self,
19797 jump_data: Option<JumpData>,
19798 split: bool,
19799 window: &mut Window,
19800 cx: &mut Context<Self>,
19801 ) {
19802 let Some(workspace) = self.workspace() else {
19803 cx.propagate();
19804 return;
19805 };
19806
19807 if self.buffer.read(cx).is_singleton() {
19808 cx.propagate();
19809 return;
19810 }
19811
19812 let mut new_selections_by_buffer = HashMap::default();
19813 match &jump_data {
19814 Some(JumpData::MultiBufferPoint {
19815 excerpt_id,
19816 position,
19817 anchor,
19818 line_offset_from_top,
19819 }) => {
19820 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19821 if let Some(buffer) = multi_buffer_snapshot
19822 .buffer_id_for_excerpt(*excerpt_id)
19823 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
19824 {
19825 let buffer_snapshot = buffer.read(cx).snapshot();
19826 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
19827 language::ToPoint::to_point(anchor, &buffer_snapshot)
19828 } else {
19829 buffer_snapshot.clip_point(*position, Bias::Left)
19830 };
19831 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
19832 new_selections_by_buffer.insert(
19833 buffer,
19834 (
19835 vec![jump_to_offset..jump_to_offset],
19836 Some(*line_offset_from_top),
19837 ),
19838 );
19839 }
19840 }
19841 Some(JumpData::MultiBufferRow {
19842 row,
19843 line_offset_from_top,
19844 }) => {
19845 let point = MultiBufferPoint::new(row.0, 0);
19846 if let Some((buffer, buffer_point, _)) =
19847 self.buffer.read(cx).point_to_buffer_point(point, cx)
19848 {
19849 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
19850 new_selections_by_buffer
19851 .entry(buffer)
19852 .or_insert((Vec::new(), Some(*line_offset_from_top)))
19853 .0
19854 .push(buffer_offset..buffer_offset)
19855 }
19856 }
19857 None => {
19858 let selections = self.selections.all::<usize>(cx);
19859 let multi_buffer = self.buffer.read(cx);
19860 for selection in selections {
19861 for (snapshot, range, _, anchor) in multi_buffer
19862 .snapshot(cx)
19863 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
19864 {
19865 if let Some(anchor) = anchor {
19866 // selection is in a deleted hunk
19867 let Some(buffer_id) = anchor.buffer_id else {
19868 continue;
19869 };
19870 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
19871 continue;
19872 };
19873 let offset = text::ToOffset::to_offset(
19874 &anchor.text_anchor,
19875 &buffer_handle.read(cx).snapshot(),
19876 );
19877 let range = offset..offset;
19878 new_selections_by_buffer
19879 .entry(buffer_handle)
19880 .or_insert((Vec::new(), None))
19881 .0
19882 .push(range)
19883 } else {
19884 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
19885 else {
19886 continue;
19887 };
19888 new_selections_by_buffer
19889 .entry(buffer_handle)
19890 .or_insert((Vec::new(), None))
19891 .0
19892 .push(range)
19893 }
19894 }
19895 }
19896 }
19897 }
19898
19899 new_selections_by_buffer
19900 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
19901
19902 if new_selections_by_buffer.is_empty() {
19903 return;
19904 }
19905
19906 // We defer the pane interaction because we ourselves are a workspace item
19907 // and activating a new item causes the pane to call a method on us reentrantly,
19908 // which panics if we're on the stack.
19909 window.defer(cx, move |window, cx| {
19910 workspace.update(cx, |workspace, cx| {
19911 let pane = if split {
19912 workspace.adjacent_pane(window, cx)
19913 } else {
19914 workspace.active_pane().clone()
19915 };
19916
19917 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
19918 let editor = buffer
19919 .read(cx)
19920 .file()
19921 .is_none()
19922 .then(|| {
19923 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
19924 // so `workspace.open_project_item` will never find them, always opening a new editor.
19925 // Instead, we try to activate the existing editor in the pane first.
19926 let (editor, pane_item_index) =
19927 pane.read(cx).items().enumerate().find_map(|(i, item)| {
19928 let editor = item.downcast::<Editor>()?;
19929 let singleton_buffer =
19930 editor.read(cx).buffer().read(cx).as_singleton()?;
19931 if singleton_buffer == buffer {
19932 Some((editor, i))
19933 } else {
19934 None
19935 }
19936 })?;
19937 pane.update(cx, |pane, cx| {
19938 pane.activate_item(pane_item_index, true, true, window, cx)
19939 });
19940 Some(editor)
19941 })
19942 .flatten()
19943 .unwrap_or_else(|| {
19944 workspace.open_project_item::<Self>(
19945 pane.clone(),
19946 buffer,
19947 true,
19948 true,
19949 window,
19950 cx,
19951 )
19952 });
19953
19954 editor.update(cx, |editor, cx| {
19955 let autoscroll = match scroll_offset {
19956 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
19957 None => Autoscroll::newest(),
19958 };
19959 let nav_history = editor.nav_history.take();
19960 editor.change_selections(Some(autoscroll), window, cx, |s| {
19961 s.select_ranges(ranges);
19962 });
19963 editor.nav_history = nav_history;
19964 });
19965 }
19966 })
19967 });
19968 }
19969
19970 // For now, don't allow opening excerpts in buffers that aren't backed by
19971 // regular project files.
19972 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
19973 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
19974 }
19975
19976 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
19977 let snapshot = self.buffer.read(cx).read(cx);
19978 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
19979 Some(
19980 ranges
19981 .iter()
19982 .map(move |range| {
19983 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19984 })
19985 .collect(),
19986 )
19987 }
19988
19989 fn selection_replacement_ranges(
19990 &self,
19991 range: Range<OffsetUtf16>,
19992 cx: &mut App,
19993 ) -> Vec<Range<OffsetUtf16>> {
19994 let selections = self.selections.all::<OffsetUtf16>(cx);
19995 let newest_selection = selections
19996 .iter()
19997 .max_by_key(|selection| selection.id)
19998 .unwrap();
19999 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20000 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20001 let snapshot = self.buffer.read(cx).read(cx);
20002 selections
20003 .into_iter()
20004 .map(|mut selection| {
20005 selection.start.0 =
20006 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20007 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20008 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20009 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20010 })
20011 .collect()
20012 }
20013
20014 fn report_editor_event(
20015 &self,
20016 event_type: &'static str,
20017 file_extension: Option<String>,
20018 cx: &App,
20019 ) {
20020 if cfg!(any(test, feature = "test-support")) {
20021 return;
20022 }
20023
20024 let Some(project) = &self.project else { return };
20025
20026 // If None, we are in a file without an extension
20027 let file = self
20028 .buffer
20029 .read(cx)
20030 .as_singleton()
20031 .and_then(|b| b.read(cx).file());
20032 let file_extension = file_extension.or(file
20033 .as_ref()
20034 .and_then(|file| Path::new(file.file_name(cx)).extension())
20035 .and_then(|e| e.to_str())
20036 .map(|a| a.to_string()));
20037
20038 let vim_mode = vim_enabled(cx);
20039
20040 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20041 let copilot_enabled = edit_predictions_provider
20042 == language::language_settings::EditPredictionProvider::Copilot;
20043 let copilot_enabled_for_language = self
20044 .buffer
20045 .read(cx)
20046 .language_settings(cx)
20047 .show_edit_predictions;
20048
20049 let project = project.read(cx);
20050 telemetry::event!(
20051 event_type,
20052 file_extension,
20053 vim_mode,
20054 copilot_enabled,
20055 copilot_enabled_for_language,
20056 edit_predictions_provider,
20057 is_via_ssh = project.is_via_ssh(),
20058 );
20059 }
20060
20061 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20062 /// with each line being an array of {text, highlight} objects.
20063 fn copy_highlight_json(
20064 &mut self,
20065 _: &CopyHighlightJson,
20066 window: &mut Window,
20067 cx: &mut Context<Self>,
20068 ) {
20069 #[derive(Serialize)]
20070 struct Chunk<'a> {
20071 text: String,
20072 highlight: Option<&'a str>,
20073 }
20074
20075 let snapshot = self.buffer.read(cx).snapshot(cx);
20076 let range = self
20077 .selected_text_range(false, window, cx)
20078 .and_then(|selection| {
20079 if selection.range.is_empty() {
20080 None
20081 } else {
20082 Some(selection.range)
20083 }
20084 })
20085 .unwrap_or_else(|| 0..snapshot.len());
20086
20087 let chunks = snapshot.chunks(range, true);
20088 let mut lines = Vec::new();
20089 let mut line: VecDeque<Chunk> = VecDeque::new();
20090
20091 let Some(style) = self.style.as_ref() else {
20092 return;
20093 };
20094
20095 for chunk in chunks {
20096 let highlight = chunk
20097 .syntax_highlight_id
20098 .and_then(|id| id.name(&style.syntax));
20099 let mut chunk_lines = chunk.text.split('\n').peekable();
20100 while let Some(text) = chunk_lines.next() {
20101 let mut merged_with_last_token = false;
20102 if let Some(last_token) = line.back_mut() {
20103 if last_token.highlight == highlight {
20104 last_token.text.push_str(text);
20105 merged_with_last_token = true;
20106 }
20107 }
20108
20109 if !merged_with_last_token {
20110 line.push_back(Chunk {
20111 text: text.into(),
20112 highlight,
20113 });
20114 }
20115
20116 if chunk_lines.peek().is_some() {
20117 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20118 line.pop_front();
20119 }
20120 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20121 line.pop_back();
20122 }
20123
20124 lines.push(mem::take(&mut line));
20125 }
20126 }
20127 }
20128
20129 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20130 return;
20131 };
20132 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20133 }
20134
20135 pub fn open_context_menu(
20136 &mut self,
20137 _: &OpenContextMenu,
20138 window: &mut Window,
20139 cx: &mut Context<Self>,
20140 ) {
20141 self.request_autoscroll(Autoscroll::newest(), cx);
20142 let position = self.selections.newest_display(cx).start;
20143 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20144 }
20145
20146 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20147 &self.inlay_hint_cache
20148 }
20149
20150 pub fn replay_insert_event(
20151 &mut self,
20152 text: &str,
20153 relative_utf16_range: Option<Range<isize>>,
20154 window: &mut Window,
20155 cx: &mut Context<Self>,
20156 ) {
20157 if !self.input_enabled {
20158 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20159 return;
20160 }
20161 if let Some(relative_utf16_range) = relative_utf16_range {
20162 let selections = self.selections.all::<OffsetUtf16>(cx);
20163 self.change_selections(None, window, cx, |s| {
20164 let new_ranges = selections.into_iter().map(|range| {
20165 let start = OffsetUtf16(
20166 range
20167 .head()
20168 .0
20169 .saturating_add_signed(relative_utf16_range.start),
20170 );
20171 let end = OffsetUtf16(
20172 range
20173 .head()
20174 .0
20175 .saturating_add_signed(relative_utf16_range.end),
20176 );
20177 start..end
20178 });
20179 s.select_ranges(new_ranges);
20180 });
20181 }
20182
20183 self.handle_input(text, window, cx);
20184 }
20185
20186 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20187 let Some(provider) = self.semantics_provider.as_ref() else {
20188 return false;
20189 };
20190
20191 let mut supports = false;
20192 self.buffer().update(cx, |this, cx| {
20193 this.for_each_buffer(|buffer| {
20194 supports |= provider.supports_inlay_hints(buffer, cx);
20195 });
20196 });
20197
20198 supports
20199 }
20200
20201 pub fn is_focused(&self, window: &Window) -> bool {
20202 self.focus_handle.is_focused(window)
20203 }
20204
20205 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20206 cx.emit(EditorEvent::Focused);
20207
20208 if let Some(descendant) = self
20209 .last_focused_descendant
20210 .take()
20211 .and_then(|descendant| descendant.upgrade())
20212 {
20213 window.focus(&descendant);
20214 } else {
20215 if let Some(blame) = self.blame.as_ref() {
20216 blame.update(cx, GitBlame::focus)
20217 }
20218
20219 self.blink_manager.update(cx, BlinkManager::enable);
20220 self.show_cursor_names(window, cx);
20221 self.buffer.update(cx, |buffer, cx| {
20222 buffer.finalize_last_transaction(cx);
20223 if self.leader_id.is_none() {
20224 buffer.set_active_selections(
20225 &self.selections.disjoint_anchors(),
20226 self.selections.line_mode,
20227 self.cursor_shape,
20228 cx,
20229 );
20230 }
20231 });
20232 }
20233 }
20234
20235 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20236 cx.emit(EditorEvent::FocusedIn)
20237 }
20238
20239 fn handle_focus_out(
20240 &mut self,
20241 event: FocusOutEvent,
20242 _window: &mut Window,
20243 cx: &mut Context<Self>,
20244 ) {
20245 if event.blurred != self.focus_handle {
20246 self.last_focused_descendant = Some(event.blurred);
20247 }
20248 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20249 }
20250
20251 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20252 self.blink_manager.update(cx, BlinkManager::disable);
20253 self.buffer
20254 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20255
20256 if let Some(blame) = self.blame.as_ref() {
20257 blame.update(cx, GitBlame::blur)
20258 }
20259 if !self.hover_state.focused(window, cx) {
20260 hide_hover(self, cx);
20261 }
20262 if !self
20263 .context_menu
20264 .borrow()
20265 .as_ref()
20266 .is_some_and(|context_menu| context_menu.focused(window, cx))
20267 {
20268 self.hide_context_menu(window, cx);
20269 }
20270 self.discard_inline_completion(false, cx);
20271 cx.emit(EditorEvent::Blurred);
20272 cx.notify();
20273 }
20274
20275 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20276 let mut pending: String = window
20277 .pending_input_keystrokes()
20278 .into_iter()
20279 .flatten()
20280 .filter_map(|keystroke| {
20281 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20282 keystroke.key_char.clone()
20283 } else {
20284 None
20285 }
20286 })
20287 .collect();
20288
20289 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20290 pending = "".to_string();
20291 }
20292
20293 let existing_pending = self
20294 .text_highlights::<PendingInput>(cx)
20295 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20296 if existing_pending.is_none() && pending.is_empty() {
20297 return;
20298 }
20299 let transaction =
20300 self.transact(window, cx, |this, window, cx| {
20301 let selections = this.selections.all::<usize>(cx);
20302 let edits = selections
20303 .iter()
20304 .map(|selection| (selection.end..selection.end, pending.clone()));
20305 this.edit(edits, cx);
20306 this.change_selections(None, window, cx, |s| {
20307 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20308 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20309 }));
20310 });
20311 if let Some(existing_ranges) = existing_pending {
20312 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20313 this.edit(edits, cx);
20314 }
20315 });
20316
20317 let snapshot = self.snapshot(window, cx);
20318 let ranges = self
20319 .selections
20320 .all::<usize>(cx)
20321 .into_iter()
20322 .map(|selection| {
20323 snapshot.buffer_snapshot.anchor_after(selection.end)
20324 ..snapshot
20325 .buffer_snapshot
20326 .anchor_before(selection.end + pending.len())
20327 })
20328 .collect();
20329
20330 if pending.is_empty() {
20331 self.clear_highlights::<PendingInput>(cx);
20332 } else {
20333 self.highlight_text::<PendingInput>(
20334 ranges,
20335 HighlightStyle {
20336 underline: Some(UnderlineStyle {
20337 thickness: px(1.),
20338 color: None,
20339 wavy: false,
20340 }),
20341 ..Default::default()
20342 },
20343 cx,
20344 );
20345 }
20346
20347 self.ime_transaction = self.ime_transaction.or(transaction);
20348 if let Some(transaction) = self.ime_transaction {
20349 self.buffer.update(cx, |buffer, cx| {
20350 buffer.group_until_transaction(transaction, cx);
20351 });
20352 }
20353
20354 if self.text_highlights::<PendingInput>(cx).is_none() {
20355 self.ime_transaction.take();
20356 }
20357 }
20358
20359 pub fn register_action_renderer(
20360 &mut self,
20361 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20362 ) -> Subscription {
20363 let id = self.next_editor_action_id.post_inc();
20364 self.editor_actions
20365 .borrow_mut()
20366 .insert(id, Box::new(listener));
20367
20368 let editor_actions = self.editor_actions.clone();
20369 Subscription::new(move || {
20370 editor_actions.borrow_mut().remove(&id);
20371 })
20372 }
20373
20374 pub fn register_action<A: Action>(
20375 &mut self,
20376 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20377 ) -> Subscription {
20378 let id = self.next_editor_action_id.post_inc();
20379 let listener = Arc::new(listener);
20380 self.editor_actions.borrow_mut().insert(
20381 id,
20382 Box::new(move |_, window, _| {
20383 let listener = listener.clone();
20384 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20385 let action = action.downcast_ref().unwrap();
20386 if phase == DispatchPhase::Bubble {
20387 listener(action, window, cx)
20388 }
20389 })
20390 }),
20391 );
20392
20393 let editor_actions = self.editor_actions.clone();
20394 Subscription::new(move || {
20395 editor_actions.borrow_mut().remove(&id);
20396 })
20397 }
20398
20399 pub fn file_header_size(&self) -> u32 {
20400 FILE_HEADER_HEIGHT
20401 }
20402
20403 pub fn restore(
20404 &mut self,
20405 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20406 window: &mut Window,
20407 cx: &mut Context<Self>,
20408 ) {
20409 let workspace = self.workspace();
20410 let project = self.project.as_ref();
20411 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20412 let mut tasks = Vec::new();
20413 for (buffer_id, changes) in revert_changes {
20414 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20415 buffer.update(cx, |buffer, cx| {
20416 buffer.edit(
20417 changes
20418 .into_iter()
20419 .map(|(range, text)| (range, text.to_string())),
20420 None,
20421 cx,
20422 );
20423 });
20424
20425 if let Some(project) =
20426 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20427 {
20428 project.update(cx, |project, cx| {
20429 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20430 })
20431 }
20432 }
20433 }
20434 tasks
20435 });
20436 cx.spawn_in(window, async move |_, cx| {
20437 for (buffer, task) in save_tasks {
20438 let result = task.await;
20439 if result.is_err() {
20440 let Some(path) = buffer
20441 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20442 .ok()
20443 else {
20444 continue;
20445 };
20446 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20447 let Some(task) = cx
20448 .update_window_entity(&workspace, |workspace, window, cx| {
20449 workspace
20450 .open_path_preview(path, None, false, false, false, window, cx)
20451 })
20452 .ok()
20453 else {
20454 continue;
20455 };
20456 task.await.log_err();
20457 }
20458 }
20459 }
20460 })
20461 .detach();
20462 self.change_selections(None, window, cx, |selections| selections.refresh());
20463 }
20464
20465 pub fn to_pixel_point(
20466 &self,
20467 source: multi_buffer::Anchor,
20468 editor_snapshot: &EditorSnapshot,
20469 window: &mut Window,
20470 ) -> Option<gpui::Point<Pixels>> {
20471 let source_point = source.to_display_point(editor_snapshot);
20472 self.display_to_pixel_point(source_point, editor_snapshot, window)
20473 }
20474
20475 pub fn display_to_pixel_point(
20476 &self,
20477 source: DisplayPoint,
20478 editor_snapshot: &EditorSnapshot,
20479 window: &mut Window,
20480 ) -> Option<gpui::Point<Pixels>> {
20481 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20482 let text_layout_details = self.text_layout_details(window);
20483 let scroll_top = text_layout_details
20484 .scroll_anchor
20485 .scroll_position(editor_snapshot)
20486 .y;
20487
20488 if source.row().as_f32() < scroll_top.floor() {
20489 return None;
20490 }
20491 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20492 let source_y = line_height * (source.row().as_f32() - scroll_top);
20493 Some(gpui::Point::new(source_x, source_y))
20494 }
20495
20496 pub fn has_visible_completions_menu(&self) -> bool {
20497 !self.edit_prediction_preview_is_active()
20498 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20499 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20500 })
20501 }
20502
20503 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20504 if self.mode.is_minimap() {
20505 return;
20506 }
20507 self.addons
20508 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20509 }
20510
20511 pub fn unregister_addon<T: Addon>(&mut self) {
20512 self.addons.remove(&std::any::TypeId::of::<T>());
20513 }
20514
20515 pub fn addon<T: Addon>(&self) -> Option<&T> {
20516 let type_id = std::any::TypeId::of::<T>();
20517 self.addons
20518 .get(&type_id)
20519 .and_then(|item| item.to_any().downcast_ref::<T>())
20520 }
20521
20522 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20523 let type_id = std::any::TypeId::of::<T>();
20524 self.addons
20525 .get_mut(&type_id)
20526 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20527 }
20528
20529 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
20530 let text_layout_details = self.text_layout_details(window);
20531 let style = &text_layout_details.editor_style;
20532 let font_id = window.text_system().resolve_font(&style.text.font());
20533 let font_size = style.text.font_size.to_pixels(window.rem_size());
20534 let line_height = style.text.line_height_in_pixels(window.rem_size());
20535 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20536 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
20537
20538 CharacterDimensions {
20539 em_width,
20540 em_advance,
20541 line_height,
20542 }
20543 }
20544
20545 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20546 self.load_diff_task.clone()
20547 }
20548
20549 fn read_metadata_from_db(
20550 &mut self,
20551 item_id: u64,
20552 workspace_id: WorkspaceId,
20553 window: &mut Window,
20554 cx: &mut Context<Editor>,
20555 ) {
20556 if self.is_singleton(cx)
20557 && !self.mode.is_minimap()
20558 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20559 {
20560 let buffer_snapshot = OnceCell::new();
20561
20562 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20563 if !folds.is_empty() {
20564 let snapshot =
20565 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20566 self.fold_ranges(
20567 folds
20568 .into_iter()
20569 .map(|(start, end)| {
20570 snapshot.clip_offset(start, Bias::Left)
20571 ..snapshot.clip_offset(end, Bias::Right)
20572 })
20573 .collect(),
20574 false,
20575 window,
20576 cx,
20577 );
20578 }
20579 }
20580
20581 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20582 if !selections.is_empty() {
20583 let snapshot =
20584 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20585 // skip adding the initial selection to selection history
20586 self.selection_history.mode = SelectionHistoryMode::Skipping;
20587 self.change_selections(None, window, cx, |s| {
20588 s.select_ranges(selections.into_iter().map(|(start, end)| {
20589 snapshot.clip_offset(start, Bias::Left)
20590 ..snapshot.clip_offset(end, Bias::Right)
20591 }));
20592 });
20593 self.selection_history.mode = SelectionHistoryMode::Normal;
20594 }
20595 };
20596 }
20597
20598 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20599 }
20600
20601 fn update_lsp_data(
20602 &mut self,
20603 for_server_id: Option<LanguageServerId>,
20604 for_buffer: Option<BufferId>,
20605 window: &mut Window,
20606 cx: &mut Context<'_, Self>,
20607 ) {
20608 self.pull_diagnostics(for_buffer, window, cx);
20609 self.refresh_colors(for_server_id, for_buffer, window, cx);
20610 }
20611}
20612
20613fn vim_enabled(cx: &App) -> bool {
20614 cx.global::<SettingsStore>()
20615 .raw_user_settings()
20616 .get("vim_mode")
20617 == Some(&serde_json::Value::Bool(true))
20618}
20619
20620fn process_completion_for_edit(
20621 completion: &Completion,
20622 intent: CompletionIntent,
20623 buffer: &Entity<Buffer>,
20624 cursor_position: &text::Anchor,
20625 cx: &mut Context<Editor>,
20626) -> CompletionEdit {
20627 let buffer = buffer.read(cx);
20628 let buffer_snapshot = buffer.snapshot();
20629 let (snippet, new_text) = if completion.is_snippet() {
20630 // Workaround for typescript language server issues so that methods don't expand within
20631 // strings and functions with type expressions. The previous point is used because the query
20632 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
20633 let mut snippet_source = completion.new_text.clone();
20634 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
20635 previous_point.column = previous_point.column.saturating_sub(1);
20636 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
20637 if scope.prefers_label_for_snippet_in_completion() {
20638 if let Some(label) = completion.label() {
20639 if matches!(
20640 completion.kind(),
20641 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20642 ) {
20643 snippet_source = label;
20644 }
20645 }
20646 }
20647 }
20648 match Snippet::parse(&snippet_source).log_err() {
20649 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20650 None => (None, completion.new_text.clone()),
20651 }
20652 } else {
20653 (None, completion.new_text.clone())
20654 };
20655
20656 let mut range_to_replace = {
20657 let replace_range = &completion.replace_range;
20658 if let CompletionSource::Lsp {
20659 insert_range: Some(insert_range),
20660 ..
20661 } = &completion.source
20662 {
20663 debug_assert_eq!(
20664 insert_range.start, replace_range.start,
20665 "insert_range and replace_range should start at the same position"
20666 );
20667 debug_assert!(
20668 insert_range
20669 .start
20670 .cmp(&cursor_position, &buffer_snapshot)
20671 .is_le(),
20672 "insert_range should start before or at cursor position"
20673 );
20674 debug_assert!(
20675 replace_range
20676 .start
20677 .cmp(&cursor_position, &buffer_snapshot)
20678 .is_le(),
20679 "replace_range should start before or at cursor position"
20680 );
20681 debug_assert!(
20682 insert_range
20683 .end
20684 .cmp(&cursor_position, &buffer_snapshot)
20685 .is_le(),
20686 "insert_range should end before or at cursor position"
20687 );
20688
20689 let should_replace = match intent {
20690 CompletionIntent::CompleteWithInsert => false,
20691 CompletionIntent::CompleteWithReplace => true,
20692 CompletionIntent::Complete | CompletionIntent::Compose => {
20693 let insert_mode =
20694 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
20695 .completions
20696 .lsp_insert_mode;
20697 match insert_mode {
20698 LspInsertMode::Insert => false,
20699 LspInsertMode::Replace => true,
20700 LspInsertMode::ReplaceSubsequence => {
20701 let mut text_to_replace = buffer.chars_for_range(
20702 buffer.anchor_before(replace_range.start)
20703 ..buffer.anchor_after(replace_range.end),
20704 );
20705 let mut current_needle = text_to_replace.next();
20706 for haystack_ch in completion.label.text.chars() {
20707 if let Some(needle_ch) = current_needle {
20708 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
20709 current_needle = text_to_replace.next();
20710 }
20711 }
20712 }
20713 current_needle.is_none()
20714 }
20715 LspInsertMode::ReplaceSuffix => {
20716 if replace_range
20717 .end
20718 .cmp(&cursor_position, &buffer_snapshot)
20719 .is_gt()
20720 {
20721 let range_after_cursor = *cursor_position..replace_range.end;
20722 let text_after_cursor = buffer
20723 .text_for_range(
20724 buffer.anchor_before(range_after_cursor.start)
20725 ..buffer.anchor_after(range_after_cursor.end),
20726 )
20727 .collect::<String>()
20728 .to_ascii_lowercase();
20729 completion
20730 .label
20731 .text
20732 .to_ascii_lowercase()
20733 .ends_with(&text_after_cursor)
20734 } else {
20735 true
20736 }
20737 }
20738 }
20739 }
20740 };
20741
20742 if should_replace {
20743 replace_range.clone()
20744 } else {
20745 insert_range.clone()
20746 }
20747 } else {
20748 replace_range.clone()
20749 }
20750 };
20751
20752 if range_to_replace
20753 .end
20754 .cmp(&cursor_position, &buffer_snapshot)
20755 .is_lt()
20756 {
20757 range_to_replace.end = *cursor_position;
20758 }
20759
20760 CompletionEdit {
20761 new_text,
20762 replace_range: range_to_replace.to_offset(&buffer),
20763 snippet,
20764 }
20765}
20766
20767struct CompletionEdit {
20768 new_text: String,
20769 replace_range: Range<usize>,
20770 snippet: Option<Snippet>,
20771}
20772
20773fn insert_extra_newline_brackets(
20774 buffer: &MultiBufferSnapshot,
20775 range: Range<usize>,
20776 language: &language::LanguageScope,
20777) -> bool {
20778 let leading_whitespace_len = buffer
20779 .reversed_chars_at(range.start)
20780 .take_while(|c| c.is_whitespace() && *c != '\n')
20781 .map(|c| c.len_utf8())
20782 .sum::<usize>();
20783 let trailing_whitespace_len = buffer
20784 .chars_at(range.end)
20785 .take_while(|c| c.is_whitespace() && *c != '\n')
20786 .map(|c| c.len_utf8())
20787 .sum::<usize>();
20788 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
20789
20790 language.brackets().any(|(pair, enabled)| {
20791 let pair_start = pair.start.trim_end();
20792 let pair_end = pair.end.trim_start();
20793
20794 enabled
20795 && pair.newline
20796 && buffer.contains_str_at(range.end, pair_end)
20797 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
20798 })
20799}
20800
20801fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
20802 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
20803 [(buffer, range, _)] => (*buffer, range.clone()),
20804 _ => return false,
20805 };
20806 let pair = {
20807 let mut result: Option<BracketMatch> = None;
20808
20809 for pair in buffer
20810 .all_bracket_ranges(range.clone())
20811 .filter(move |pair| {
20812 pair.open_range.start <= range.start && pair.close_range.end >= range.end
20813 })
20814 {
20815 let len = pair.close_range.end - pair.open_range.start;
20816
20817 if let Some(existing) = &result {
20818 let existing_len = existing.close_range.end - existing.open_range.start;
20819 if len > existing_len {
20820 continue;
20821 }
20822 }
20823
20824 result = Some(pair);
20825 }
20826
20827 result
20828 };
20829 let Some(pair) = pair else {
20830 return false;
20831 };
20832 pair.newline_only
20833 && buffer
20834 .chars_for_range(pair.open_range.end..range.start)
20835 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
20836 .all(|c| c.is_whitespace() && c != '\n')
20837}
20838
20839fn update_uncommitted_diff_for_buffer(
20840 editor: Entity<Editor>,
20841 project: &Entity<Project>,
20842 buffers: impl IntoIterator<Item = Entity<Buffer>>,
20843 buffer: Entity<MultiBuffer>,
20844 cx: &mut App,
20845) -> Task<()> {
20846 let mut tasks = Vec::new();
20847 project.update(cx, |project, cx| {
20848 for buffer in buffers {
20849 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
20850 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
20851 }
20852 }
20853 });
20854 cx.spawn(async move |cx| {
20855 let diffs = future::join_all(tasks).await;
20856 if editor
20857 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
20858 .unwrap_or(false)
20859 {
20860 return;
20861 }
20862
20863 buffer
20864 .update(cx, |buffer, cx| {
20865 for diff in diffs.into_iter().flatten() {
20866 buffer.add_diff(diff, cx);
20867 }
20868 })
20869 .ok();
20870 })
20871}
20872
20873fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
20874 let tab_size = tab_size.get() as usize;
20875 let mut width = offset;
20876
20877 for ch in text.chars() {
20878 width += if ch == '\t' {
20879 tab_size - (width % tab_size)
20880 } else {
20881 1
20882 };
20883 }
20884
20885 width - offset
20886}
20887
20888#[cfg(test)]
20889mod tests {
20890 use super::*;
20891
20892 #[test]
20893 fn test_string_size_with_expanded_tabs() {
20894 let nz = |val| NonZeroU32::new(val).unwrap();
20895 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
20896 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
20897 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
20898 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
20899 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
20900 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
20901 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
20902 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
20903 }
20904}
20905
20906/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
20907struct WordBreakingTokenizer<'a> {
20908 input: &'a str,
20909}
20910
20911impl<'a> WordBreakingTokenizer<'a> {
20912 fn new(input: &'a str) -> Self {
20913 Self { input }
20914 }
20915}
20916
20917fn is_char_ideographic(ch: char) -> bool {
20918 use unicode_script::Script::*;
20919 use unicode_script::UnicodeScript;
20920 matches!(ch.script(), Han | Tangut | Yi)
20921}
20922
20923fn is_grapheme_ideographic(text: &str) -> bool {
20924 text.chars().any(is_char_ideographic)
20925}
20926
20927fn is_grapheme_whitespace(text: &str) -> bool {
20928 text.chars().any(|x| x.is_whitespace())
20929}
20930
20931fn should_stay_with_preceding_ideograph(text: &str) -> bool {
20932 text.chars().next().map_or(false, |ch| {
20933 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
20934 })
20935}
20936
20937#[derive(PartialEq, Eq, Debug, Clone, Copy)]
20938enum WordBreakToken<'a> {
20939 Word { token: &'a str, grapheme_len: usize },
20940 InlineWhitespace { token: &'a str, grapheme_len: usize },
20941 Newline,
20942}
20943
20944impl<'a> Iterator for WordBreakingTokenizer<'a> {
20945 /// Yields a span, the count of graphemes in the token, and whether it was
20946 /// whitespace. Note that it also breaks at word boundaries.
20947 type Item = WordBreakToken<'a>;
20948
20949 fn next(&mut self) -> Option<Self::Item> {
20950 use unicode_segmentation::UnicodeSegmentation;
20951 if self.input.is_empty() {
20952 return None;
20953 }
20954
20955 let mut iter = self.input.graphemes(true).peekable();
20956 let mut offset = 0;
20957 let mut grapheme_len = 0;
20958 if let Some(first_grapheme) = iter.next() {
20959 let is_newline = first_grapheme == "\n";
20960 let is_whitespace = is_grapheme_whitespace(first_grapheme);
20961 offset += first_grapheme.len();
20962 grapheme_len += 1;
20963 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
20964 if let Some(grapheme) = iter.peek().copied() {
20965 if should_stay_with_preceding_ideograph(grapheme) {
20966 offset += grapheme.len();
20967 grapheme_len += 1;
20968 }
20969 }
20970 } else {
20971 let mut words = self.input[offset..].split_word_bound_indices().peekable();
20972 let mut next_word_bound = words.peek().copied();
20973 if next_word_bound.map_or(false, |(i, _)| i == 0) {
20974 next_word_bound = words.next();
20975 }
20976 while let Some(grapheme) = iter.peek().copied() {
20977 if next_word_bound.map_or(false, |(i, _)| i == offset) {
20978 break;
20979 };
20980 if is_grapheme_whitespace(grapheme) != is_whitespace
20981 || (grapheme == "\n") != is_newline
20982 {
20983 break;
20984 };
20985 offset += grapheme.len();
20986 grapheme_len += 1;
20987 iter.next();
20988 }
20989 }
20990 let token = &self.input[..offset];
20991 self.input = &self.input[offset..];
20992 if token == "\n" {
20993 Some(WordBreakToken::Newline)
20994 } else if is_whitespace {
20995 Some(WordBreakToken::InlineWhitespace {
20996 token,
20997 grapheme_len,
20998 })
20999 } else {
21000 Some(WordBreakToken::Word {
21001 token,
21002 grapheme_len,
21003 })
21004 }
21005 } else {
21006 None
21007 }
21008 }
21009}
21010
21011#[test]
21012fn test_word_breaking_tokenizer() {
21013 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21014 ("", &[]),
21015 (" ", &[whitespace(" ", 2)]),
21016 ("Ʒ", &[word("Ʒ", 1)]),
21017 ("Ǽ", &[word("Ǽ", 1)]),
21018 ("⋑", &[word("⋑", 1)]),
21019 ("⋑⋑", &[word("⋑⋑", 2)]),
21020 (
21021 "原理,进而",
21022 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21023 ),
21024 (
21025 "hello world",
21026 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21027 ),
21028 (
21029 "hello, world",
21030 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21031 ),
21032 (
21033 " hello world",
21034 &[
21035 whitespace(" ", 2),
21036 word("hello", 5),
21037 whitespace(" ", 1),
21038 word("world", 5),
21039 ],
21040 ),
21041 (
21042 "这是什么 \n 钢笔",
21043 &[
21044 word("这", 1),
21045 word("是", 1),
21046 word("什", 1),
21047 word("么", 1),
21048 whitespace(" ", 1),
21049 newline(),
21050 whitespace(" ", 1),
21051 word("钢", 1),
21052 word("笔", 1),
21053 ],
21054 ),
21055 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21056 ];
21057
21058 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21059 WordBreakToken::Word {
21060 token,
21061 grapheme_len,
21062 }
21063 }
21064
21065 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21066 WordBreakToken::InlineWhitespace {
21067 token,
21068 grapheme_len,
21069 }
21070 }
21071
21072 fn newline() -> WordBreakToken<'static> {
21073 WordBreakToken::Newline
21074 }
21075
21076 for (input, result) in tests {
21077 assert_eq!(
21078 WordBreakingTokenizer::new(input)
21079 .collect::<Vec<_>>()
21080 .as_slice(),
21081 *result,
21082 );
21083 }
21084}
21085
21086fn wrap_with_prefix(
21087 line_prefix: String,
21088 unwrapped_text: String,
21089 wrap_column: usize,
21090 tab_size: NonZeroU32,
21091 preserve_existing_whitespace: bool,
21092) -> String {
21093 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
21094 let mut wrapped_text = String::new();
21095 let mut current_line = line_prefix.clone();
21096
21097 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21098 let mut current_line_len = line_prefix_len;
21099 let mut in_whitespace = false;
21100 for token in tokenizer {
21101 let have_preceding_whitespace = in_whitespace;
21102 match token {
21103 WordBreakToken::Word {
21104 token,
21105 grapheme_len,
21106 } => {
21107 in_whitespace = false;
21108 if current_line_len + grapheme_len > wrap_column
21109 && current_line_len != line_prefix_len
21110 {
21111 wrapped_text.push_str(current_line.trim_end());
21112 wrapped_text.push('\n');
21113 current_line.truncate(line_prefix.len());
21114 current_line_len = line_prefix_len;
21115 }
21116 current_line.push_str(token);
21117 current_line_len += grapheme_len;
21118 }
21119 WordBreakToken::InlineWhitespace {
21120 mut token,
21121 mut grapheme_len,
21122 } => {
21123 in_whitespace = true;
21124 if have_preceding_whitespace && !preserve_existing_whitespace {
21125 continue;
21126 }
21127 if !preserve_existing_whitespace {
21128 token = " ";
21129 grapheme_len = 1;
21130 }
21131 if current_line_len + grapheme_len > wrap_column {
21132 wrapped_text.push_str(current_line.trim_end());
21133 wrapped_text.push('\n');
21134 current_line.truncate(line_prefix.len());
21135 current_line_len = line_prefix_len;
21136 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
21137 current_line.push_str(token);
21138 current_line_len += grapheme_len;
21139 }
21140 }
21141 WordBreakToken::Newline => {
21142 in_whitespace = true;
21143 if preserve_existing_whitespace {
21144 wrapped_text.push_str(current_line.trim_end());
21145 wrapped_text.push('\n');
21146 current_line.truncate(line_prefix.len());
21147 current_line_len = line_prefix_len;
21148 } else if have_preceding_whitespace {
21149 continue;
21150 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
21151 {
21152 wrapped_text.push_str(current_line.trim_end());
21153 wrapped_text.push('\n');
21154 current_line.truncate(line_prefix.len());
21155 current_line_len = line_prefix_len;
21156 } else if current_line_len != line_prefix_len {
21157 current_line.push(' ');
21158 current_line_len += 1;
21159 }
21160 }
21161 }
21162 }
21163
21164 if !current_line.is_empty() {
21165 wrapped_text.push_str(¤t_line);
21166 }
21167 wrapped_text
21168}
21169
21170#[test]
21171fn test_wrap_with_prefix() {
21172 assert_eq!(
21173 wrap_with_prefix(
21174 "# ".to_string(),
21175 "abcdefg".to_string(),
21176 4,
21177 NonZeroU32::new(4).unwrap(),
21178 false,
21179 ),
21180 "# abcdefg"
21181 );
21182 assert_eq!(
21183 wrap_with_prefix(
21184 "".to_string(),
21185 "\thello world".to_string(),
21186 8,
21187 NonZeroU32::new(4).unwrap(),
21188 false,
21189 ),
21190 "hello\nworld"
21191 );
21192 assert_eq!(
21193 wrap_with_prefix(
21194 "// ".to_string(),
21195 "xx \nyy zz aa bb cc".to_string(),
21196 12,
21197 NonZeroU32::new(4).unwrap(),
21198 false,
21199 ),
21200 "// xx yy zz\n// aa bb cc"
21201 );
21202 assert_eq!(
21203 wrap_with_prefix(
21204 String::new(),
21205 "这是什么 \n 钢笔".to_string(),
21206 3,
21207 NonZeroU32::new(4).unwrap(),
21208 false,
21209 ),
21210 "这是什\n么 钢\n笔"
21211 );
21212}
21213
21214pub trait CollaborationHub {
21215 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21216 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21217 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21218}
21219
21220impl CollaborationHub for Entity<Project> {
21221 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21222 self.read(cx).collaborators()
21223 }
21224
21225 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21226 self.read(cx).user_store().read(cx).participant_indices()
21227 }
21228
21229 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21230 let this = self.read(cx);
21231 let user_ids = this.collaborators().values().map(|c| c.user_id);
21232 this.user_store().read(cx).participant_names(user_ids, cx)
21233 }
21234}
21235
21236pub trait SemanticsProvider {
21237 fn hover(
21238 &self,
21239 buffer: &Entity<Buffer>,
21240 position: text::Anchor,
21241 cx: &mut App,
21242 ) -> Option<Task<Vec<project::Hover>>>;
21243
21244 fn inline_values(
21245 &self,
21246 buffer_handle: Entity<Buffer>,
21247 range: Range<text::Anchor>,
21248 cx: &mut App,
21249 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21250
21251 fn inlay_hints(
21252 &self,
21253 buffer_handle: Entity<Buffer>,
21254 range: Range<text::Anchor>,
21255 cx: &mut App,
21256 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21257
21258 fn resolve_inlay_hint(
21259 &self,
21260 hint: InlayHint,
21261 buffer_handle: Entity<Buffer>,
21262 server_id: LanguageServerId,
21263 cx: &mut App,
21264 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21265
21266 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21267
21268 fn document_highlights(
21269 &self,
21270 buffer: &Entity<Buffer>,
21271 position: text::Anchor,
21272 cx: &mut App,
21273 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21274
21275 fn definitions(
21276 &self,
21277 buffer: &Entity<Buffer>,
21278 position: text::Anchor,
21279 kind: GotoDefinitionKind,
21280 cx: &mut App,
21281 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21282
21283 fn range_for_rename(
21284 &self,
21285 buffer: &Entity<Buffer>,
21286 position: text::Anchor,
21287 cx: &mut App,
21288 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21289
21290 fn perform_rename(
21291 &self,
21292 buffer: &Entity<Buffer>,
21293 position: text::Anchor,
21294 new_name: String,
21295 cx: &mut App,
21296 ) -> Option<Task<Result<ProjectTransaction>>>;
21297}
21298
21299pub trait CompletionProvider {
21300 fn completions(
21301 &self,
21302 excerpt_id: ExcerptId,
21303 buffer: &Entity<Buffer>,
21304 buffer_position: text::Anchor,
21305 trigger: CompletionContext,
21306 window: &mut Window,
21307 cx: &mut Context<Editor>,
21308 ) -> Task<Result<Vec<CompletionResponse>>>;
21309
21310 fn resolve_completions(
21311 &self,
21312 _buffer: Entity<Buffer>,
21313 _completion_indices: Vec<usize>,
21314 _completions: Rc<RefCell<Box<[Completion]>>>,
21315 _cx: &mut Context<Editor>,
21316 ) -> Task<Result<bool>> {
21317 Task::ready(Ok(false))
21318 }
21319
21320 fn apply_additional_edits_for_completion(
21321 &self,
21322 _buffer: Entity<Buffer>,
21323 _completions: Rc<RefCell<Box<[Completion]>>>,
21324 _completion_index: usize,
21325 _push_to_history: bool,
21326 _cx: &mut Context<Editor>,
21327 ) -> Task<Result<Option<language::Transaction>>> {
21328 Task::ready(Ok(None))
21329 }
21330
21331 fn is_completion_trigger(
21332 &self,
21333 buffer: &Entity<Buffer>,
21334 position: language::Anchor,
21335 text: &str,
21336 trigger_in_words: bool,
21337 menu_is_open: bool,
21338 cx: &mut Context<Editor>,
21339 ) -> bool;
21340
21341 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21342
21343 fn sort_completions(&self) -> bool {
21344 true
21345 }
21346
21347 fn filter_completions(&self) -> bool {
21348 true
21349 }
21350}
21351
21352pub trait CodeActionProvider {
21353 fn id(&self) -> Arc<str>;
21354
21355 fn code_actions(
21356 &self,
21357 buffer: &Entity<Buffer>,
21358 range: Range<text::Anchor>,
21359 window: &mut Window,
21360 cx: &mut App,
21361 ) -> Task<Result<Vec<CodeAction>>>;
21362
21363 fn apply_code_action(
21364 &self,
21365 buffer_handle: Entity<Buffer>,
21366 action: CodeAction,
21367 excerpt_id: ExcerptId,
21368 push_to_history: bool,
21369 window: &mut Window,
21370 cx: &mut App,
21371 ) -> Task<Result<ProjectTransaction>>;
21372}
21373
21374impl CodeActionProvider for Entity<Project> {
21375 fn id(&self) -> Arc<str> {
21376 "project".into()
21377 }
21378
21379 fn code_actions(
21380 &self,
21381 buffer: &Entity<Buffer>,
21382 range: Range<text::Anchor>,
21383 _window: &mut Window,
21384 cx: &mut App,
21385 ) -> Task<Result<Vec<CodeAction>>> {
21386 self.update(cx, |project, cx| {
21387 let code_lens = project.code_lens(buffer, range.clone(), cx);
21388 let code_actions = project.code_actions(buffer, range, None, cx);
21389 cx.background_spawn(async move {
21390 let (code_lens, code_actions) = join(code_lens, code_actions).await;
21391 Ok(code_lens
21392 .context("code lens fetch")?
21393 .into_iter()
21394 .chain(code_actions.context("code action fetch")?)
21395 .collect())
21396 })
21397 })
21398 }
21399
21400 fn apply_code_action(
21401 &self,
21402 buffer_handle: Entity<Buffer>,
21403 action: CodeAction,
21404 _excerpt_id: ExcerptId,
21405 push_to_history: bool,
21406 _window: &mut Window,
21407 cx: &mut App,
21408 ) -> Task<Result<ProjectTransaction>> {
21409 self.update(cx, |project, cx| {
21410 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21411 })
21412 }
21413}
21414
21415fn snippet_completions(
21416 project: &Project,
21417 buffer: &Entity<Buffer>,
21418 buffer_position: text::Anchor,
21419 cx: &mut App,
21420) -> Task<Result<CompletionResponse>> {
21421 let languages = buffer.read(cx).languages_at(buffer_position);
21422 let snippet_store = project.snippets().read(cx);
21423
21424 let scopes: Vec<_> = languages
21425 .iter()
21426 .filter_map(|language| {
21427 let language_name = language.lsp_id();
21428 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21429
21430 if snippets.is_empty() {
21431 None
21432 } else {
21433 Some((language.default_scope(), snippets))
21434 }
21435 })
21436 .collect();
21437
21438 if scopes.is_empty() {
21439 return Task::ready(Ok(CompletionResponse {
21440 completions: vec![],
21441 is_incomplete: false,
21442 }));
21443 }
21444
21445 let snapshot = buffer.read(cx).text_snapshot();
21446 let chars: String = snapshot
21447 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
21448 .collect();
21449 let executor = cx.background_executor().clone();
21450
21451 cx.background_spawn(async move {
21452 let mut is_incomplete = false;
21453 let mut completions: Vec<Completion> = Vec::new();
21454 for (scope, snippets) in scopes.into_iter() {
21455 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
21456 let mut last_word = chars
21457 .chars()
21458 .take_while(|c| classifier.is_word(*c))
21459 .collect::<String>();
21460 last_word = last_word.chars().rev().collect();
21461
21462 if last_word.is_empty() {
21463 return Ok(CompletionResponse {
21464 completions: vec![],
21465 is_incomplete: true,
21466 });
21467 }
21468
21469 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
21470 let to_lsp = |point: &text::Anchor| {
21471 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
21472 point_to_lsp(end)
21473 };
21474 let lsp_end = to_lsp(&buffer_position);
21475
21476 let candidates = snippets
21477 .iter()
21478 .enumerate()
21479 .flat_map(|(ix, snippet)| {
21480 snippet
21481 .prefix
21482 .iter()
21483 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21484 })
21485 .collect::<Vec<StringMatchCandidate>>();
21486
21487 const MAX_RESULTS: usize = 100;
21488 let mut matches = fuzzy::match_strings(
21489 &candidates,
21490 &last_word,
21491 last_word.chars().any(|c| c.is_uppercase()),
21492 true,
21493 MAX_RESULTS,
21494 &Default::default(),
21495 executor.clone(),
21496 )
21497 .await;
21498
21499 if matches.len() >= MAX_RESULTS {
21500 is_incomplete = true;
21501 }
21502
21503 // Remove all candidates where the query's start does not match the start of any word in the candidate
21504 if let Some(query_start) = last_word.chars().next() {
21505 matches.retain(|string_match| {
21506 split_words(&string_match.string).any(|word| {
21507 // Check that the first codepoint of the word as lowercase matches the first
21508 // codepoint of the query as lowercase
21509 word.chars()
21510 .flat_map(|codepoint| codepoint.to_lowercase())
21511 .zip(query_start.to_lowercase())
21512 .all(|(word_cp, query_cp)| word_cp == query_cp)
21513 })
21514 });
21515 }
21516
21517 let matched_strings = matches
21518 .into_iter()
21519 .map(|m| m.string)
21520 .collect::<HashSet<_>>();
21521
21522 completions.extend(snippets.iter().filter_map(|snippet| {
21523 let matching_prefix = snippet
21524 .prefix
21525 .iter()
21526 .find(|prefix| matched_strings.contains(*prefix))?;
21527 let start = as_offset - last_word.len();
21528 let start = snapshot.anchor_before(start);
21529 let range = start..buffer_position;
21530 let lsp_start = to_lsp(&start);
21531 let lsp_range = lsp::Range {
21532 start: lsp_start,
21533 end: lsp_end,
21534 };
21535 Some(Completion {
21536 replace_range: range,
21537 new_text: snippet.body.clone(),
21538 source: CompletionSource::Lsp {
21539 insert_range: None,
21540 server_id: LanguageServerId(usize::MAX),
21541 resolved: true,
21542 lsp_completion: Box::new(lsp::CompletionItem {
21543 label: snippet.prefix.first().unwrap().clone(),
21544 kind: Some(CompletionItemKind::SNIPPET),
21545 label_details: snippet.description.as_ref().map(|description| {
21546 lsp::CompletionItemLabelDetails {
21547 detail: Some(description.clone()),
21548 description: None,
21549 }
21550 }),
21551 insert_text_format: Some(InsertTextFormat::SNIPPET),
21552 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21553 lsp::InsertReplaceEdit {
21554 new_text: snippet.body.clone(),
21555 insert: lsp_range,
21556 replace: lsp_range,
21557 },
21558 )),
21559 filter_text: Some(snippet.body.clone()),
21560 sort_text: Some(char::MAX.to_string()),
21561 ..lsp::CompletionItem::default()
21562 }),
21563 lsp_defaults: None,
21564 },
21565 label: CodeLabel {
21566 text: matching_prefix.clone(),
21567 runs: Vec::new(),
21568 filter_range: 0..matching_prefix.len(),
21569 },
21570 icon_path: None,
21571 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
21572 single_line: snippet.name.clone().into(),
21573 plain_text: snippet
21574 .description
21575 .clone()
21576 .map(|description| description.into()),
21577 }),
21578 insert_text_mode: None,
21579 confirm: None,
21580 })
21581 }))
21582 }
21583
21584 Ok(CompletionResponse {
21585 completions,
21586 is_incomplete,
21587 })
21588 })
21589}
21590
21591impl CompletionProvider for Entity<Project> {
21592 fn completions(
21593 &self,
21594 _excerpt_id: ExcerptId,
21595 buffer: &Entity<Buffer>,
21596 buffer_position: text::Anchor,
21597 options: CompletionContext,
21598 _window: &mut Window,
21599 cx: &mut Context<Editor>,
21600 ) -> Task<Result<Vec<CompletionResponse>>> {
21601 self.update(cx, |project, cx| {
21602 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21603 let project_completions = project.completions(buffer, buffer_position, options, cx);
21604 cx.background_spawn(async move {
21605 let mut responses = project_completions.await?;
21606 let snippets = snippets.await?;
21607 if !snippets.completions.is_empty() {
21608 responses.push(snippets);
21609 }
21610 Ok(responses)
21611 })
21612 })
21613 }
21614
21615 fn resolve_completions(
21616 &self,
21617 buffer: Entity<Buffer>,
21618 completion_indices: Vec<usize>,
21619 completions: Rc<RefCell<Box<[Completion]>>>,
21620 cx: &mut Context<Editor>,
21621 ) -> Task<Result<bool>> {
21622 self.update(cx, |project, cx| {
21623 project.lsp_store().update(cx, |lsp_store, cx| {
21624 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21625 })
21626 })
21627 }
21628
21629 fn apply_additional_edits_for_completion(
21630 &self,
21631 buffer: Entity<Buffer>,
21632 completions: Rc<RefCell<Box<[Completion]>>>,
21633 completion_index: usize,
21634 push_to_history: bool,
21635 cx: &mut Context<Editor>,
21636 ) -> Task<Result<Option<language::Transaction>>> {
21637 self.update(cx, |project, cx| {
21638 project.lsp_store().update(cx, |lsp_store, cx| {
21639 lsp_store.apply_additional_edits_for_completion(
21640 buffer,
21641 completions,
21642 completion_index,
21643 push_to_history,
21644 cx,
21645 )
21646 })
21647 })
21648 }
21649
21650 fn is_completion_trigger(
21651 &self,
21652 buffer: &Entity<Buffer>,
21653 position: language::Anchor,
21654 text: &str,
21655 trigger_in_words: bool,
21656 menu_is_open: bool,
21657 cx: &mut Context<Editor>,
21658 ) -> bool {
21659 let mut chars = text.chars();
21660 let char = if let Some(char) = chars.next() {
21661 char
21662 } else {
21663 return false;
21664 };
21665 if chars.next().is_some() {
21666 return false;
21667 }
21668
21669 let buffer = buffer.read(cx);
21670 let snapshot = buffer.snapshot();
21671 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
21672 return false;
21673 }
21674 let classifier = snapshot.char_classifier_at(position).for_completion(true);
21675 if trigger_in_words && classifier.is_word(char) {
21676 return true;
21677 }
21678
21679 buffer.completion_triggers().contains(text)
21680 }
21681}
21682
21683impl SemanticsProvider for Entity<Project> {
21684 fn hover(
21685 &self,
21686 buffer: &Entity<Buffer>,
21687 position: text::Anchor,
21688 cx: &mut App,
21689 ) -> Option<Task<Vec<project::Hover>>> {
21690 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
21691 }
21692
21693 fn document_highlights(
21694 &self,
21695 buffer: &Entity<Buffer>,
21696 position: text::Anchor,
21697 cx: &mut App,
21698 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
21699 Some(self.update(cx, |project, cx| {
21700 project.document_highlights(buffer, position, cx)
21701 }))
21702 }
21703
21704 fn definitions(
21705 &self,
21706 buffer: &Entity<Buffer>,
21707 position: text::Anchor,
21708 kind: GotoDefinitionKind,
21709 cx: &mut App,
21710 ) -> Option<Task<Result<Vec<LocationLink>>>> {
21711 Some(self.update(cx, |project, cx| match kind {
21712 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
21713 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
21714 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
21715 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
21716 }))
21717 }
21718
21719 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
21720 // TODO: make this work for remote projects
21721 self.update(cx, |project, cx| {
21722 if project
21723 .active_debug_session(cx)
21724 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
21725 {
21726 return true;
21727 }
21728
21729 buffer.update(cx, |buffer, cx| {
21730 project.any_language_server_supports_inlay_hints(buffer, cx)
21731 })
21732 })
21733 }
21734
21735 fn inline_values(
21736 &self,
21737 buffer_handle: Entity<Buffer>,
21738 range: Range<text::Anchor>,
21739 cx: &mut App,
21740 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21741 self.update(cx, |project, cx| {
21742 let (session, active_stack_frame) = project.active_debug_session(cx)?;
21743
21744 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
21745 })
21746 }
21747
21748 fn inlay_hints(
21749 &self,
21750 buffer_handle: Entity<Buffer>,
21751 range: Range<text::Anchor>,
21752 cx: &mut App,
21753 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21754 Some(self.update(cx, |project, cx| {
21755 project.inlay_hints(buffer_handle, range, cx)
21756 }))
21757 }
21758
21759 fn resolve_inlay_hint(
21760 &self,
21761 hint: InlayHint,
21762 buffer_handle: Entity<Buffer>,
21763 server_id: LanguageServerId,
21764 cx: &mut App,
21765 ) -> Option<Task<anyhow::Result<InlayHint>>> {
21766 Some(self.update(cx, |project, cx| {
21767 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
21768 }))
21769 }
21770
21771 fn range_for_rename(
21772 &self,
21773 buffer: &Entity<Buffer>,
21774 position: text::Anchor,
21775 cx: &mut App,
21776 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
21777 Some(self.update(cx, |project, cx| {
21778 let buffer = buffer.clone();
21779 let task = project.prepare_rename(buffer.clone(), position, cx);
21780 cx.spawn(async move |_, cx| {
21781 Ok(match task.await? {
21782 PrepareRenameResponse::Success(range) => Some(range),
21783 PrepareRenameResponse::InvalidPosition => None,
21784 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
21785 // Fallback on using TreeSitter info to determine identifier range
21786 buffer.read_with(cx, |buffer, _| {
21787 let snapshot = buffer.snapshot();
21788 let (range, kind) = snapshot.surrounding_word(position);
21789 if kind != Some(CharKind::Word) {
21790 return None;
21791 }
21792 Some(
21793 snapshot.anchor_before(range.start)
21794 ..snapshot.anchor_after(range.end),
21795 )
21796 })?
21797 }
21798 })
21799 })
21800 }))
21801 }
21802
21803 fn perform_rename(
21804 &self,
21805 buffer: &Entity<Buffer>,
21806 position: text::Anchor,
21807 new_name: String,
21808 cx: &mut App,
21809 ) -> Option<Task<Result<ProjectTransaction>>> {
21810 Some(self.update(cx, |project, cx| {
21811 project.perform_rename(buffer.clone(), position, new_name, cx)
21812 }))
21813 }
21814}
21815
21816fn inlay_hint_settings(
21817 location: Anchor,
21818 snapshot: &MultiBufferSnapshot,
21819 cx: &mut Context<Editor>,
21820) -> InlayHintSettings {
21821 let file = snapshot.file_at(location);
21822 let language = snapshot.language_at(location).map(|l| l.name());
21823 language_settings(language, file, cx).inlay_hints
21824}
21825
21826fn consume_contiguous_rows(
21827 contiguous_row_selections: &mut Vec<Selection<Point>>,
21828 selection: &Selection<Point>,
21829 display_map: &DisplaySnapshot,
21830 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
21831) -> (MultiBufferRow, MultiBufferRow) {
21832 contiguous_row_selections.push(selection.clone());
21833 let start_row = MultiBufferRow(selection.start.row);
21834 let mut end_row = ending_row(selection, display_map);
21835
21836 while let Some(next_selection) = selections.peek() {
21837 if next_selection.start.row <= end_row.0 {
21838 end_row = ending_row(next_selection, display_map);
21839 contiguous_row_selections.push(selections.next().unwrap().clone());
21840 } else {
21841 break;
21842 }
21843 }
21844 (start_row, end_row)
21845}
21846
21847fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
21848 if next_selection.end.column > 0 || next_selection.is_empty() {
21849 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
21850 } else {
21851 MultiBufferRow(next_selection.end.row)
21852 }
21853}
21854
21855impl EditorSnapshot {
21856 pub fn remote_selections_in_range<'a>(
21857 &'a self,
21858 range: &'a Range<Anchor>,
21859 collaboration_hub: &dyn CollaborationHub,
21860 cx: &'a App,
21861 ) -> impl 'a + Iterator<Item = RemoteSelection> {
21862 let participant_names = collaboration_hub.user_names(cx);
21863 let participant_indices = collaboration_hub.user_participant_indices(cx);
21864 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
21865 let collaborators_by_replica_id = collaborators_by_peer_id
21866 .values()
21867 .map(|collaborator| (collaborator.replica_id, collaborator))
21868 .collect::<HashMap<_, _>>();
21869 self.buffer_snapshot
21870 .selections_in_range(range, false)
21871 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
21872 if replica_id == AGENT_REPLICA_ID {
21873 Some(RemoteSelection {
21874 replica_id,
21875 selection,
21876 cursor_shape,
21877 line_mode,
21878 collaborator_id: CollaboratorId::Agent,
21879 user_name: Some("Agent".into()),
21880 color: cx.theme().players().agent(),
21881 })
21882 } else {
21883 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
21884 let participant_index = participant_indices.get(&collaborator.user_id).copied();
21885 let user_name = participant_names.get(&collaborator.user_id).cloned();
21886 Some(RemoteSelection {
21887 replica_id,
21888 selection,
21889 cursor_shape,
21890 line_mode,
21891 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
21892 user_name,
21893 color: if let Some(index) = participant_index {
21894 cx.theme().players().color_for_participant(index.0)
21895 } else {
21896 cx.theme().players().absent()
21897 },
21898 })
21899 }
21900 })
21901 }
21902
21903 pub fn hunks_for_ranges(
21904 &self,
21905 ranges: impl IntoIterator<Item = Range<Point>>,
21906 ) -> Vec<MultiBufferDiffHunk> {
21907 let mut hunks = Vec::new();
21908 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
21909 HashMap::default();
21910 for query_range in ranges {
21911 let query_rows =
21912 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
21913 for hunk in self.buffer_snapshot.diff_hunks_in_range(
21914 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
21915 ) {
21916 // Include deleted hunks that are adjacent to the query range, because
21917 // otherwise they would be missed.
21918 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
21919 if hunk.status().is_deleted() {
21920 intersects_range |= hunk.row_range.start == query_rows.end;
21921 intersects_range |= hunk.row_range.end == query_rows.start;
21922 }
21923 if intersects_range {
21924 if !processed_buffer_rows
21925 .entry(hunk.buffer_id)
21926 .or_default()
21927 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
21928 {
21929 continue;
21930 }
21931 hunks.push(hunk);
21932 }
21933 }
21934 }
21935
21936 hunks
21937 }
21938
21939 fn display_diff_hunks_for_rows<'a>(
21940 &'a self,
21941 display_rows: Range<DisplayRow>,
21942 folded_buffers: &'a HashSet<BufferId>,
21943 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
21944 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
21945 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
21946
21947 self.buffer_snapshot
21948 .diff_hunks_in_range(buffer_start..buffer_end)
21949 .filter_map(|hunk| {
21950 if folded_buffers.contains(&hunk.buffer_id) {
21951 return None;
21952 }
21953
21954 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
21955 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
21956
21957 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
21958 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
21959
21960 let display_hunk = if hunk_display_start.column() != 0 {
21961 DisplayDiffHunk::Folded {
21962 display_row: hunk_display_start.row(),
21963 }
21964 } else {
21965 let mut end_row = hunk_display_end.row();
21966 if hunk_display_end.column() > 0 {
21967 end_row.0 += 1;
21968 }
21969 let is_created_file = hunk.is_created_file();
21970 DisplayDiffHunk::Unfolded {
21971 status: hunk.status(),
21972 diff_base_byte_range: hunk.diff_base_byte_range,
21973 display_row_range: hunk_display_start.row()..end_row,
21974 multi_buffer_range: Anchor::range_in_buffer(
21975 hunk.excerpt_id,
21976 hunk.buffer_id,
21977 hunk.buffer_range,
21978 ),
21979 is_created_file,
21980 }
21981 };
21982
21983 Some(display_hunk)
21984 })
21985 }
21986
21987 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
21988 self.display_snapshot.buffer_snapshot.language_at(position)
21989 }
21990
21991 pub fn is_focused(&self) -> bool {
21992 self.is_focused
21993 }
21994
21995 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
21996 self.placeholder_text.as_ref()
21997 }
21998
21999 pub fn scroll_position(&self) -> gpui::Point<f32> {
22000 self.scroll_anchor.scroll_position(&self.display_snapshot)
22001 }
22002
22003 fn gutter_dimensions(
22004 &self,
22005 font_id: FontId,
22006 font_size: Pixels,
22007 max_line_number_width: Pixels,
22008 cx: &App,
22009 ) -> Option<GutterDimensions> {
22010 if !self.show_gutter {
22011 return None;
22012 }
22013
22014 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22015 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22016
22017 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22018 matches!(
22019 ProjectSettings::get_global(cx).git.git_gutter,
22020 Some(GitGutterSetting::TrackedFiles)
22021 )
22022 });
22023 let gutter_settings = EditorSettings::get_global(cx).gutter;
22024 let show_line_numbers = self
22025 .show_line_numbers
22026 .unwrap_or(gutter_settings.line_numbers);
22027 let line_gutter_width = if show_line_numbers {
22028 // Avoid flicker-like gutter resizes when the line number gains another digit by
22029 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22030 let min_width_for_number_on_gutter =
22031 ch_advance * gutter_settings.min_line_number_digits as f32;
22032 max_line_number_width.max(min_width_for_number_on_gutter)
22033 } else {
22034 0.0.into()
22035 };
22036
22037 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22038 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22039
22040 let git_blame_entries_width =
22041 self.git_blame_gutter_max_author_length
22042 .map(|max_author_length| {
22043 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22044 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22045
22046 /// The number of characters to dedicate to gaps and margins.
22047 const SPACING_WIDTH: usize = 4;
22048
22049 let max_char_count = max_author_length.min(renderer.max_author_length())
22050 + ::git::SHORT_SHA_LENGTH
22051 + MAX_RELATIVE_TIMESTAMP.len()
22052 + SPACING_WIDTH;
22053
22054 ch_advance * max_char_count
22055 });
22056
22057 let is_singleton = self.buffer_snapshot.is_singleton();
22058
22059 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22060 left_padding += if !is_singleton {
22061 ch_width * 4.0
22062 } else if show_runnables || show_breakpoints {
22063 ch_width * 3.0
22064 } else if show_git_gutter && show_line_numbers {
22065 ch_width * 2.0
22066 } else if show_git_gutter || show_line_numbers {
22067 ch_width
22068 } else {
22069 px(0.)
22070 };
22071
22072 let shows_folds = is_singleton && gutter_settings.folds;
22073
22074 let right_padding = if shows_folds && show_line_numbers {
22075 ch_width * 4.0
22076 } else if shows_folds || (!is_singleton && show_line_numbers) {
22077 ch_width * 3.0
22078 } else if show_line_numbers {
22079 ch_width
22080 } else {
22081 px(0.)
22082 };
22083
22084 Some(GutterDimensions {
22085 left_padding,
22086 right_padding,
22087 width: line_gutter_width + left_padding + right_padding,
22088 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22089 git_blame_entries_width,
22090 })
22091 }
22092
22093 pub fn render_crease_toggle(
22094 &self,
22095 buffer_row: MultiBufferRow,
22096 row_contains_cursor: bool,
22097 editor: Entity<Editor>,
22098 window: &mut Window,
22099 cx: &mut App,
22100 ) -> Option<AnyElement> {
22101 let folded = self.is_line_folded(buffer_row);
22102 let mut is_foldable = false;
22103
22104 if let Some(crease) = self
22105 .crease_snapshot
22106 .query_row(buffer_row, &self.buffer_snapshot)
22107 {
22108 is_foldable = true;
22109 match crease {
22110 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22111 if let Some(render_toggle) = render_toggle {
22112 let toggle_callback =
22113 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22114 if folded {
22115 editor.update(cx, |editor, cx| {
22116 editor.fold_at(buffer_row, window, cx)
22117 });
22118 } else {
22119 editor.update(cx, |editor, cx| {
22120 editor.unfold_at(buffer_row, window, cx)
22121 });
22122 }
22123 });
22124 return Some((render_toggle)(
22125 buffer_row,
22126 folded,
22127 toggle_callback,
22128 window,
22129 cx,
22130 ));
22131 }
22132 }
22133 }
22134 }
22135
22136 is_foldable |= self.starts_indent(buffer_row);
22137
22138 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22139 Some(
22140 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22141 .toggle_state(folded)
22142 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22143 if folded {
22144 this.unfold_at(buffer_row, window, cx);
22145 } else {
22146 this.fold_at(buffer_row, window, cx);
22147 }
22148 }))
22149 .into_any_element(),
22150 )
22151 } else {
22152 None
22153 }
22154 }
22155
22156 pub fn render_crease_trailer(
22157 &self,
22158 buffer_row: MultiBufferRow,
22159 window: &mut Window,
22160 cx: &mut App,
22161 ) -> Option<AnyElement> {
22162 let folded = self.is_line_folded(buffer_row);
22163 if let Crease::Inline { render_trailer, .. } = self
22164 .crease_snapshot
22165 .query_row(buffer_row, &self.buffer_snapshot)?
22166 {
22167 let render_trailer = render_trailer.as_ref()?;
22168 Some(render_trailer(buffer_row, folded, window, cx))
22169 } else {
22170 None
22171 }
22172 }
22173}
22174
22175impl Deref for EditorSnapshot {
22176 type Target = DisplaySnapshot;
22177
22178 fn deref(&self) -> &Self::Target {
22179 &self.display_snapshot
22180 }
22181}
22182
22183#[derive(Clone, Debug, PartialEq, Eq)]
22184pub enum EditorEvent {
22185 InputIgnored {
22186 text: Arc<str>,
22187 },
22188 InputHandled {
22189 utf16_range_to_replace: Option<Range<isize>>,
22190 text: Arc<str>,
22191 },
22192 ExcerptsAdded {
22193 buffer: Entity<Buffer>,
22194 predecessor: ExcerptId,
22195 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22196 },
22197 ExcerptsRemoved {
22198 ids: Vec<ExcerptId>,
22199 removed_buffer_ids: Vec<BufferId>,
22200 },
22201 BufferFoldToggled {
22202 ids: Vec<ExcerptId>,
22203 folded: bool,
22204 },
22205 ExcerptsEdited {
22206 ids: Vec<ExcerptId>,
22207 },
22208 ExcerptsExpanded {
22209 ids: Vec<ExcerptId>,
22210 },
22211 BufferEdited,
22212 Edited {
22213 transaction_id: clock::Lamport,
22214 },
22215 Reparsed(BufferId),
22216 Focused,
22217 FocusedIn,
22218 Blurred,
22219 DirtyChanged,
22220 Saved,
22221 TitleChanged,
22222 DiffBaseChanged,
22223 SelectionsChanged {
22224 local: bool,
22225 },
22226 ScrollPositionChanged {
22227 local: bool,
22228 autoscroll: bool,
22229 },
22230 Closed,
22231 TransactionUndone {
22232 transaction_id: clock::Lamport,
22233 },
22234 TransactionBegun {
22235 transaction_id: clock::Lamport,
22236 },
22237 Reloaded,
22238 CursorShapeChanged,
22239 PushedToNavHistory {
22240 anchor: Anchor,
22241 is_deactivate: bool,
22242 },
22243}
22244
22245impl EventEmitter<EditorEvent> for Editor {}
22246
22247impl Focusable for Editor {
22248 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22249 self.focus_handle.clone()
22250 }
22251}
22252
22253impl Render for Editor {
22254 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22255 let settings = ThemeSettings::get_global(cx);
22256
22257 let mut text_style = match self.mode {
22258 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22259 color: cx.theme().colors().editor_foreground,
22260 font_family: settings.ui_font.family.clone(),
22261 font_features: settings.ui_font.features.clone(),
22262 font_fallbacks: settings.ui_font.fallbacks.clone(),
22263 font_size: rems(0.875).into(),
22264 font_weight: settings.ui_font.weight,
22265 line_height: relative(settings.buffer_line_height.value()),
22266 ..Default::default()
22267 },
22268 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22269 color: cx.theme().colors().editor_foreground,
22270 font_family: settings.buffer_font.family.clone(),
22271 font_features: settings.buffer_font.features.clone(),
22272 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22273 font_size: settings.buffer_font_size(cx).into(),
22274 font_weight: settings.buffer_font.weight,
22275 line_height: relative(settings.buffer_line_height.value()),
22276 ..Default::default()
22277 },
22278 };
22279 if let Some(text_style_refinement) = &self.text_style_refinement {
22280 text_style.refine(text_style_refinement)
22281 }
22282
22283 let background = match self.mode {
22284 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22285 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22286 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22287 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22288 };
22289
22290 EditorElement::new(
22291 &cx.entity(),
22292 EditorStyle {
22293 background,
22294 local_player: cx.theme().players().local(),
22295 text: text_style,
22296 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22297 syntax: cx.theme().syntax().clone(),
22298 status: cx.theme().status().clone(),
22299 inlay_hints_style: make_inlay_hints_style(cx),
22300 inline_completion_styles: make_suggestion_styles(cx),
22301 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22302 show_underlines: self.diagnostics_enabled(),
22303 },
22304 )
22305 }
22306}
22307
22308impl EntityInputHandler for Editor {
22309 fn text_for_range(
22310 &mut self,
22311 range_utf16: Range<usize>,
22312 adjusted_range: &mut Option<Range<usize>>,
22313 _: &mut Window,
22314 cx: &mut Context<Self>,
22315 ) -> Option<String> {
22316 let snapshot = self.buffer.read(cx).read(cx);
22317 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22318 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22319 if (start.0..end.0) != range_utf16 {
22320 adjusted_range.replace(start.0..end.0);
22321 }
22322 Some(snapshot.text_for_range(start..end).collect())
22323 }
22324
22325 fn selected_text_range(
22326 &mut self,
22327 ignore_disabled_input: bool,
22328 _: &mut Window,
22329 cx: &mut Context<Self>,
22330 ) -> Option<UTF16Selection> {
22331 // Prevent the IME menu from appearing when holding down an alphabetic key
22332 // while input is disabled.
22333 if !ignore_disabled_input && !self.input_enabled {
22334 return None;
22335 }
22336
22337 let selection = self.selections.newest::<OffsetUtf16>(cx);
22338 let range = selection.range();
22339
22340 Some(UTF16Selection {
22341 range: range.start.0..range.end.0,
22342 reversed: selection.reversed,
22343 })
22344 }
22345
22346 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22347 let snapshot = self.buffer.read(cx).read(cx);
22348 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22349 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22350 }
22351
22352 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22353 self.clear_highlights::<InputComposition>(cx);
22354 self.ime_transaction.take();
22355 }
22356
22357 fn replace_text_in_range(
22358 &mut self,
22359 range_utf16: Option<Range<usize>>,
22360 text: &str,
22361 window: &mut Window,
22362 cx: &mut Context<Self>,
22363 ) {
22364 if !self.input_enabled {
22365 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22366 return;
22367 }
22368
22369 self.transact(window, cx, |this, window, cx| {
22370 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22371 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22372 Some(this.selection_replacement_ranges(range_utf16, cx))
22373 } else {
22374 this.marked_text_ranges(cx)
22375 };
22376
22377 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22378 let newest_selection_id = this.selections.newest_anchor().id;
22379 this.selections
22380 .all::<OffsetUtf16>(cx)
22381 .iter()
22382 .zip(ranges_to_replace.iter())
22383 .find_map(|(selection, range)| {
22384 if selection.id == newest_selection_id {
22385 Some(
22386 (range.start.0 as isize - selection.head().0 as isize)
22387 ..(range.end.0 as isize - selection.head().0 as isize),
22388 )
22389 } else {
22390 None
22391 }
22392 })
22393 });
22394
22395 cx.emit(EditorEvent::InputHandled {
22396 utf16_range_to_replace: range_to_replace,
22397 text: text.into(),
22398 });
22399
22400 if let Some(new_selected_ranges) = new_selected_ranges {
22401 this.change_selections(None, window, cx, |selections| {
22402 selections.select_ranges(new_selected_ranges)
22403 });
22404 this.backspace(&Default::default(), window, cx);
22405 }
22406
22407 this.handle_input(text, window, cx);
22408 });
22409
22410 if let Some(transaction) = self.ime_transaction {
22411 self.buffer.update(cx, |buffer, cx| {
22412 buffer.group_until_transaction(transaction, cx);
22413 });
22414 }
22415
22416 self.unmark_text(window, cx);
22417 }
22418
22419 fn replace_and_mark_text_in_range(
22420 &mut self,
22421 range_utf16: Option<Range<usize>>,
22422 text: &str,
22423 new_selected_range_utf16: Option<Range<usize>>,
22424 window: &mut Window,
22425 cx: &mut Context<Self>,
22426 ) {
22427 if !self.input_enabled {
22428 return;
22429 }
22430
22431 let transaction = self.transact(window, cx, |this, window, cx| {
22432 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22433 let snapshot = this.buffer.read(cx).read(cx);
22434 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22435 for marked_range in &mut marked_ranges {
22436 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22437 marked_range.start.0 += relative_range_utf16.start;
22438 marked_range.start =
22439 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22440 marked_range.end =
22441 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22442 }
22443 }
22444 Some(marked_ranges)
22445 } else if let Some(range_utf16) = range_utf16 {
22446 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22447 Some(this.selection_replacement_ranges(range_utf16, cx))
22448 } else {
22449 None
22450 };
22451
22452 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22453 let newest_selection_id = this.selections.newest_anchor().id;
22454 this.selections
22455 .all::<OffsetUtf16>(cx)
22456 .iter()
22457 .zip(ranges_to_replace.iter())
22458 .find_map(|(selection, range)| {
22459 if selection.id == newest_selection_id {
22460 Some(
22461 (range.start.0 as isize - selection.head().0 as isize)
22462 ..(range.end.0 as isize - selection.head().0 as isize),
22463 )
22464 } else {
22465 None
22466 }
22467 })
22468 });
22469
22470 cx.emit(EditorEvent::InputHandled {
22471 utf16_range_to_replace: range_to_replace,
22472 text: text.into(),
22473 });
22474
22475 if let Some(ranges) = ranges_to_replace {
22476 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
22477 }
22478
22479 let marked_ranges = {
22480 let snapshot = this.buffer.read(cx).read(cx);
22481 this.selections
22482 .disjoint_anchors()
22483 .iter()
22484 .map(|selection| {
22485 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22486 })
22487 .collect::<Vec<_>>()
22488 };
22489
22490 if text.is_empty() {
22491 this.unmark_text(window, cx);
22492 } else {
22493 this.highlight_text::<InputComposition>(
22494 marked_ranges.clone(),
22495 HighlightStyle {
22496 underline: Some(UnderlineStyle {
22497 thickness: px(1.),
22498 color: None,
22499 wavy: false,
22500 }),
22501 ..Default::default()
22502 },
22503 cx,
22504 );
22505 }
22506
22507 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22508 let use_autoclose = this.use_autoclose;
22509 let use_auto_surround = this.use_auto_surround;
22510 this.set_use_autoclose(false);
22511 this.set_use_auto_surround(false);
22512 this.handle_input(text, window, cx);
22513 this.set_use_autoclose(use_autoclose);
22514 this.set_use_auto_surround(use_auto_surround);
22515
22516 if let Some(new_selected_range) = new_selected_range_utf16 {
22517 let snapshot = this.buffer.read(cx).read(cx);
22518 let new_selected_ranges = marked_ranges
22519 .into_iter()
22520 .map(|marked_range| {
22521 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22522 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22523 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22524 snapshot.clip_offset_utf16(new_start, Bias::Left)
22525 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22526 })
22527 .collect::<Vec<_>>();
22528
22529 drop(snapshot);
22530 this.change_selections(None, window, cx, |selections| {
22531 selections.select_ranges(new_selected_ranges)
22532 });
22533 }
22534 });
22535
22536 self.ime_transaction = self.ime_transaction.or(transaction);
22537 if let Some(transaction) = self.ime_transaction {
22538 self.buffer.update(cx, |buffer, cx| {
22539 buffer.group_until_transaction(transaction, cx);
22540 });
22541 }
22542
22543 if self.text_highlights::<InputComposition>(cx).is_none() {
22544 self.ime_transaction.take();
22545 }
22546 }
22547
22548 fn bounds_for_range(
22549 &mut self,
22550 range_utf16: Range<usize>,
22551 element_bounds: gpui::Bounds<Pixels>,
22552 window: &mut Window,
22553 cx: &mut Context<Self>,
22554 ) -> Option<gpui::Bounds<Pixels>> {
22555 let text_layout_details = self.text_layout_details(window);
22556 let CharacterDimensions {
22557 em_width,
22558 em_advance,
22559 line_height,
22560 } = self.character_dimensions(window);
22561
22562 let snapshot = self.snapshot(window, cx);
22563 let scroll_position = snapshot.scroll_position();
22564 let scroll_left = scroll_position.x * em_advance;
22565
22566 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22567 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22568 + self.gutter_dimensions.full_width();
22569 let y = line_height * (start.row().as_f32() - scroll_position.y);
22570
22571 Some(Bounds {
22572 origin: element_bounds.origin + point(x, y),
22573 size: size(em_width, line_height),
22574 })
22575 }
22576
22577 fn character_index_for_point(
22578 &mut self,
22579 point: gpui::Point<Pixels>,
22580 _window: &mut Window,
22581 _cx: &mut Context<Self>,
22582 ) -> Option<usize> {
22583 let position_map = self.last_position_map.as_ref()?;
22584 if !position_map.text_hitbox.contains(&point) {
22585 return None;
22586 }
22587 let display_point = position_map.point_for_position(point).previous_valid;
22588 let anchor = position_map
22589 .snapshot
22590 .display_point_to_anchor(display_point, Bias::Left);
22591 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22592 Some(utf16_offset.0)
22593 }
22594}
22595
22596trait SelectionExt {
22597 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22598 fn spanned_rows(
22599 &self,
22600 include_end_if_at_line_start: bool,
22601 map: &DisplaySnapshot,
22602 ) -> Range<MultiBufferRow>;
22603}
22604
22605impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22606 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22607 let start = self
22608 .start
22609 .to_point(&map.buffer_snapshot)
22610 .to_display_point(map);
22611 let end = self
22612 .end
22613 .to_point(&map.buffer_snapshot)
22614 .to_display_point(map);
22615 if self.reversed {
22616 end..start
22617 } else {
22618 start..end
22619 }
22620 }
22621
22622 fn spanned_rows(
22623 &self,
22624 include_end_if_at_line_start: bool,
22625 map: &DisplaySnapshot,
22626 ) -> Range<MultiBufferRow> {
22627 let start = self.start.to_point(&map.buffer_snapshot);
22628 let mut end = self.end.to_point(&map.buffer_snapshot);
22629 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22630 end.row -= 1;
22631 }
22632
22633 let buffer_start = map.prev_line_boundary(start).0;
22634 let buffer_end = map.next_line_boundary(end).0;
22635 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22636 }
22637}
22638
22639impl<T: InvalidationRegion> InvalidationStack<T> {
22640 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22641 where
22642 S: Clone + ToOffset,
22643 {
22644 while let Some(region) = self.last() {
22645 let all_selections_inside_invalidation_ranges =
22646 if selections.len() == region.ranges().len() {
22647 selections
22648 .iter()
22649 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
22650 .all(|(selection, invalidation_range)| {
22651 let head = selection.head().to_offset(buffer);
22652 invalidation_range.start <= head && invalidation_range.end >= head
22653 })
22654 } else {
22655 false
22656 };
22657
22658 if all_selections_inside_invalidation_ranges {
22659 break;
22660 } else {
22661 self.pop();
22662 }
22663 }
22664 }
22665}
22666
22667impl<T> Default for InvalidationStack<T> {
22668 fn default() -> Self {
22669 Self(Default::default())
22670 }
22671}
22672
22673impl<T> Deref for InvalidationStack<T> {
22674 type Target = Vec<T>;
22675
22676 fn deref(&self) -> &Self::Target {
22677 &self.0
22678 }
22679}
22680
22681impl<T> DerefMut for InvalidationStack<T> {
22682 fn deref_mut(&mut self) -> &mut Self::Target {
22683 &mut self.0
22684 }
22685}
22686
22687impl InvalidationRegion for SnippetState {
22688 fn ranges(&self) -> &[Range<Anchor>] {
22689 &self.ranges[self.active_index]
22690 }
22691}
22692
22693fn inline_completion_edit_text(
22694 current_snapshot: &BufferSnapshot,
22695 edits: &[(Range<Anchor>, String)],
22696 edit_preview: &EditPreview,
22697 include_deletions: bool,
22698 cx: &App,
22699) -> HighlightedText {
22700 let edits = edits
22701 .iter()
22702 .map(|(anchor, text)| {
22703 (
22704 anchor.start.text_anchor..anchor.end.text_anchor,
22705 text.clone(),
22706 )
22707 })
22708 .collect::<Vec<_>>();
22709
22710 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
22711}
22712
22713pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
22714 match severity {
22715 lsp::DiagnosticSeverity::ERROR => colors.error,
22716 lsp::DiagnosticSeverity::WARNING => colors.warning,
22717 lsp::DiagnosticSeverity::INFORMATION => colors.info,
22718 lsp::DiagnosticSeverity::HINT => colors.info,
22719 _ => colors.ignored,
22720 }
22721}
22722
22723pub fn styled_runs_for_code_label<'a>(
22724 label: &'a CodeLabel,
22725 syntax_theme: &'a theme::SyntaxTheme,
22726) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
22727 let fade_out = HighlightStyle {
22728 fade_out: Some(0.35),
22729 ..Default::default()
22730 };
22731
22732 let mut prev_end = label.filter_range.end;
22733 label
22734 .runs
22735 .iter()
22736 .enumerate()
22737 .flat_map(move |(ix, (range, highlight_id))| {
22738 let style = if let Some(style) = highlight_id.style(syntax_theme) {
22739 style
22740 } else {
22741 return Default::default();
22742 };
22743 let mut muted_style = style;
22744 muted_style.highlight(fade_out);
22745
22746 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
22747 if range.start >= label.filter_range.end {
22748 if range.start > prev_end {
22749 runs.push((prev_end..range.start, fade_out));
22750 }
22751 runs.push((range.clone(), muted_style));
22752 } else if range.end <= label.filter_range.end {
22753 runs.push((range.clone(), style));
22754 } else {
22755 runs.push((range.start..label.filter_range.end, style));
22756 runs.push((label.filter_range.end..range.end, muted_style));
22757 }
22758 prev_end = cmp::max(prev_end, range.end);
22759
22760 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
22761 runs.push((prev_end..label.text.len(), fade_out));
22762 }
22763
22764 runs
22765 })
22766}
22767
22768pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
22769 let mut prev_index = 0;
22770 let mut prev_codepoint: Option<char> = None;
22771 text.char_indices()
22772 .chain([(text.len(), '\0')])
22773 .filter_map(move |(index, codepoint)| {
22774 let prev_codepoint = prev_codepoint.replace(codepoint)?;
22775 let is_boundary = index == text.len()
22776 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
22777 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
22778 if is_boundary {
22779 let chunk = &text[prev_index..index];
22780 prev_index = index;
22781 Some(chunk)
22782 } else {
22783 None
22784 }
22785 })
22786}
22787
22788pub trait RangeToAnchorExt: Sized {
22789 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
22790
22791 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
22792 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
22793 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
22794 }
22795}
22796
22797impl<T: ToOffset> RangeToAnchorExt for Range<T> {
22798 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
22799 let start_offset = self.start.to_offset(snapshot);
22800 let end_offset = self.end.to_offset(snapshot);
22801 if start_offset == end_offset {
22802 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
22803 } else {
22804 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
22805 }
22806 }
22807}
22808
22809pub trait RowExt {
22810 fn as_f32(&self) -> f32;
22811
22812 fn next_row(&self) -> Self;
22813
22814 fn previous_row(&self) -> Self;
22815
22816 fn minus(&self, other: Self) -> u32;
22817}
22818
22819impl RowExt for DisplayRow {
22820 fn as_f32(&self) -> f32 {
22821 self.0 as f32
22822 }
22823
22824 fn next_row(&self) -> Self {
22825 Self(self.0 + 1)
22826 }
22827
22828 fn previous_row(&self) -> Self {
22829 Self(self.0.saturating_sub(1))
22830 }
22831
22832 fn minus(&self, other: Self) -> u32 {
22833 self.0 - other.0
22834 }
22835}
22836
22837impl RowExt for MultiBufferRow {
22838 fn as_f32(&self) -> f32 {
22839 self.0 as f32
22840 }
22841
22842 fn next_row(&self) -> Self {
22843 Self(self.0 + 1)
22844 }
22845
22846 fn previous_row(&self) -> Self {
22847 Self(self.0.saturating_sub(1))
22848 }
22849
22850 fn minus(&self, other: Self) -> u32 {
22851 self.0 - other.0
22852 }
22853}
22854
22855trait RowRangeExt {
22856 type Row;
22857
22858 fn len(&self) -> usize;
22859
22860 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
22861}
22862
22863impl RowRangeExt for Range<MultiBufferRow> {
22864 type Row = MultiBufferRow;
22865
22866 fn len(&self) -> usize {
22867 (self.end.0 - self.start.0) as usize
22868 }
22869
22870 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
22871 (self.start.0..self.end.0).map(MultiBufferRow)
22872 }
22873}
22874
22875impl RowRangeExt for Range<DisplayRow> {
22876 type Row = DisplayRow;
22877
22878 fn len(&self) -> usize {
22879 (self.end.0 - self.start.0) as usize
22880 }
22881
22882 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
22883 (self.start.0..self.end.0).map(DisplayRow)
22884 }
22885}
22886
22887/// If select range has more than one line, we
22888/// just point the cursor to range.start.
22889fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
22890 if range.start.row == range.end.row {
22891 range
22892 } else {
22893 range.start..range.start
22894 }
22895}
22896pub struct KillRing(ClipboardItem);
22897impl Global for KillRing {}
22898
22899const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
22900
22901enum BreakpointPromptEditAction {
22902 Log,
22903 Condition,
22904 HitCondition,
22905}
22906
22907struct BreakpointPromptEditor {
22908 pub(crate) prompt: Entity<Editor>,
22909 editor: WeakEntity<Editor>,
22910 breakpoint_anchor: Anchor,
22911 breakpoint: Breakpoint,
22912 edit_action: BreakpointPromptEditAction,
22913 block_ids: HashSet<CustomBlockId>,
22914 editor_margins: Arc<Mutex<EditorMargins>>,
22915 _subscriptions: Vec<Subscription>,
22916}
22917
22918impl BreakpointPromptEditor {
22919 const MAX_LINES: u8 = 4;
22920
22921 fn new(
22922 editor: WeakEntity<Editor>,
22923 breakpoint_anchor: Anchor,
22924 breakpoint: Breakpoint,
22925 edit_action: BreakpointPromptEditAction,
22926 window: &mut Window,
22927 cx: &mut Context<Self>,
22928 ) -> Self {
22929 let base_text = match edit_action {
22930 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
22931 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
22932 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
22933 }
22934 .map(|msg| msg.to_string())
22935 .unwrap_or_default();
22936
22937 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
22938 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
22939
22940 let prompt = cx.new(|cx| {
22941 let mut prompt = Editor::new(
22942 EditorMode::AutoHeight {
22943 min_lines: 1,
22944 max_lines: Some(Self::MAX_LINES as usize),
22945 },
22946 buffer,
22947 None,
22948 window,
22949 cx,
22950 );
22951 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
22952 prompt.set_show_cursor_when_unfocused(false, cx);
22953 prompt.set_placeholder_text(
22954 match edit_action {
22955 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
22956 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
22957 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
22958 },
22959 cx,
22960 );
22961
22962 prompt
22963 });
22964
22965 Self {
22966 prompt,
22967 editor,
22968 breakpoint_anchor,
22969 breakpoint,
22970 edit_action,
22971 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
22972 block_ids: Default::default(),
22973 _subscriptions: vec![],
22974 }
22975 }
22976
22977 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
22978 self.block_ids.extend(block_ids)
22979 }
22980
22981 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
22982 if let Some(editor) = self.editor.upgrade() {
22983 let message = self
22984 .prompt
22985 .read(cx)
22986 .buffer
22987 .read(cx)
22988 .as_singleton()
22989 .expect("A multi buffer in breakpoint prompt isn't possible")
22990 .read(cx)
22991 .as_rope()
22992 .to_string();
22993
22994 editor.update(cx, |editor, cx| {
22995 editor.edit_breakpoint_at_anchor(
22996 self.breakpoint_anchor,
22997 self.breakpoint.clone(),
22998 match self.edit_action {
22999 BreakpointPromptEditAction::Log => {
23000 BreakpointEditAction::EditLogMessage(message.into())
23001 }
23002 BreakpointPromptEditAction::Condition => {
23003 BreakpointEditAction::EditCondition(message.into())
23004 }
23005 BreakpointPromptEditAction::HitCondition => {
23006 BreakpointEditAction::EditHitCondition(message.into())
23007 }
23008 },
23009 cx,
23010 );
23011
23012 editor.remove_blocks(self.block_ids.clone(), None, cx);
23013 cx.focus_self(window);
23014 });
23015 }
23016 }
23017
23018 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23019 self.editor
23020 .update(cx, |editor, cx| {
23021 editor.remove_blocks(self.block_ids.clone(), None, cx);
23022 window.focus(&editor.focus_handle);
23023 })
23024 .log_err();
23025 }
23026
23027 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23028 let settings = ThemeSettings::get_global(cx);
23029 let text_style = TextStyle {
23030 color: if self.prompt.read(cx).read_only(cx) {
23031 cx.theme().colors().text_disabled
23032 } else {
23033 cx.theme().colors().text
23034 },
23035 font_family: settings.buffer_font.family.clone(),
23036 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23037 font_size: settings.buffer_font_size(cx).into(),
23038 font_weight: settings.buffer_font.weight,
23039 line_height: relative(settings.buffer_line_height.value()),
23040 ..Default::default()
23041 };
23042 EditorElement::new(
23043 &self.prompt,
23044 EditorStyle {
23045 background: cx.theme().colors().editor_background,
23046 local_player: cx.theme().players().local(),
23047 text: text_style,
23048 ..Default::default()
23049 },
23050 )
23051 }
23052}
23053
23054impl Render for BreakpointPromptEditor {
23055 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23056 let editor_margins = *self.editor_margins.lock();
23057 let gutter_dimensions = editor_margins.gutter;
23058 h_flex()
23059 .key_context("Editor")
23060 .bg(cx.theme().colors().editor_background)
23061 .border_y_1()
23062 .border_color(cx.theme().status().info_border)
23063 .size_full()
23064 .py(window.line_height() / 2.5)
23065 .on_action(cx.listener(Self::confirm))
23066 .on_action(cx.listener(Self::cancel))
23067 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23068 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23069 }
23070}
23071
23072impl Focusable for BreakpointPromptEditor {
23073 fn focus_handle(&self, cx: &App) -> FocusHandle {
23074 self.prompt.focus_handle(cx)
23075 }
23076}
23077
23078fn all_edits_insertions_or_deletions(
23079 edits: &Vec<(Range<Anchor>, String)>,
23080 snapshot: &MultiBufferSnapshot,
23081) -> bool {
23082 let mut all_insertions = true;
23083 let mut all_deletions = true;
23084
23085 for (range, new_text) in edits.iter() {
23086 let range_is_empty = range.to_offset(&snapshot).is_empty();
23087 let text_is_empty = new_text.is_empty();
23088
23089 if range_is_empty != text_is_empty {
23090 if range_is_empty {
23091 all_deletions = false;
23092 } else {
23093 all_insertions = false;
23094 }
23095 } else {
23096 return false;
23097 }
23098
23099 if !all_insertions && !all_deletions {
23100 return false;
23101 }
23102 }
23103 all_insertions || all_deletions
23104}
23105
23106struct MissingEditPredictionKeybindingTooltip;
23107
23108impl Render for MissingEditPredictionKeybindingTooltip {
23109 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23110 ui::tooltip_container(window, cx, |container, _, cx| {
23111 container
23112 .flex_shrink_0()
23113 .max_w_80()
23114 .min_h(rems_from_px(124.))
23115 .justify_between()
23116 .child(
23117 v_flex()
23118 .flex_1()
23119 .text_ui_sm(cx)
23120 .child(Label::new("Conflict with Accept Keybinding"))
23121 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23122 )
23123 .child(
23124 h_flex()
23125 .pb_1()
23126 .gap_1()
23127 .items_end()
23128 .w_full()
23129 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23130 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23131 }))
23132 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23133 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23134 })),
23135 )
23136 })
23137 }
23138}
23139
23140#[derive(Debug, Clone, Copy, PartialEq)]
23141pub struct LineHighlight {
23142 pub background: Background,
23143 pub border: Option<gpui::Hsla>,
23144 pub include_gutter: bool,
23145 pub type_id: Option<TypeId>,
23146}
23147
23148struct LineManipulationResult {
23149 pub new_text: String,
23150 pub line_count_before: usize,
23151 pub line_count_after: usize,
23152}
23153
23154fn render_diff_hunk_controls(
23155 row: u32,
23156 status: &DiffHunkStatus,
23157 hunk_range: Range<Anchor>,
23158 is_created_file: bool,
23159 line_height: Pixels,
23160 editor: &Entity<Editor>,
23161 _window: &mut Window,
23162 cx: &mut App,
23163) -> AnyElement {
23164 h_flex()
23165 .h(line_height)
23166 .mr_1()
23167 .gap_1()
23168 .px_0p5()
23169 .pb_1()
23170 .border_x_1()
23171 .border_b_1()
23172 .border_color(cx.theme().colors().border_variant)
23173 .rounded_b_lg()
23174 .bg(cx.theme().colors().editor_background)
23175 .gap_1()
23176 .block_mouse_except_scroll()
23177 .shadow_md()
23178 .child(if status.has_secondary_hunk() {
23179 Button::new(("stage", row as u64), "Stage")
23180 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23181 .tooltip({
23182 let focus_handle = editor.focus_handle(cx);
23183 move |window, cx| {
23184 Tooltip::for_action_in(
23185 "Stage Hunk",
23186 &::git::ToggleStaged,
23187 &focus_handle,
23188 window,
23189 cx,
23190 )
23191 }
23192 })
23193 .on_click({
23194 let editor = editor.clone();
23195 move |_event, _window, cx| {
23196 editor.update(cx, |editor, cx| {
23197 editor.stage_or_unstage_diff_hunks(
23198 true,
23199 vec![hunk_range.start..hunk_range.start],
23200 cx,
23201 );
23202 });
23203 }
23204 })
23205 } else {
23206 Button::new(("unstage", row as u64), "Unstage")
23207 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23208 .tooltip({
23209 let focus_handle = editor.focus_handle(cx);
23210 move |window, cx| {
23211 Tooltip::for_action_in(
23212 "Unstage Hunk",
23213 &::git::ToggleStaged,
23214 &focus_handle,
23215 window,
23216 cx,
23217 )
23218 }
23219 })
23220 .on_click({
23221 let editor = editor.clone();
23222 move |_event, _window, cx| {
23223 editor.update(cx, |editor, cx| {
23224 editor.stage_or_unstage_diff_hunks(
23225 false,
23226 vec![hunk_range.start..hunk_range.start],
23227 cx,
23228 );
23229 });
23230 }
23231 })
23232 })
23233 .child(
23234 Button::new(("restore", row as u64), "Restore")
23235 .tooltip({
23236 let focus_handle = editor.focus_handle(cx);
23237 move |window, cx| {
23238 Tooltip::for_action_in(
23239 "Restore Hunk",
23240 &::git::Restore,
23241 &focus_handle,
23242 window,
23243 cx,
23244 )
23245 }
23246 })
23247 .on_click({
23248 let editor = editor.clone();
23249 move |_event, window, cx| {
23250 editor.update(cx, |editor, cx| {
23251 let snapshot = editor.snapshot(window, cx);
23252 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23253 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23254 });
23255 }
23256 })
23257 .disabled(is_created_file),
23258 )
23259 .when(
23260 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23261 |el| {
23262 el.child(
23263 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23264 .shape(IconButtonShape::Square)
23265 .icon_size(IconSize::Small)
23266 // .disabled(!has_multiple_hunks)
23267 .tooltip({
23268 let focus_handle = editor.focus_handle(cx);
23269 move |window, cx| {
23270 Tooltip::for_action_in(
23271 "Next Hunk",
23272 &GoToHunk,
23273 &focus_handle,
23274 window,
23275 cx,
23276 )
23277 }
23278 })
23279 .on_click({
23280 let editor = editor.clone();
23281 move |_event, window, cx| {
23282 editor.update(cx, |editor, cx| {
23283 let snapshot = editor.snapshot(window, cx);
23284 let position =
23285 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23286 editor.go_to_hunk_before_or_after_position(
23287 &snapshot,
23288 position,
23289 Direction::Next,
23290 window,
23291 cx,
23292 );
23293 editor.expand_selected_diff_hunks(cx);
23294 });
23295 }
23296 }),
23297 )
23298 .child(
23299 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23300 .shape(IconButtonShape::Square)
23301 .icon_size(IconSize::Small)
23302 // .disabled(!has_multiple_hunks)
23303 .tooltip({
23304 let focus_handle = editor.focus_handle(cx);
23305 move |window, cx| {
23306 Tooltip::for_action_in(
23307 "Previous Hunk",
23308 &GoToPreviousHunk,
23309 &focus_handle,
23310 window,
23311 cx,
23312 )
23313 }
23314 })
23315 .on_click({
23316 let editor = editor.clone();
23317 move |_event, window, cx| {
23318 editor.update(cx, |editor, cx| {
23319 let snapshot = editor.snapshot(window, cx);
23320 let point =
23321 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23322 editor.go_to_hunk_before_or_after_position(
23323 &snapshot,
23324 point,
23325 Direction::Prev,
23326 window,
23327 cx,
23328 );
23329 editor.expand_selected_diff_hunks(cx);
23330 });
23331 }
23332 }),
23333 )
23334 },
23335 )
23336 .into_any_element()
23337}