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 feature_flags::{DebuggerFeatureFlag, FeatureFlagAppExt};
77use futures::{
78 FutureExt, StreamExt as _,
79 future::{self, Shared, join},
80 stream::FuturesUnordered,
81};
82use fuzzy::{StringMatch, StringMatchCandidate};
83use lsp_colors::LspColorData;
84
85use ::git::blame::BlameEntry;
86use ::git::{Restore, blame::ParsedCommitMessage};
87use code_context_menus::{
88 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
89 CompletionsMenu, ContextMenuOrigin,
90};
91use git::blame::{GitBlame, GlobalBlameRenderer};
92use gpui::{
93 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
94 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
95 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
96 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
97 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
98 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
99 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
100 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
101};
102use highlight_matching_bracket::refresh_matching_bracket_highlights;
103use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
104pub use hover_popover::hover_markdown_style;
105use hover_popover::{HoverState, hide_hover};
106use indent_guides::ActiveIndentGuidesState;
107use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
108pub use inline_completion::Direction;
109use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
110pub use items::MAX_TAB_TITLE_LEN;
111use itertools::Itertools;
112use language::{
113 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
114 CursorShape, DiagnosticEntry, DiffOptions, DocumentationConfig, EditPredictionsMode,
115 EditPreview, HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point,
116 Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
117 language_settings::{
118 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
119 all_language_settings, language_settings,
120 },
121 point_from_lsp, text_diff_with_options,
122};
123use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
124use linked_editing_ranges::refresh_linked_ranges;
125use markdown::Markdown;
126use mouse_context_menu::MouseContextMenu;
127use persistence::DB;
128use project::{
129 BreakpointWithPosition, CompletionResponse, ProjectPath,
130 debugger::{
131 breakpoint_store::{
132 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
133 BreakpointStoreEvent,
134 },
135 session::{Session, SessionEvent},
136 },
137 git_store::{GitStoreEvent, RepositoryEvent},
138 project_settings::DiagnosticSeverity,
139};
140
141pub use git::blame::BlameRenderer;
142pub use proposed_changes_editor::{
143 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
144};
145use std::{cell::OnceCell, iter::Peekable, ops::Not};
146use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
147
148pub use lsp::CompletionContext;
149use lsp::{
150 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
151 LanguageServerId, LanguageServerName,
152};
153
154use language::BufferSnapshot;
155pub use lsp_ext::lsp_tasks;
156use movement::TextLayoutDetails;
157pub use multi_buffer::{
158 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
159 RowInfo, ToOffset, ToPoint,
160};
161use multi_buffer::{
162 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
163 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
164};
165use parking_lot::Mutex;
166use project::{
167 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
168 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
169 TaskSourceKind,
170 debugger::breakpoint_store::Breakpoint,
171 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
172 project_settings::{GitGutterSetting, ProjectSettings},
173};
174use rand::prelude::*;
175use rpc::{ErrorExt, proto::*};
176use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
177use selections_collection::{
178 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
179};
180use serde::{Deserialize, Serialize};
181use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
182use smallvec::{SmallVec, smallvec};
183use snippet::Snippet;
184use std::sync::Arc;
185use std::{
186 any::TypeId,
187 borrow::Cow,
188 cell::RefCell,
189 cmp::{self, Ordering, Reverse},
190 mem,
191 num::NonZeroU32,
192 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
193 path::{Path, PathBuf},
194 rc::Rc,
195 time::{Duration, Instant},
196};
197pub use sum_tree::Bias;
198use sum_tree::TreeMap;
199use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
200use theme::{
201 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
202 observe_buffer_font_size_adjustment,
203};
204use ui::{
205 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
206 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
207};
208use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
209use workspace::{
210 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
211 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
212 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
213 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
214 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
215 searchable::SearchEvent,
216};
217
218use crate::{
219 code_context_menus::CompletionsMenuSource,
220 hover_links::{find_url, find_url_from_range},
221};
222use crate::{
223 editor_settings::MultiCursorModifier,
224 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
225};
226
227pub const FILE_HEADER_HEIGHT: u32 = 2;
228pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
229pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
230const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
231const MAX_LINE_LEN: usize = 1024;
232const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
233const MAX_SELECTION_HISTORY_LEN: usize = 1024;
234pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
235#[doc(hidden)]
236pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
237const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
238
239pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
240pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
241pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
242
243pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
244pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
245pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
246
247pub type RenderDiffHunkControlsFn = Arc<
248 dyn Fn(
249 u32,
250 &DiffHunkStatus,
251 Range<Anchor>,
252 bool,
253 Pixels,
254 &Entity<Editor>,
255 &mut Window,
256 &mut App,
257 ) -> AnyElement,
258>;
259
260struct InlineValueCache {
261 enabled: bool,
262 inlays: Vec<InlayId>,
263 refresh_task: Task<Option<()>>,
264}
265
266impl InlineValueCache {
267 fn new(enabled: bool) -> Self {
268 Self {
269 enabled,
270 inlays: Vec::new(),
271 refresh_task: Task::ready(None),
272 }
273 }
274}
275
276#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
277pub enum InlayId {
278 InlineCompletion(usize),
279 DebuggerValue(usize),
280 // LSP
281 Hint(usize),
282 Color(usize),
283}
284
285impl InlayId {
286 fn id(&self) -> usize {
287 match self {
288 Self::InlineCompletion(id) => *id,
289 Self::DebuggerValue(id) => *id,
290 Self::Hint(id) => *id,
291 Self::Color(id) => *id,
292 }
293 }
294}
295
296pub enum ActiveDebugLine {}
297pub enum DebugStackFrameLine {}
298enum DocumentHighlightRead {}
299enum DocumentHighlightWrite {}
300enum InputComposition {}
301pub enum PendingInput {}
302enum SelectedTextHighlight {}
303
304pub enum ConflictsOuter {}
305pub enum ConflictsOurs {}
306pub enum ConflictsTheirs {}
307pub enum ConflictsOursMarker {}
308pub enum ConflictsTheirsMarker {}
309
310#[derive(Debug, Copy, Clone, PartialEq, Eq)]
311pub enum Navigated {
312 Yes,
313 No,
314}
315
316impl Navigated {
317 pub fn from_bool(yes: bool) -> Navigated {
318 if yes { Navigated::Yes } else { Navigated::No }
319 }
320}
321
322#[derive(Debug, Clone, PartialEq, Eq)]
323enum DisplayDiffHunk {
324 Folded {
325 display_row: DisplayRow,
326 },
327 Unfolded {
328 is_created_file: bool,
329 diff_base_byte_range: Range<usize>,
330 display_row_range: Range<DisplayRow>,
331 multi_buffer_range: Range<Anchor>,
332 status: DiffHunkStatus,
333 },
334}
335
336pub enum HideMouseCursorOrigin {
337 TypingAction,
338 MovementAction,
339}
340
341pub fn init_settings(cx: &mut App) {
342 EditorSettings::register(cx);
343}
344
345pub fn init(cx: &mut App) {
346 init_settings(cx);
347
348 cx.set_global(GlobalBlameRenderer(Arc::new(())));
349
350 workspace::register_project_item::<Editor>(cx);
351 workspace::FollowableViewRegistry::register::<Editor>(cx);
352 workspace::register_serializable_item::<Editor>(cx);
353
354 cx.observe_new(
355 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
356 workspace.register_action(Editor::new_file);
357 workspace.register_action(Editor::new_file_vertical);
358 workspace.register_action(Editor::new_file_horizontal);
359 workspace.register_action(Editor::cancel_language_server_work);
360 },
361 )
362 .detach();
363
364 cx.on_action(move |_: &workspace::NewFile, cx| {
365 let app_state = workspace::AppState::global(cx);
366 if let Some(app_state) = app_state.upgrade() {
367 workspace::open_new(
368 Default::default(),
369 app_state,
370 cx,
371 |workspace, window, cx| {
372 Editor::new_file(workspace, &Default::default(), window, cx)
373 },
374 )
375 .detach();
376 }
377 });
378 cx.on_action(move |_: &workspace::NewWindow, cx| {
379 let app_state = workspace::AppState::global(cx);
380 if let Some(app_state) = app_state.upgrade() {
381 workspace::open_new(
382 Default::default(),
383 app_state,
384 cx,
385 |workspace, window, cx| {
386 cx.activate(true);
387 Editor::new_file(workspace, &Default::default(), window, cx)
388 },
389 )
390 .detach();
391 }
392 });
393}
394
395pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
396 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
397}
398
399pub trait DiagnosticRenderer {
400 fn render_group(
401 &self,
402 diagnostic_group: Vec<DiagnosticEntry<Point>>,
403 buffer_id: BufferId,
404 snapshot: EditorSnapshot,
405 editor: WeakEntity<Editor>,
406 cx: &mut App,
407 ) -> Vec<BlockProperties<Anchor>>;
408
409 fn render_hover(
410 &self,
411 diagnostic_group: Vec<DiagnosticEntry<Point>>,
412 range: Range<Point>,
413 buffer_id: BufferId,
414 cx: &mut App,
415 ) -> Option<Entity<markdown::Markdown>>;
416
417 fn open_link(
418 &self,
419 editor: &mut Editor,
420 link: SharedString,
421 window: &mut Window,
422 cx: &mut Context<Editor>,
423 );
424}
425
426pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
427
428impl GlobalDiagnosticRenderer {
429 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
430 cx.try_global::<Self>().map(|g| g.0.clone())
431 }
432}
433
434impl gpui::Global for GlobalDiagnosticRenderer {}
435pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
436 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
437}
438
439pub struct SearchWithinRange;
440
441trait InvalidationRegion {
442 fn ranges(&self) -> &[Range<Anchor>];
443}
444
445#[derive(Clone, Debug, PartialEq)]
446pub enum SelectPhase {
447 Begin {
448 position: DisplayPoint,
449 add: bool,
450 click_count: usize,
451 },
452 BeginColumnar {
453 position: DisplayPoint,
454 reset: bool,
455 mode: ColumnarMode,
456 goal_column: u32,
457 },
458 Extend {
459 position: DisplayPoint,
460 click_count: usize,
461 },
462 Update {
463 position: DisplayPoint,
464 goal_column: u32,
465 scroll_delta: gpui::Point<f32>,
466 },
467 End,
468}
469
470#[derive(Clone, Debug, PartialEq)]
471pub enum ColumnarMode {
472 FromMouse,
473 FromSelection,
474}
475
476#[derive(Clone, Debug)]
477pub enum SelectMode {
478 Character,
479 Word(Range<Anchor>),
480 Line(Range<Anchor>),
481 All,
482}
483
484#[derive(Clone, PartialEq, Eq, Debug)]
485pub enum EditorMode {
486 SingleLine {
487 auto_width: bool,
488 },
489 AutoHeight {
490 min_lines: usize,
491 max_lines: usize,
492 },
493 Full {
494 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
495 scale_ui_elements_with_buffer_font_size: bool,
496 /// When set to `true`, the editor will render a background for the active line.
497 show_active_line_background: bool,
498 /// When set to `true`, the editor's height will be determined by its content.
499 sized_by_content: bool,
500 },
501 Minimap {
502 parent: WeakEntity<Editor>,
503 },
504}
505
506impl EditorMode {
507 pub fn full() -> Self {
508 Self::Full {
509 scale_ui_elements_with_buffer_font_size: true,
510 show_active_line_background: true,
511 sized_by_content: false,
512 }
513 }
514
515 pub fn is_full(&self) -> bool {
516 matches!(self, Self::Full { .. })
517 }
518
519 fn is_minimap(&self) -> bool {
520 matches!(self, Self::Minimap { .. })
521 }
522}
523
524#[derive(Copy, Clone, Debug)]
525pub enum SoftWrap {
526 /// Prefer not to wrap at all.
527 ///
528 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
529 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
530 GitDiff,
531 /// Prefer a single line generally, unless an overly long line is encountered.
532 None,
533 /// Soft wrap lines that exceed the editor width.
534 EditorWidth,
535 /// Soft wrap lines at the preferred line length.
536 Column(u32),
537 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
538 Bounded(u32),
539}
540
541#[derive(Clone)]
542pub struct EditorStyle {
543 pub background: Hsla,
544 pub local_player: PlayerColor,
545 pub text: TextStyle,
546 pub scrollbar_width: Pixels,
547 pub syntax: Arc<SyntaxTheme>,
548 pub status: StatusColors,
549 pub inlay_hints_style: HighlightStyle,
550 pub inline_completion_styles: InlineCompletionStyles,
551 pub unnecessary_code_fade: f32,
552 pub show_underlines: bool,
553}
554
555impl Default for EditorStyle {
556 fn default() -> Self {
557 Self {
558 background: Hsla::default(),
559 local_player: PlayerColor::default(),
560 text: TextStyle::default(),
561 scrollbar_width: Pixels::default(),
562 syntax: Default::default(),
563 // HACK: Status colors don't have a real default.
564 // We should look into removing the status colors from the editor
565 // style and retrieve them directly from the theme.
566 status: StatusColors::dark(),
567 inlay_hints_style: HighlightStyle::default(),
568 inline_completion_styles: InlineCompletionStyles {
569 insertion: HighlightStyle::default(),
570 whitespace: HighlightStyle::default(),
571 },
572 unnecessary_code_fade: Default::default(),
573 show_underlines: true,
574 }
575 }
576}
577
578pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
579 let show_background = language_settings::language_settings(None, None, cx)
580 .inlay_hints
581 .show_background;
582
583 HighlightStyle {
584 color: Some(cx.theme().status().hint),
585 background_color: show_background.then(|| cx.theme().status().hint_background),
586 ..HighlightStyle::default()
587 }
588}
589
590pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
591 InlineCompletionStyles {
592 insertion: HighlightStyle {
593 color: Some(cx.theme().status().predictive),
594 ..HighlightStyle::default()
595 },
596 whitespace: HighlightStyle {
597 background_color: Some(cx.theme().status().created_background),
598 ..HighlightStyle::default()
599 },
600 }
601}
602
603type CompletionId = usize;
604
605pub(crate) enum EditDisplayMode {
606 TabAccept,
607 DiffPopover,
608 Inline,
609}
610
611enum InlineCompletion {
612 Edit {
613 edits: Vec<(Range<Anchor>, String)>,
614 edit_preview: Option<EditPreview>,
615 display_mode: EditDisplayMode,
616 snapshot: BufferSnapshot,
617 },
618 Move {
619 target: Anchor,
620 snapshot: BufferSnapshot,
621 },
622}
623
624struct InlineCompletionState {
625 inlay_ids: Vec<InlayId>,
626 completion: InlineCompletion,
627 completion_id: Option<SharedString>,
628 invalidation_range: Range<Anchor>,
629}
630
631enum EditPredictionSettings {
632 Disabled,
633 Enabled {
634 show_in_menu: bool,
635 preview_requires_modifier: bool,
636 },
637}
638
639enum InlineCompletionHighlight {}
640
641#[derive(Debug, Clone)]
642struct InlineDiagnostic {
643 message: SharedString,
644 group_id: usize,
645 is_primary: bool,
646 start: Point,
647 severity: lsp::DiagnosticSeverity,
648}
649
650pub enum MenuInlineCompletionsPolicy {
651 Never,
652 ByProvider,
653}
654
655pub enum EditPredictionPreview {
656 /// Modifier is not pressed
657 Inactive { released_too_fast: bool },
658 /// Modifier pressed
659 Active {
660 since: Instant,
661 previous_scroll_position: Option<ScrollAnchor>,
662 },
663}
664
665impl EditPredictionPreview {
666 pub fn released_too_fast(&self) -> bool {
667 match self {
668 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
669 EditPredictionPreview::Active { .. } => false,
670 }
671 }
672
673 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
674 if let EditPredictionPreview::Active {
675 previous_scroll_position,
676 ..
677 } = self
678 {
679 *previous_scroll_position = scroll_position;
680 }
681 }
682}
683
684pub struct ContextMenuOptions {
685 pub min_entries_visible: usize,
686 pub max_entries_visible: usize,
687 pub placement: Option<ContextMenuPlacement>,
688}
689
690#[derive(Debug, Clone, PartialEq, Eq)]
691pub enum ContextMenuPlacement {
692 Above,
693 Below,
694}
695
696#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
697struct EditorActionId(usize);
698
699impl EditorActionId {
700 pub fn post_inc(&mut self) -> Self {
701 let answer = self.0;
702
703 *self = Self(answer + 1);
704
705 Self(answer)
706 }
707}
708
709// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
710// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
711
712#[derive(Clone)]
713pub struct BackgroundHighlight {
714 pub range: Range<Anchor>,
715 pub color_fetcher: fn(&Theme) -> Hsla,
716}
717
718type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
719
720#[derive(Default)]
721struct ScrollbarMarkerState {
722 scrollbar_size: Size<Pixels>,
723 dirty: bool,
724 markers: Arc<[PaintQuad]>,
725 pending_refresh: Option<Task<Result<()>>>,
726}
727
728impl ScrollbarMarkerState {
729 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
730 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
731 }
732}
733
734#[derive(Clone, Copy, PartialEq, Eq)]
735pub enum MinimapVisibility {
736 Disabled,
737 Enabled {
738 /// The configuration currently present in the users settings.
739 setting_configuration: bool,
740 /// Whether to override the currently set visibility from the users setting.
741 toggle_override: bool,
742 },
743}
744
745impl MinimapVisibility {
746 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
747 if mode.is_full() {
748 Self::Enabled {
749 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
750 toggle_override: false,
751 }
752 } else {
753 Self::Disabled
754 }
755 }
756
757 fn hidden(&self) -> Self {
758 match *self {
759 Self::Enabled {
760 setting_configuration,
761 ..
762 } => Self::Enabled {
763 setting_configuration,
764 toggle_override: setting_configuration,
765 },
766 Self::Disabled => Self::Disabled,
767 }
768 }
769
770 fn disabled(&self) -> bool {
771 match *self {
772 Self::Disabled => true,
773 _ => false,
774 }
775 }
776
777 fn settings_visibility(&self) -> bool {
778 match *self {
779 Self::Enabled {
780 setting_configuration,
781 ..
782 } => setting_configuration,
783 _ => false,
784 }
785 }
786
787 fn visible(&self) -> bool {
788 match *self {
789 Self::Enabled {
790 setting_configuration,
791 toggle_override,
792 } => setting_configuration ^ toggle_override,
793 _ => false,
794 }
795 }
796
797 fn toggle_visibility(&self) -> Self {
798 match *self {
799 Self::Enabled {
800 toggle_override,
801 setting_configuration,
802 } => Self::Enabled {
803 setting_configuration,
804 toggle_override: !toggle_override,
805 },
806 Self::Disabled => Self::Disabled,
807 }
808 }
809}
810
811#[derive(Clone, Debug)]
812struct RunnableTasks {
813 templates: Vec<(TaskSourceKind, TaskTemplate)>,
814 offset: multi_buffer::Anchor,
815 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
816 column: u32,
817 // Values of all named captures, including those starting with '_'
818 extra_variables: HashMap<String, String>,
819 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
820 context_range: Range<BufferOffset>,
821}
822
823impl RunnableTasks {
824 fn resolve<'a>(
825 &'a self,
826 cx: &'a task::TaskContext,
827 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
828 self.templates.iter().filter_map(|(kind, template)| {
829 template
830 .resolve_task(&kind.to_id_base(), cx)
831 .map(|task| (kind.clone(), task))
832 })
833 }
834}
835
836#[derive(Clone)]
837pub struct ResolvedTasks {
838 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
839 position: Anchor,
840}
841
842#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
843struct BufferOffset(usize);
844
845// Addons allow storing per-editor state in other crates (e.g. Vim)
846pub trait Addon: 'static {
847 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
848
849 fn render_buffer_header_controls(
850 &self,
851 _: &ExcerptInfo,
852 _: &Window,
853 _: &App,
854 ) -> Option<AnyElement> {
855 None
856 }
857
858 fn to_any(&self) -> &dyn std::any::Any;
859
860 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
861 None
862 }
863}
864
865/// A set of caret positions, registered when the editor was edited.
866pub struct ChangeList {
867 changes: Vec<Vec<Anchor>>,
868 /// Currently "selected" change.
869 position: Option<usize>,
870}
871
872impl ChangeList {
873 pub fn new() -> Self {
874 Self {
875 changes: Vec::new(),
876 position: None,
877 }
878 }
879
880 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
881 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
882 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
883 if self.changes.is_empty() {
884 return None;
885 }
886
887 let prev = self.position.unwrap_or(self.changes.len());
888 let next = if direction == Direction::Prev {
889 prev.saturating_sub(count)
890 } else {
891 (prev + count).min(self.changes.len() - 1)
892 };
893 self.position = Some(next);
894 self.changes.get(next).map(|anchors| anchors.as_slice())
895 }
896
897 /// Adds a new change to the list, resetting the change list position.
898 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
899 self.position.take();
900 if pop_state {
901 self.changes.pop();
902 }
903 self.changes.push(new_positions.clone());
904 }
905
906 pub fn last(&self) -> Option<&[Anchor]> {
907 self.changes.last().map(|anchors| anchors.as_slice())
908 }
909}
910
911#[derive(Clone)]
912struct InlineBlamePopoverState {
913 scroll_handle: ScrollHandle,
914 commit_message: Option<ParsedCommitMessage>,
915 markdown: Entity<Markdown>,
916}
917
918struct InlineBlamePopover {
919 position: gpui::Point<Pixels>,
920 hide_task: Option<Task<()>>,
921 popover_bounds: Option<Bounds<Pixels>>,
922 popover_state: InlineBlamePopoverState,
923}
924
925enum SelectionDragState {
926 /// State when no drag related activity is detected.
927 None,
928 /// State when the mouse is down on a selection that is about to be dragged.
929 ReadyToDrag {
930 selection: Selection<Anchor>,
931 click_position: gpui::Point<Pixels>,
932 mouse_down_time: Instant,
933 },
934 /// State when the mouse is dragging the selection in the editor.
935 Dragging {
936 selection: Selection<Anchor>,
937 drop_cursor: Selection<Anchor>,
938 hide_drop_cursor: bool,
939 },
940}
941
942enum ColumnarSelectionState {
943 FromMouse {
944 selection_tail: Anchor,
945 display_point: Option<DisplayPoint>,
946 },
947 FromSelection {
948 selection_tail: Anchor,
949 },
950}
951
952/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
953/// a breakpoint on them.
954#[derive(Clone, Copy, Debug, PartialEq, Eq)]
955struct PhantomBreakpointIndicator {
956 display_row: DisplayRow,
957 /// There's a small debounce between hovering over the line and showing the indicator.
958 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
959 is_active: bool,
960 collides_with_existing_breakpoint: bool,
961}
962
963/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
964///
965/// See the [module level documentation](self) for more information.
966pub struct Editor {
967 focus_handle: FocusHandle,
968 last_focused_descendant: Option<WeakFocusHandle>,
969 /// The text buffer being edited
970 buffer: Entity<MultiBuffer>,
971 /// Map of how text in the buffer should be displayed.
972 /// Handles soft wraps, folds, fake inlay text insertions, etc.
973 pub display_map: Entity<DisplayMap>,
974 pub selections: SelectionsCollection,
975 pub scroll_manager: ScrollManager,
976 /// When inline assist editors are linked, they all render cursors because
977 /// typing enters text into each of them, even the ones that aren't focused.
978 pub(crate) show_cursor_when_unfocused: bool,
979 columnar_selection_state: Option<ColumnarSelectionState>,
980 add_selections_state: Option<AddSelectionsState>,
981 select_next_state: Option<SelectNextState>,
982 select_prev_state: Option<SelectNextState>,
983 selection_history: SelectionHistory,
984 defer_selection_effects: bool,
985 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
986 autoclose_regions: Vec<AutocloseRegion>,
987 snippet_stack: InvalidationStack<SnippetState>,
988 select_syntax_node_history: SelectSyntaxNodeHistory,
989 ime_transaction: Option<TransactionId>,
990 pub diagnostics_max_severity: DiagnosticSeverity,
991 active_diagnostics: ActiveDiagnostic,
992 show_inline_diagnostics: bool,
993 inline_diagnostics_update: Task<()>,
994 inline_diagnostics_enabled: bool,
995 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
996 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
997 hard_wrap: Option<usize>,
998
999 // TODO: make this a access method
1000 pub project: Option<Entity<Project>>,
1001 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1002 completion_provider: Option<Rc<dyn CompletionProvider>>,
1003 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1004 blink_manager: Entity<BlinkManager>,
1005 show_cursor_names: bool,
1006 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1007 pub show_local_selections: bool,
1008 mode: EditorMode,
1009 show_breadcrumbs: bool,
1010 show_gutter: bool,
1011 show_scrollbars: ScrollbarAxes,
1012 minimap_visibility: MinimapVisibility,
1013 offset_content: bool,
1014 disable_expand_excerpt_buttons: bool,
1015 show_line_numbers: Option<bool>,
1016 use_relative_line_numbers: Option<bool>,
1017 show_git_diff_gutter: Option<bool>,
1018 show_code_actions: Option<bool>,
1019 show_runnables: Option<bool>,
1020 show_breakpoints: Option<bool>,
1021 show_wrap_guides: Option<bool>,
1022 show_indent_guides: Option<bool>,
1023 placeholder_text: Option<Arc<str>>,
1024 highlight_order: usize,
1025 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1026 background_highlights: TreeMap<TypeId, Vec<BackgroundHighlight>>,
1027 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1028 scrollbar_marker_state: ScrollbarMarkerState,
1029 active_indent_guides_state: ActiveIndentGuidesState,
1030 nav_history: Option<ItemNavHistory>,
1031 context_menu: RefCell<Option<CodeContextMenu>>,
1032 context_menu_options: Option<ContextMenuOptions>,
1033 mouse_context_menu: Option<MouseContextMenu>,
1034 completion_tasks: Vec<(CompletionId, Task<()>)>,
1035 inline_blame_popover: Option<InlineBlamePopover>,
1036 inline_blame_popover_show_task: Option<Task<()>>,
1037 signature_help_state: SignatureHelpState,
1038 auto_signature_help: Option<bool>,
1039 find_all_references_task_sources: Vec<Anchor>,
1040 next_completion_id: CompletionId,
1041 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1042 code_actions_task: Option<Task<Result<()>>>,
1043 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1044 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1045 document_highlights_task: Option<Task<()>>,
1046 linked_editing_range_task: Option<Task<Option<()>>>,
1047 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1048 pending_rename: Option<RenameState>,
1049 searchable: bool,
1050 cursor_shape: CursorShape,
1051 current_line_highlight: Option<CurrentLineHighlight>,
1052 collapse_matches: bool,
1053 autoindent_mode: Option<AutoindentMode>,
1054 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1055 input_enabled: bool,
1056 use_modal_editing: bool,
1057 read_only: bool,
1058 leader_id: Option<CollaboratorId>,
1059 remote_id: Option<ViewId>,
1060 pub hover_state: HoverState,
1061 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1062 gutter_hovered: bool,
1063 hovered_link_state: Option<HoveredLinkState>,
1064 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1065 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1066 active_inline_completion: Option<InlineCompletionState>,
1067 /// Used to prevent flickering as the user types while the menu is open
1068 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1069 edit_prediction_settings: EditPredictionSettings,
1070 inline_completions_hidden_for_vim_mode: bool,
1071 show_inline_completions_override: Option<bool>,
1072 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1073 edit_prediction_preview: EditPredictionPreview,
1074 edit_prediction_indent_conflict: bool,
1075 edit_prediction_requires_modifier_in_indent_conflict: bool,
1076 inlay_hint_cache: InlayHintCache,
1077 next_inlay_id: usize,
1078 _subscriptions: Vec<Subscription>,
1079 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1080 gutter_dimensions: GutterDimensions,
1081 style: Option<EditorStyle>,
1082 text_style_refinement: Option<TextStyleRefinement>,
1083 next_editor_action_id: EditorActionId,
1084 editor_actions: Rc<
1085 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1086 >,
1087 use_autoclose: bool,
1088 use_auto_surround: bool,
1089 auto_replace_emoji_shortcode: bool,
1090 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1091 show_git_blame_gutter: bool,
1092 show_git_blame_inline: bool,
1093 show_git_blame_inline_delay_task: Option<Task<()>>,
1094 git_blame_inline_enabled: bool,
1095 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1096 serialize_dirty_buffers: bool,
1097 show_selection_menu: Option<bool>,
1098 blame: Option<Entity<GitBlame>>,
1099 blame_subscription: Option<Subscription>,
1100 custom_context_menu: Option<
1101 Box<
1102 dyn 'static
1103 + Fn(
1104 &mut Self,
1105 DisplayPoint,
1106 &mut Window,
1107 &mut Context<Self>,
1108 ) -> Option<Entity<ui::ContextMenu>>,
1109 >,
1110 >,
1111 last_bounds: Option<Bounds<Pixels>>,
1112 last_position_map: Option<Rc<PositionMap>>,
1113 expect_bounds_change: Option<Bounds<Pixels>>,
1114 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1115 tasks_update_task: Option<Task<()>>,
1116 breakpoint_store: Option<Entity<BreakpointStore>>,
1117 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1118 hovered_diff_hunk_row: Option<DisplayRow>,
1119 pull_diagnostics_task: Task<()>,
1120 in_project_search: bool,
1121 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1122 breadcrumb_header: Option<String>,
1123 focused_block: Option<FocusedBlock>,
1124 next_scroll_position: NextScrollCursorCenterTopBottom,
1125 addons: HashMap<TypeId, Box<dyn Addon>>,
1126 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1127 load_diff_task: Option<Shared<Task<()>>>,
1128 /// Whether we are temporarily displaying a diff other than git's
1129 temporary_diff_override: bool,
1130 selection_mark_mode: bool,
1131 toggle_fold_multiple_buffers: Task<()>,
1132 _scroll_cursor_center_top_bottom_task: Task<()>,
1133 serialize_selections: Task<()>,
1134 serialize_folds: Task<()>,
1135 mouse_cursor_hidden: bool,
1136 minimap: Option<Entity<Self>>,
1137 hide_mouse_mode: HideMouseMode,
1138 pub change_list: ChangeList,
1139 inline_value_cache: InlineValueCache,
1140 selection_drag_state: SelectionDragState,
1141 drag_and_drop_selection_enabled: bool,
1142 next_color_inlay_id: usize,
1143 colors: Option<LspColorData>,
1144}
1145
1146#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1147enum NextScrollCursorCenterTopBottom {
1148 #[default]
1149 Center,
1150 Top,
1151 Bottom,
1152}
1153
1154impl NextScrollCursorCenterTopBottom {
1155 fn next(&self) -> Self {
1156 match self {
1157 Self::Center => Self::Top,
1158 Self::Top => Self::Bottom,
1159 Self::Bottom => Self::Center,
1160 }
1161 }
1162}
1163
1164#[derive(Clone)]
1165pub struct EditorSnapshot {
1166 pub mode: EditorMode,
1167 show_gutter: bool,
1168 show_line_numbers: Option<bool>,
1169 show_git_diff_gutter: Option<bool>,
1170 show_code_actions: Option<bool>,
1171 show_runnables: Option<bool>,
1172 show_breakpoints: Option<bool>,
1173 git_blame_gutter_max_author_length: Option<usize>,
1174 pub display_snapshot: DisplaySnapshot,
1175 pub placeholder_text: Option<Arc<str>>,
1176 is_focused: bool,
1177 scroll_anchor: ScrollAnchor,
1178 ongoing_scroll: OngoingScroll,
1179 current_line_highlight: CurrentLineHighlight,
1180 gutter_hovered: bool,
1181}
1182
1183#[derive(Default, Debug, Clone, Copy)]
1184pub struct GutterDimensions {
1185 pub left_padding: Pixels,
1186 pub right_padding: Pixels,
1187 pub width: Pixels,
1188 pub margin: Pixels,
1189 pub git_blame_entries_width: Option<Pixels>,
1190}
1191
1192impl GutterDimensions {
1193 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1194 Self {
1195 margin: Self::default_gutter_margin(font_id, font_size, cx),
1196 ..Default::default()
1197 }
1198 }
1199
1200 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1201 -cx.text_system().descent(font_id, font_size)
1202 }
1203 /// The full width of the space taken up by the gutter.
1204 pub fn full_width(&self) -> Pixels {
1205 self.margin + self.width
1206 }
1207
1208 /// The width of the space reserved for the fold indicators,
1209 /// use alongside 'justify_end' and `gutter_width` to
1210 /// right align content with the line numbers
1211 pub fn fold_area_width(&self) -> Pixels {
1212 self.margin + self.right_padding
1213 }
1214}
1215
1216#[derive(Debug)]
1217pub struct RemoteSelection {
1218 pub replica_id: ReplicaId,
1219 pub selection: Selection<Anchor>,
1220 pub cursor_shape: CursorShape,
1221 pub collaborator_id: CollaboratorId,
1222 pub line_mode: bool,
1223 pub user_name: Option<SharedString>,
1224 pub color: PlayerColor,
1225}
1226
1227#[derive(Clone, Debug)]
1228struct SelectionHistoryEntry {
1229 selections: Arc<[Selection<Anchor>]>,
1230 select_next_state: Option<SelectNextState>,
1231 select_prev_state: Option<SelectNextState>,
1232 add_selections_state: Option<AddSelectionsState>,
1233}
1234
1235#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1236enum SelectionHistoryMode {
1237 Normal,
1238 Undoing,
1239 Redoing,
1240 Skipping,
1241}
1242
1243#[derive(Clone, PartialEq, Eq, Hash)]
1244struct HoveredCursor {
1245 replica_id: u16,
1246 selection_id: usize,
1247}
1248
1249impl Default for SelectionHistoryMode {
1250 fn default() -> Self {
1251 Self::Normal
1252 }
1253}
1254
1255#[derive(Debug)]
1256pub struct SelectionEffects {
1257 nav_history: bool,
1258 completions: bool,
1259 scroll: Option<Autoscroll>,
1260}
1261
1262impl Default for SelectionEffects {
1263 fn default() -> Self {
1264 Self {
1265 nav_history: true,
1266 completions: true,
1267 scroll: Some(Autoscroll::fit()),
1268 }
1269 }
1270}
1271impl SelectionEffects {
1272 pub fn scroll(scroll: Autoscroll) -> Self {
1273 Self {
1274 scroll: Some(scroll),
1275 ..Default::default()
1276 }
1277 }
1278
1279 pub fn no_scroll() -> Self {
1280 Self {
1281 scroll: None,
1282 ..Default::default()
1283 }
1284 }
1285
1286 pub fn completions(self, completions: bool) -> Self {
1287 Self {
1288 completions,
1289 ..self
1290 }
1291 }
1292
1293 pub fn nav_history(self, nav_history: bool) -> Self {
1294 Self {
1295 nav_history,
1296 ..self
1297 }
1298 }
1299}
1300
1301struct DeferredSelectionEffectsState {
1302 changed: bool,
1303 effects: SelectionEffects,
1304 old_cursor_position: Anchor,
1305 history_entry: SelectionHistoryEntry,
1306}
1307
1308#[derive(Default)]
1309struct SelectionHistory {
1310 #[allow(clippy::type_complexity)]
1311 selections_by_transaction:
1312 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1313 mode: SelectionHistoryMode,
1314 undo_stack: VecDeque<SelectionHistoryEntry>,
1315 redo_stack: VecDeque<SelectionHistoryEntry>,
1316}
1317
1318impl SelectionHistory {
1319 #[track_caller]
1320 fn insert_transaction(
1321 &mut self,
1322 transaction_id: TransactionId,
1323 selections: Arc<[Selection<Anchor>]>,
1324 ) {
1325 if selections.is_empty() {
1326 log::error!(
1327 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1328 std::panic::Location::caller()
1329 );
1330 return;
1331 }
1332 self.selections_by_transaction
1333 .insert(transaction_id, (selections, None));
1334 }
1335
1336 #[allow(clippy::type_complexity)]
1337 fn transaction(
1338 &self,
1339 transaction_id: TransactionId,
1340 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1341 self.selections_by_transaction.get(&transaction_id)
1342 }
1343
1344 #[allow(clippy::type_complexity)]
1345 fn transaction_mut(
1346 &mut self,
1347 transaction_id: TransactionId,
1348 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1349 self.selections_by_transaction.get_mut(&transaction_id)
1350 }
1351
1352 fn push(&mut self, entry: SelectionHistoryEntry) {
1353 if !entry.selections.is_empty() {
1354 match self.mode {
1355 SelectionHistoryMode::Normal => {
1356 self.push_undo(entry);
1357 self.redo_stack.clear();
1358 }
1359 SelectionHistoryMode::Undoing => self.push_redo(entry),
1360 SelectionHistoryMode::Redoing => self.push_undo(entry),
1361 SelectionHistoryMode::Skipping => {}
1362 }
1363 }
1364 }
1365
1366 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1367 if self
1368 .undo_stack
1369 .back()
1370 .map_or(true, |e| e.selections != entry.selections)
1371 {
1372 self.undo_stack.push_back(entry);
1373 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1374 self.undo_stack.pop_front();
1375 }
1376 }
1377 }
1378
1379 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1380 if self
1381 .redo_stack
1382 .back()
1383 .map_or(true, |e| e.selections != entry.selections)
1384 {
1385 self.redo_stack.push_back(entry);
1386 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1387 self.redo_stack.pop_front();
1388 }
1389 }
1390 }
1391}
1392
1393#[derive(Clone, Copy)]
1394pub struct RowHighlightOptions {
1395 pub autoscroll: bool,
1396 pub include_gutter: bool,
1397}
1398
1399impl Default for RowHighlightOptions {
1400 fn default() -> Self {
1401 Self {
1402 autoscroll: Default::default(),
1403 include_gutter: true,
1404 }
1405 }
1406}
1407
1408struct RowHighlight {
1409 index: usize,
1410 range: Range<Anchor>,
1411 color: Hsla,
1412 options: RowHighlightOptions,
1413 type_id: TypeId,
1414}
1415
1416#[derive(Clone, Debug)]
1417struct AddSelectionsState {
1418 groups: Vec<AddSelectionsGroup>,
1419}
1420
1421#[derive(Clone, Debug)]
1422struct AddSelectionsGroup {
1423 above: bool,
1424 stack: Vec<usize>,
1425}
1426
1427#[derive(Clone)]
1428struct SelectNextState {
1429 query: AhoCorasick,
1430 wordwise: bool,
1431 done: bool,
1432}
1433
1434impl std::fmt::Debug for SelectNextState {
1435 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1436 f.debug_struct(std::any::type_name::<Self>())
1437 .field("wordwise", &self.wordwise)
1438 .field("done", &self.done)
1439 .finish()
1440 }
1441}
1442
1443#[derive(Debug)]
1444struct AutocloseRegion {
1445 selection_id: usize,
1446 range: Range<Anchor>,
1447 pair: BracketPair,
1448}
1449
1450#[derive(Debug)]
1451struct SnippetState {
1452 ranges: Vec<Vec<Range<Anchor>>>,
1453 active_index: usize,
1454 choices: Vec<Option<Vec<String>>>,
1455}
1456
1457#[doc(hidden)]
1458pub struct RenameState {
1459 pub range: Range<Anchor>,
1460 pub old_name: Arc<str>,
1461 pub editor: Entity<Editor>,
1462 block_id: CustomBlockId,
1463}
1464
1465struct InvalidationStack<T>(Vec<T>);
1466
1467struct RegisteredInlineCompletionProvider {
1468 provider: Arc<dyn InlineCompletionProviderHandle>,
1469 _subscription: Subscription,
1470}
1471
1472#[derive(Debug, PartialEq, Eq)]
1473pub struct ActiveDiagnosticGroup {
1474 pub active_range: Range<Anchor>,
1475 pub active_message: String,
1476 pub group_id: usize,
1477 pub blocks: HashSet<CustomBlockId>,
1478}
1479
1480#[derive(Debug, PartialEq, Eq)]
1481
1482pub(crate) enum ActiveDiagnostic {
1483 None,
1484 All,
1485 Group(ActiveDiagnosticGroup),
1486}
1487
1488#[derive(Serialize, Deserialize, Clone, Debug)]
1489pub struct ClipboardSelection {
1490 /// The number of bytes in this selection.
1491 pub len: usize,
1492 /// Whether this was a full-line selection.
1493 pub is_entire_line: bool,
1494 /// The indentation of the first line when this content was originally copied.
1495 pub first_line_indent: u32,
1496}
1497
1498// selections, scroll behavior, was newest selection reversed
1499type SelectSyntaxNodeHistoryState = (
1500 Box<[Selection<usize>]>,
1501 SelectSyntaxNodeScrollBehavior,
1502 bool,
1503);
1504
1505#[derive(Default)]
1506struct SelectSyntaxNodeHistory {
1507 stack: Vec<SelectSyntaxNodeHistoryState>,
1508 // disable temporarily to allow changing selections without losing the stack
1509 pub disable_clearing: bool,
1510}
1511
1512impl SelectSyntaxNodeHistory {
1513 pub fn try_clear(&mut self) {
1514 if !self.disable_clearing {
1515 self.stack.clear();
1516 }
1517 }
1518
1519 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1520 self.stack.push(selection);
1521 }
1522
1523 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1524 self.stack.pop()
1525 }
1526}
1527
1528enum SelectSyntaxNodeScrollBehavior {
1529 CursorTop,
1530 FitSelection,
1531 CursorBottom,
1532}
1533
1534#[derive(Debug)]
1535pub(crate) struct NavigationData {
1536 cursor_anchor: Anchor,
1537 cursor_position: Point,
1538 scroll_anchor: ScrollAnchor,
1539 scroll_top_row: u32,
1540}
1541
1542#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1543pub enum GotoDefinitionKind {
1544 Symbol,
1545 Declaration,
1546 Type,
1547 Implementation,
1548}
1549
1550#[derive(Debug, Clone)]
1551enum InlayHintRefreshReason {
1552 ModifiersChanged(bool),
1553 Toggle(bool),
1554 SettingsChange(InlayHintSettings),
1555 NewLinesShown,
1556 BufferEdited(HashSet<Arc<Language>>),
1557 RefreshRequested,
1558 ExcerptsRemoved(Vec<ExcerptId>),
1559}
1560
1561impl InlayHintRefreshReason {
1562 fn description(&self) -> &'static str {
1563 match self {
1564 Self::ModifiersChanged(_) => "modifiers changed",
1565 Self::Toggle(_) => "toggle",
1566 Self::SettingsChange(_) => "settings change",
1567 Self::NewLinesShown => "new lines shown",
1568 Self::BufferEdited(_) => "buffer edited",
1569 Self::RefreshRequested => "refresh requested",
1570 Self::ExcerptsRemoved(_) => "excerpts removed",
1571 }
1572 }
1573}
1574
1575pub enum FormatTarget {
1576 Buffers(HashSet<Entity<Buffer>>),
1577 Ranges(Vec<Range<MultiBufferPoint>>),
1578}
1579
1580pub(crate) struct FocusedBlock {
1581 id: BlockId,
1582 focus_handle: WeakFocusHandle,
1583}
1584
1585#[derive(Clone)]
1586enum JumpData {
1587 MultiBufferRow {
1588 row: MultiBufferRow,
1589 line_offset_from_top: u32,
1590 },
1591 MultiBufferPoint {
1592 excerpt_id: ExcerptId,
1593 position: Point,
1594 anchor: text::Anchor,
1595 line_offset_from_top: u32,
1596 },
1597}
1598
1599pub enum MultibufferSelectionMode {
1600 First,
1601 All,
1602}
1603
1604#[derive(Clone, Copy, Debug, Default)]
1605pub struct RewrapOptions {
1606 pub override_language_settings: bool,
1607 pub preserve_existing_whitespace: bool,
1608}
1609
1610impl Editor {
1611 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1612 let buffer = cx.new(|cx| Buffer::local("", cx));
1613 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1614 Self::new(
1615 EditorMode::SingleLine { auto_width: false },
1616 buffer,
1617 None,
1618 window,
1619 cx,
1620 )
1621 }
1622
1623 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1624 let buffer = cx.new(|cx| Buffer::local("", cx));
1625 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1626 Self::new(EditorMode::full(), buffer, None, window, cx)
1627 }
1628
1629 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1630 let buffer = cx.new(|cx| Buffer::local("", cx));
1631 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1632 Self::new(
1633 EditorMode::SingleLine { auto_width: true },
1634 buffer,
1635 None,
1636 window,
1637 cx,
1638 )
1639 }
1640
1641 pub fn auto_height(
1642 min_lines: usize,
1643 max_lines: usize,
1644 window: &mut Window,
1645 cx: &mut Context<Self>,
1646 ) -> Self {
1647 let buffer = cx.new(|cx| Buffer::local("", cx));
1648 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1649 Self::new(
1650 EditorMode::AutoHeight {
1651 min_lines,
1652 max_lines,
1653 },
1654 buffer,
1655 None,
1656 window,
1657 cx,
1658 )
1659 }
1660
1661 pub fn for_buffer(
1662 buffer: Entity<Buffer>,
1663 project: Option<Entity<Project>>,
1664 window: &mut Window,
1665 cx: &mut Context<Self>,
1666 ) -> Self {
1667 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1668 Self::new(EditorMode::full(), buffer, project, window, cx)
1669 }
1670
1671 pub fn for_multibuffer(
1672 buffer: Entity<MultiBuffer>,
1673 project: Option<Entity<Project>>,
1674 window: &mut Window,
1675 cx: &mut Context<Self>,
1676 ) -> Self {
1677 Self::new(EditorMode::full(), buffer, project, window, cx)
1678 }
1679
1680 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1681 let mut clone = Self::new(
1682 self.mode.clone(),
1683 self.buffer.clone(),
1684 self.project.clone(),
1685 window,
1686 cx,
1687 );
1688 self.display_map.update(cx, |display_map, cx| {
1689 let snapshot = display_map.snapshot(cx);
1690 clone.display_map.update(cx, |display_map, cx| {
1691 display_map.set_state(&snapshot, cx);
1692 });
1693 });
1694 clone.folds_did_change(cx);
1695 clone.selections.clone_state(&self.selections);
1696 clone.scroll_manager.clone_state(&self.scroll_manager);
1697 clone.searchable = self.searchable;
1698 clone.read_only = self.read_only;
1699 clone
1700 }
1701
1702 pub fn new(
1703 mode: EditorMode,
1704 buffer: Entity<MultiBuffer>,
1705 project: Option<Entity<Project>>,
1706 window: &mut Window,
1707 cx: &mut Context<Self>,
1708 ) -> Self {
1709 Editor::new_internal(mode, buffer, project, None, window, cx)
1710 }
1711
1712 fn new_internal(
1713 mode: EditorMode,
1714 buffer: Entity<MultiBuffer>,
1715 project: Option<Entity<Project>>,
1716 display_map: Option<Entity<DisplayMap>>,
1717 window: &mut Window,
1718 cx: &mut Context<Self>,
1719 ) -> Self {
1720 debug_assert!(
1721 display_map.is_none() || mode.is_minimap(),
1722 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1723 );
1724
1725 let full_mode = mode.is_full();
1726 let diagnostics_max_severity = if full_mode {
1727 EditorSettings::get_global(cx)
1728 .diagnostics_max_severity
1729 .unwrap_or(DiagnosticSeverity::Hint)
1730 } else {
1731 DiagnosticSeverity::Off
1732 };
1733 let style = window.text_style();
1734 let font_size = style.font_size.to_pixels(window.rem_size());
1735 let editor = cx.entity().downgrade();
1736 let fold_placeholder = FoldPlaceholder {
1737 constrain_width: true,
1738 render: Arc::new(move |fold_id, fold_range, cx| {
1739 let editor = editor.clone();
1740 div()
1741 .id(fold_id)
1742 .bg(cx.theme().colors().ghost_element_background)
1743 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1744 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1745 .rounded_xs()
1746 .size_full()
1747 .cursor_pointer()
1748 .child("⋯")
1749 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1750 .on_click(move |_, _window, cx| {
1751 editor
1752 .update(cx, |editor, cx| {
1753 editor.unfold_ranges(
1754 &[fold_range.start..fold_range.end],
1755 true,
1756 false,
1757 cx,
1758 );
1759 cx.stop_propagation();
1760 })
1761 .ok();
1762 })
1763 .into_any()
1764 }),
1765 merge_adjacent: true,
1766 ..FoldPlaceholder::default()
1767 };
1768 let display_map = display_map.unwrap_or_else(|| {
1769 cx.new(|cx| {
1770 DisplayMap::new(
1771 buffer.clone(),
1772 style.font(),
1773 font_size,
1774 None,
1775 FILE_HEADER_HEIGHT,
1776 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1777 fold_placeholder,
1778 diagnostics_max_severity,
1779 cx,
1780 )
1781 })
1782 });
1783
1784 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1785
1786 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1787
1788 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1789 .then(|| language_settings::SoftWrap::None);
1790
1791 let mut project_subscriptions = Vec::new();
1792 if mode.is_full() {
1793 if let Some(project) = project.as_ref() {
1794 project_subscriptions.push(cx.subscribe_in(
1795 project,
1796 window,
1797 |editor, _, event, window, cx| match event {
1798 project::Event::RefreshCodeLens => {
1799 // we always query lens with actions, without storing them, always refreshing them
1800 }
1801 project::Event::RefreshInlayHints => {
1802 editor
1803 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1804 }
1805 project::Event::LanguageServerAdded(server_id, ..)
1806 | project::Event::LanguageServerRemoved(server_id) => {
1807 if editor.tasks_update_task.is_none() {
1808 editor.tasks_update_task =
1809 Some(editor.refresh_runnables(window, cx));
1810 }
1811 editor.update_lsp_data(false, Some(*server_id), None, window, cx);
1812 }
1813 project::Event::SnippetEdit(id, snippet_edits) => {
1814 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1815 let focus_handle = editor.focus_handle(cx);
1816 if focus_handle.is_focused(window) {
1817 let snapshot = buffer.read(cx).snapshot();
1818 for (range, snippet) in snippet_edits {
1819 let editor_range =
1820 language::range_from_lsp(*range).to_offset(&snapshot);
1821 editor
1822 .insert_snippet(
1823 &[editor_range],
1824 snippet.clone(),
1825 window,
1826 cx,
1827 )
1828 .ok();
1829 }
1830 }
1831 }
1832 }
1833 _ => {}
1834 },
1835 ));
1836 if let Some(task_inventory) = project
1837 .read(cx)
1838 .task_store()
1839 .read(cx)
1840 .task_inventory()
1841 .cloned()
1842 {
1843 project_subscriptions.push(cx.observe_in(
1844 &task_inventory,
1845 window,
1846 |editor, _, window, cx| {
1847 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1848 },
1849 ));
1850 };
1851
1852 project_subscriptions.push(cx.subscribe_in(
1853 &project.read(cx).breakpoint_store(),
1854 window,
1855 |editor, _, event, window, cx| match event {
1856 BreakpointStoreEvent::ClearDebugLines => {
1857 editor.clear_row_highlights::<ActiveDebugLine>();
1858 editor.refresh_inline_values(cx);
1859 }
1860 BreakpointStoreEvent::SetDebugLine => {
1861 if editor.go_to_active_debug_line(window, cx) {
1862 cx.stop_propagation();
1863 }
1864
1865 editor.refresh_inline_values(cx);
1866 }
1867 _ => {}
1868 },
1869 ));
1870 let git_store = project.read(cx).git_store().clone();
1871 let project = project.clone();
1872 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1873 match event {
1874 GitStoreEvent::RepositoryUpdated(
1875 _,
1876 RepositoryEvent::Updated {
1877 new_instance: true, ..
1878 },
1879 _,
1880 ) => {
1881 this.load_diff_task = Some(
1882 update_uncommitted_diff_for_buffer(
1883 cx.entity(),
1884 &project,
1885 this.buffer.read(cx).all_buffers(),
1886 this.buffer.clone(),
1887 cx,
1888 )
1889 .shared(),
1890 );
1891 }
1892 _ => {}
1893 }
1894 }));
1895 }
1896 }
1897
1898 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1899
1900 let inlay_hint_settings =
1901 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1902 let focus_handle = cx.focus_handle();
1903 cx.on_focus(&focus_handle, window, Self::handle_focus)
1904 .detach();
1905 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1906 .detach();
1907 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1908 .detach();
1909 cx.on_blur(&focus_handle, window, Self::handle_blur)
1910 .detach();
1911 cx.observe_pending_input(window, Self::observe_pending_input)
1912 .detach();
1913
1914 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1915 Some(false)
1916 } else {
1917 None
1918 };
1919
1920 let breakpoint_store = match (&mode, project.as_ref()) {
1921 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1922 _ => None,
1923 };
1924
1925 let mut code_action_providers = Vec::new();
1926 let mut load_uncommitted_diff = None;
1927 if let Some(project) = project.clone() {
1928 load_uncommitted_diff = Some(
1929 update_uncommitted_diff_for_buffer(
1930 cx.entity(),
1931 &project,
1932 buffer.read(cx).all_buffers(),
1933 buffer.clone(),
1934 cx,
1935 )
1936 .shared(),
1937 );
1938 code_action_providers.push(Rc::new(project) as Rc<_>);
1939 }
1940
1941 let mut editor = Self {
1942 focus_handle,
1943 show_cursor_when_unfocused: false,
1944 last_focused_descendant: None,
1945 buffer: buffer.clone(),
1946 display_map: display_map.clone(),
1947 selections,
1948 scroll_manager: ScrollManager::new(cx),
1949 columnar_selection_state: None,
1950 add_selections_state: None,
1951 select_next_state: None,
1952 select_prev_state: None,
1953 selection_history: SelectionHistory::default(),
1954 defer_selection_effects: false,
1955 deferred_selection_effects_state: None,
1956 autoclose_regions: Vec::new(),
1957 snippet_stack: InvalidationStack::default(),
1958 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1959 ime_transaction: None,
1960 active_diagnostics: ActiveDiagnostic::None,
1961 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1962 inline_diagnostics_update: Task::ready(()),
1963 inline_diagnostics: Vec::new(),
1964 soft_wrap_mode_override,
1965 diagnostics_max_severity,
1966 hard_wrap: None,
1967 completion_provider: project.clone().map(|project| Rc::new(project) as _),
1968 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1969 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1970 project,
1971 blink_manager: blink_manager.clone(),
1972 show_local_selections: true,
1973 show_scrollbars: ScrollbarAxes {
1974 horizontal: full_mode,
1975 vertical: full_mode,
1976 },
1977 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1978 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
1979 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1980 show_gutter: mode.is_full(),
1981 show_line_numbers: None,
1982 use_relative_line_numbers: None,
1983 disable_expand_excerpt_buttons: false,
1984 show_git_diff_gutter: None,
1985 show_code_actions: None,
1986 show_runnables: None,
1987 show_breakpoints: None,
1988 show_wrap_guides: None,
1989 show_indent_guides,
1990 placeholder_text: None,
1991 highlight_order: 0,
1992 highlighted_rows: HashMap::default(),
1993 background_highlights: TreeMap::default(),
1994 gutter_highlights: TreeMap::default(),
1995 scrollbar_marker_state: ScrollbarMarkerState::default(),
1996 active_indent_guides_state: ActiveIndentGuidesState::default(),
1997 nav_history: None,
1998 context_menu: RefCell::new(None),
1999 context_menu_options: None,
2000 mouse_context_menu: None,
2001 completion_tasks: Vec::new(),
2002 inline_blame_popover: None,
2003 inline_blame_popover_show_task: None,
2004 signature_help_state: SignatureHelpState::default(),
2005 auto_signature_help: None,
2006 find_all_references_task_sources: Vec::new(),
2007 next_completion_id: 0,
2008 next_inlay_id: 0,
2009 code_action_providers,
2010 available_code_actions: None,
2011 code_actions_task: None,
2012 quick_selection_highlight_task: None,
2013 debounced_selection_highlight_task: None,
2014 document_highlights_task: None,
2015 linked_editing_range_task: None,
2016 pending_rename: None,
2017 searchable: true,
2018 cursor_shape: EditorSettings::get_global(cx)
2019 .cursor_shape
2020 .unwrap_or_default(),
2021 current_line_highlight: None,
2022 autoindent_mode: Some(AutoindentMode::EachLine),
2023 collapse_matches: false,
2024 workspace: None,
2025 input_enabled: true,
2026 use_modal_editing: mode.is_full(),
2027 read_only: mode.is_minimap(),
2028 use_autoclose: true,
2029 use_auto_surround: true,
2030 auto_replace_emoji_shortcode: false,
2031 jsx_tag_auto_close_enabled_in_any_buffer: false,
2032 leader_id: None,
2033 remote_id: None,
2034 hover_state: HoverState::default(),
2035 pending_mouse_down: None,
2036 hovered_link_state: None,
2037 edit_prediction_provider: None,
2038 active_inline_completion: None,
2039 stale_inline_completion_in_menu: None,
2040 edit_prediction_preview: EditPredictionPreview::Inactive {
2041 released_too_fast: false,
2042 },
2043 inline_diagnostics_enabled: mode.is_full(),
2044 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2045 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2046
2047 gutter_hovered: false,
2048 pixel_position_of_newest_cursor: None,
2049 last_bounds: None,
2050 last_position_map: None,
2051 expect_bounds_change: None,
2052 gutter_dimensions: GutterDimensions::default(),
2053 style: None,
2054 show_cursor_names: false,
2055 hovered_cursors: HashMap::default(),
2056 next_editor_action_id: EditorActionId::default(),
2057 editor_actions: Rc::default(),
2058 inline_completions_hidden_for_vim_mode: false,
2059 show_inline_completions_override: None,
2060 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
2061 edit_prediction_settings: EditPredictionSettings::Disabled,
2062 edit_prediction_indent_conflict: false,
2063 edit_prediction_requires_modifier_in_indent_conflict: true,
2064 custom_context_menu: None,
2065 show_git_blame_gutter: false,
2066 show_git_blame_inline: false,
2067 show_selection_menu: None,
2068 show_git_blame_inline_delay_task: None,
2069 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2070 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2071 serialize_dirty_buffers: !mode.is_minimap()
2072 && ProjectSettings::get_global(cx)
2073 .session
2074 .restore_unsaved_buffers,
2075 blame: None,
2076 blame_subscription: None,
2077 tasks: BTreeMap::default(),
2078
2079 breakpoint_store,
2080 gutter_breakpoint_indicator: (None, None),
2081 hovered_diff_hunk_row: None,
2082 _subscriptions: vec![
2083 cx.observe(&buffer, Self::on_buffer_changed),
2084 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2085 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2086 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2087 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2088 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2089 cx.observe_window_activation(window, |editor, window, cx| {
2090 let active = window.is_window_active();
2091 editor.blink_manager.update(cx, |blink_manager, cx| {
2092 if active {
2093 blink_manager.enable(cx);
2094 } else {
2095 blink_manager.disable(cx);
2096 }
2097 });
2098 if active {
2099 editor.show_mouse_cursor(cx);
2100 }
2101 }),
2102 ],
2103 tasks_update_task: None,
2104 pull_diagnostics_task: Task::ready(()),
2105 colors: None,
2106 next_color_inlay_id: 0,
2107 linked_edit_ranges: Default::default(),
2108 in_project_search: false,
2109 previous_search_ranges: None,
2110 breadcrumb_header: None,
2111 focused_block: None,
2112 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2113 addons: HashMap::default(),
2114 registered_buffers: HashMap::default(),
2115 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2116 selection_mark_mode: false,
2117 toggle_fold_multiple_buffers: Task::ready(()),
2118 serialize_selections: Task::ready(()),
2119 serialize_folds: Task::ready(()),
2120 text_style_refinement: None,
2121 load_diff_task: load_uncommitted_diff,
2122 temporary_diff_override: false,
2123 mouse_cursor_hidden: false,
2124 minimap: None,
2125 hide_mouse_mode: EditorSettings::get_global(cx)
2126 .hide_mouse
2127 .unwrap_or_default(),
2128 change_list: ChangeList::new(),
2129 mode,
2130 selection_drag_state: SelectionDragState::None,
2131 drag_and_drop_selection_enabled: EditorSettings::get_global(cx).drag_and_drop_selection,
2132 };
2133 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2134 editor
2135 ._subscriptions
2136 .push(cx.observe(breakpoints, |_, _, cx| {
2137 cx.notify();
2138 }));
2139 }
2140 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2141 editor._subscriptions.extend(project_subscriptions);
2142
2143 editor._subscriptions.push(cx.subscribe_in(
2144 &cx.entity(),
2145 window,
2146 |editor, _, e: &EditorEvent, window, cx| match e {
2147 EditorEvent::ScrollPositionChanged { local, .. } => {
2148 if *local {
2149 let new_anchor = editor.scroll_manager.anchor();
2150 let snapshot = editor.snapshot(window, cx);
2151 editor.update_restoration_data(cx, move |data| {
2152 data.scroll_position = (
2153 new_anchor.top_row(&snapshot.buffer_snapshot),
2154 new_anchor.offset,
2155 );
2156 });
2157 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2158 editor.inline_blame_popover.take();
2159 }
2160 }
2161 EditorEvent::Edited { .. } => {
2162 if !vim_enabled(cx) {
2163 let (map, selections) = editor.selections.all_adjusted_display(cx);
2164 let pop_state = editor
2165 .change_list
2166 .last()
2167 .map(|previous| {
2168 previous.len() == selections.len()
2169 && previous.iter().enumerate().all(|(ix, p)| {
2170 p.to_display_point(&map).row()
2171 == selections[ix].head().row()
2172 })
2173 })
2174 .unwrap_or(false);
2175 let new_positions = selections
2176 .into_iter()
2177 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2178 .collect();
2179 editor
2180 .change_list
2181 .push_to_change_list(pop_state, new_positions);
2182 }
2183 }
2184 _ => (),
2185 },
2186 ));
2187
2188 if let Some(dap_store) = editor
2189 .project
2190 .as_ref()
2191 .map(|project| project.read(cx).dap_store())
2192 {
2193 let weak_editor = cx.weak_entity();
2194
2195 editor
2196 ._subscriptions
2197 .push(
2198 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2199 let session_entity = cx.entity();
2200 weak_editor
2201 .update(cx, |editor, cx| {
2202 editor._subscriptions.push(
2203 cx.subscribe(&session_entity, Self::on_debug_session_event),
2204 );
2205 })
2206 .ok();
2207 }),
2208 );
2209
2210 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2211 editor
2212 ._subscriptions
2213 .push(cx.subscribe(&session, Self::on_debug_session_event));
2214 }
2215 }
2216
2217 // skip adding the initial selection to selection history
2218 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2219 editor.end_selection(window, cx);
2220 editor.selection_history.mode = SelectionHistoryMode::Normal;
2221
2222 editor.scroll_manager.show_scrollbars(window, cx);
2223 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2224
2225 if full_mode {
2226 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2227 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2228
2229 if editor.git_blame_inline_enabled {
2230 editor.start_git_blame_inline(false, window, cx);
2231 }
2232
2233 editor.go_to_active_debug_line(window, cx);
2234
2235 if let Some(buffer) = buffer.read(cx).as_singleton() {
2236 if let Some(project) = editor.project.as_ref() {
2237 let handle = project.update(cx, |project, cx| {
2238 project.register_buffer_with_language_servers(&buffer, cx)
2239 });
2240 editor
2241 .registered_buffers
2242 .insert(buffer.read(cx).remote_id(), handle);
2243 }
2244 }
2245
2246 editor.minimap =
2247 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2248 editor.colors = Some(LspColorData::new(cx));
2249 editor.update_lsp_data(false, None, None, window, cx);
2250 }
2251
2252 editor.report_editor_event("Editor Opened", None, cx);
2253 editor
2254 }
2255
2256 pub fn deploy_mouse_context_menu(
2257 &mut self,
2258 position: gpui::Point<Pixels>,
2259 context_menu: Entity<ContextMenu>,
2260 window: &mut Window,
2261 cx: &mut Context<Self>,
2262 ) {
2263 self.mouse_context_menu = Some(MouseContextMenu::new(
2264 self,
2265 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2266 context_menu,
2267 window,
2268 cx,
2269 ));
2270 }
2271
2272 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2273 self.mouse_context_menu
2274 .as_ref()
2275 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2276 }
2277
2278 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2279 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2280 }
2281
2282 fn key_context_internal(
2283 &self,
2284 has_active_edit_prediction: bool,
2285 window: &Window,
2286 cx: &App,
2287 ) -> KeyContext {
2288 let mut key_context = KeyContext::new_with_defaults();
2289 key_context.add("Editor");
2290 let mode = match self.mode {
2291 EditorMode::SingleLine { .. } => "single_line",
2292 EditorMode::AutoHeight { .. } => "auto_height",
2293 EditorMode::Minimap { .. } => "minimap",
2294 EditorMode::Full { .. } => "full",
2295 };
2296
2297 if EditorSettings::jupyter_enabled(cx) {
2298 key_context.add("jupyter");
2299 }
2300
2301 key_context.set("mode", mode);
2302 if self.pending_rename.is_some() {
2303 key_context.add("renaming");
2304 }
2305
2306 match self.context_menu.borrow().as_ref() {
2307 Some(CodeContextMenu::Completions(_)) => {
2308 key_context.add("menu");
2309 key_context.add("showing_completions");
2310 }
2311 Some(CodeContextMenu::CodeActions(_)) => {
2312 key_context.add("menu");
2313 key_context.add("showing_code_actions")
2314 }
2315 None => {}
2316 }
2317
2318 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2319 if !self.focus_handle(cx).contains_focused(window, cx)
2320 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2321 {
2322 for addon in self.addons.values() {
2323 addon.extend_key_context(&mut key_context, cx)
2324 }
2325 }
2326
2327 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2328 if let Some(extension) = singleton_buffer
2329 .read(cx)
2330 .file()
2331 .and_then(|file| file.path().extension()?.to_str())
2332 {
2333 key_context.set("extension", extension.to_string());
2334 }
2335 } else {
2336 key_context.add("multibuffer");
2337 }
2338
2339 if has_active_edit_prediction {
2340 if self.edit_prediction_in_conflict() {
2341 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2342 } else {
2343 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2344 key_context.add("copilot_suggestion");
2345 }
2346 }
2347
2348 if self.selection_mark_mode {
2349 key_context.add("selection_mode");
2350 }
2351
2352 key_context
2353 }
2354
2355 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2356 if self.mouse_cursor_hidden {
2357 self.mouse_cursor_hidden = false;
2358 cx.notify();
2359 }
2360 }
2361
2362 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2363 let hide_mouse_cursor = match origin {
2364 HideMouseCursorOrigin::TypingAction => {
2365 matches!(
2366 self.hide_mouse_mode,
2367 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2368 )
2369 }
2370 HideMouseCursorOrigin::MovementAction => {
2371 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2372 }
2373 };
2374 if self.mouse_cursor_hidden != hide_mouse_cursor {
2375 self.mouse_cursor_hidden = hide_mouse_cursor;
2376 cx.notify();
2377 }
2378 }
2379
2380 pub fn edit_prediction_in_conflict(&self) -> bool {
2381 if !self.show_edit_predictions_in_menu() {
2382 return false;
2383 }
2384
2385 let showing_completions = self
2386 .context_menu
2387 .borrow()
2388 .as_ref()
2389 .map_or(false, |context| {
2390 matches!(context, CodeContextMenu::Completions(_))
2391 });
2392
2393 showing_completions
2394 || self.edit_prediction_requires_modifier()
2395 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2396 // bindings to insert tab characters.
2397 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2398 }
2399
2400 pub fn accept_edit_prediction_keybind(
2401 &self,
2402 accept_partial: bool,
2403 window: &Window,
2404 cx: &App,
2405 ) -> AcceptEditPredictionBinding {
2406 let key_context = self.key_context_internal(true, window, cx);
2407 let in_conflict = self.edit_prediction_in_conflict();
2408
2409 let bindings = if accept_partial {
2410 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2411 } else {
2412 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2413 };
2414
2415 // TODO: if the binding contains multiple keystrokes, display all of them, not
2416 // just the first one.
2417 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2418 !in_conflict
2419 || binding
2420 .keystrokes()
2421 .first()
2422 .map_or(false, |keystroke| keystroke.modifiers.modified())
2423 }))
2424 }
2425
2426 pub fn new_file(
2427 workspace: &mut Workspace,
2428 _: &workspace::NewFile,
2429 window: &mut Window,
2430 cx: &mut Context<Workspace>,
2431 ) {
2432 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2433 "Failed to create buffer",
2434 window,
2435 cx,
2436 |e, _, _| match e.error_code() {
2437 ErrorCode::RemoteUpgradeRequired => Some(format!(
2438 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2439 e.error_tag("required").unwrap_or("the latest version")
2440 )),
2441 _ => None,
2442 },
2443 );
2444 }
2445
2446 pub fn new_in_workspace(
2447 workspace: &mut Workspace,
2448 window: &mut Window,
2449 cx: &mut Context<Workspace>,
2450 ) -> Task<Result<Entity<Editor>>> {
2451 let project = workspace.project().clone();
2452 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2453
2454 cx.spawn_in(window, async move |workspace, cx| {
2455 let buffer = create.await?;
2456 workspace.update_in(cx, |workspace, window, cx| {
2457 let editor =
2458 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2459 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2460 editor
2461 })
2462 })
2463 }
2464
2465 fn new_file_vertical(
2466 workspace: &mut Workspace,
2467 _: &workspace::NewFileSplitVertical,
2468 window: &mut Window,
2469 cx: &mut Context<Workspace>,
2470 ) {
2471 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2472 }
2473
2474 fn new_file_horizontal(
2475 workspace: &mut Workspace,
2476 _: &workspace::NewFileSplitHorizontal,
2477 window: &mut Window,
2478 cx: &mut Context<Workspace>,
2479 ) {
2480 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2481 }
2482
2483 fn new_file_in_direction(
2484 workspace: &mut Workspace,
2485 direction: SplitDirection,
2486 window: &mut Window,
2487 cx: &mut Context<Workspace>,
2488 ) {
2489 let project = workspace.project().clone();
2490 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2491
2492 cx.spawn_in(window, async move |workspace, cx| {
2493 let buffer = create.await?;
2494 workspace.update_in(cx, move |workspace, window, cx| {
2495 workspace.split_item(
2496 direction,
2497 Box::new(
2498 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2499 ),
2500 window,
2501 cx,
2502 )
2503 })?;
2504 anyhow::Ok(())
2505 })
2506 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2507 match e.error_code() {
2508 ErrorCode::RemoteUpgradeRequired => Some(format!(
2509 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2510 e.error_tag("required").unwrap_or("the latest version")
2511 )),
2512 _ => None,
2513 }
2514 });
2515 }
2516
2517 pub fn leader_id(&self) -> Option<CollaboratorId> {
2518 self.leader_id
2519 }
2520
2521 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2522 &self.buffer
2523 }
2524
2525 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2526 self.workspace.as_ref()?.0.upgrade()
2527 }
2528
2529 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2530 self.buffer().read(cx).title(cx)
2531 }
2532
2533 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2534 let git_blame_gutter_max_author_length = self
2535 .render_git_blame_gutter(cx)
2536 .then(|| {
2537 if let Some(blame) = self.blame.as_ref() {
2538 let max_author_length =
2539 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2540 Some(max_author_length)
2541 } else {
2542 None
2543 }
2544 })
2545 .flatten();
2546
2547 EditorSnapshot {
2548 mode: self.mode.clone(),
2549 show_gutter: self.show_gutter,
2550 show_line_numbers: self.show_line_numbers,
2551 show_git_diff_gutter: self.show_git_diff_gutter,
2552 show_code_actions: self.show_code_actions,
2553 show_runnables: self.show_runnables,
2554 show_breakpoints: self.show_breakpoints,
2555 git_blame_gutter_max_author_length,
2556 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2557 scroll_anchor: self.scroll_manager.anchor(),
2558 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2559 placeholder_text: self.placeholder_text.clone(),
2560 is_focused: self.focus_handle.is_focused(window),
2561 current_line_highlight: self
2562 .current_line_highlight
2563 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2564 gutter_hovered: self.gutter_hovered,
2565 }
2566 }
2567
2568 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2569 self.buffer.read(cx).language_at(point, cx)
2570 }
2571
2572 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2573 self.buffer.read(cx).read(cx).file_at(point).cloned()
2574 }
2575
2576 pub fn active_excerpt(
2577 &self,
2578 cx: &App,
2579 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2580 self.buffer
2581 .read(cx)
2582 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2583 }
2584
2585 pub fn mode(&self) -> &EditorMode {
2586 &self.mode
2587 }
2588
2589 pub fn set_mode(&mut self, mode: EditorMode) {
2590 self.mode = mode;
2591 }
2592
2593 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2594 self.collaboration_hub.as_deref()
2595 }
2596
2597 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2598 self.collaboration_hub = Some(hub);
2599 }
2600
2601 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2602 self.in_project_search = in_project_search;
2603 }
2604
2605 pub fn set_custom_context_menu(
2606 &mut self,
2607 f: impl 'static
2608 + Fn(
2609 &mut Self,
2610 DisplayPoint,
2611 &mut Window,
2612 &mut Context<Self>,
2613 ) -> Option<Entity<ui::ContextMenu>>,
2614 ) {
2615 self.custom_context_menu = Some(Box::new(f))
2616 }
2617
2618 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2619 self.completion_provider = provider;
2620 }
2621
2622 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2623 self.semantics_provider.clone()
2624 }
2625
2626 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2627 self.semantics_provider = provider;
2628 }
2629
2630 pub fn set_edit_prediction_provider<T>(
2631 &mut self,
2632 provider: Option<Entity<T>>,
2633 window: &mut Window,
2634 cx: &mut Context<Self>,
2635 ) where
2636 T: EditPredictionProvider,
2637 {
2638 self.edit_prediction_provider =
2639 provider.map(|provider| RegisteredInlineCompletionProvider {
2640 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2641 if this.focus_handle.is_focused(window) {
2642 this.update_visible_inline_completion(window, cx);
2643 }
2644 }),
2645 provider: Arc::new(provider),
2646 });
2647 self.update_edit_prediction_settings(cx);
2648 self.refresh_inline_completion(false, false, window, cx);
2649 }
2650
2651 pub fn placeholder_text(&self) -> Option<&str> {
2652 self.placeholder_text.as_deref()
2653 }
2654
2655 pub fn set_placeholder_text(
2656 &mut self,
2657 placeholder_text: impl Into<Arc<str>>,
2658 cx: &mut Context<Self>,
2659 ) {
2660 let placeholder_text = Some(placeholder_text.into());
2661 if self.placeholder_text != placeholder_text {
2662 self.placeholder_text = placeholder_text;
2663 cx.notify();
2664 }
2665 }
2666
2667 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2668 self.cursor_shape = cursor_shape;
2669
2670 // Disrupt blink for immediate user feedback that the cursor shape has changed
2671 self.blink_manager.update(cx, BlinkManager::show_cursor);
2672
2673 cx.notify();
2674 }
2675
2676 pub fn set_current_line_highlight(
2677 &mut self,
2678 current_line_highlight: Option<CurrentLineHighlight>,
2679 ) {
2680 self.current_line_highlight = current_line_highlight;
2681 }
2682
2683 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2684 self.collapse_matches = collapse_matches;
2685 }
2686
2687 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2688 let buffers = self.buffer.read(cx).all_buffers();
2689 let Some(project) = self.project.as_ref() else {
2690 return;
2691 };
2692 project.update(cx, |project, cx| {
2693 for buffer in buffers {
2694 self.registered_buffers
2695 .entry(buffer.read(cx).remote_id())
2696 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2697 }
2698 })
2699 }
2700
2701 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2702 if self.collapse_matches {
2703 return range.start..range.start;
2704 }
2705 range.clone()
2706 }
2707
2708 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2709 if self.display_map.read(cx).clip_at_line_ends != clip {
2710 self.display_map
2711 .update(cx, |map, _| map.clip_at_line_ends = clip);
2712 }
2713 }
2714
2715 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2716 self.input_enabled = input_enabled;
2717 }
2718
2719 pub fn set_inline_completions_hidden_for_vim_mode(
2720 &mut self,
2721 hidden: bool,
2722 window: &mut Window,
2723 cx: &mut Context<Self>,
2724 ) {
2725 if hidden != self.inline_completions_hidden_for_vim_mode {
2726 self.inline_completions_hidden_for_vim_mode = hidden;
2727 if hidden {
2728 self.update_visible_inline_completion(window, cx);
2729 } else {
2730 self.refresh_inline_completion(true, false, window, cx);
2731 }
2732 }
2733 }
2734
2735 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2736 self.menu_inline_completions_policy = value;
2737 }
2738
2739 pub fn set_autoindent(&mut self, autoindent: bool) {
2740 if autoindent {
2741 self.autoindent_mode = Some(AutoindentMode::EachLine);
2742 } else {
2743 self.autoindent_mode = None;
2744 }
2745 }
2746
2747 pub fn read_only(&self, cx: &App) -> bool {
2748 self.read_only || self.buffer.read(cx).read_only()
2749 }
2750
2751 pub fn set_read_only(&mut self, read_only: bool) {
2752 self.read_only = read_only;
2753 }
2754
2755 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2756 self.use_autoclose = autoclose;
2757 }
2758
2759 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2760 self.use_auto_surround = auto_surround;
2761 }
2762
2763 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2764 self.auto_replace_emoji_shortcode = auto_replace;
2765 }
2766
2767 pub fn toggle_edit_predictions(
2768 &mut self,
2769 _: &ToggleEditPrediction,
2770 window: &mut Window,
2771 cx: &mut Context<Self>,
2772 ) {
2773 if self.show_inline_completions_override.is_some() {
2774 self.set_show_edit_predictions(None, window, cx);
2775 } else {
2776 let show_edit_predictions = !self.edit_predictions_enabled();
2777 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2778 }
2779 }
2780
2781 pub fn set_show_edit_predictions(
2782 &mut self,
2783 show_edit_predictions: Option<bool>,
2784 window: &mut Window,
2785 cx: &mut Context<Self>,
2786 ) {
2787 self.show_inline_completions_override = show_edit_predictions;
2788 self.update_edit_prediction_settings(cx);
2789
2790 if let Some(false) = show_edit_predictions {
2791 self.discard_inline_completion(false, cx);
2792 } else {
2793 self.refresh_inline_completion(false, true, window, cx);
2794 }
2795 }
2796
2797 fn inline_completions_disabled_in_scope(
2798 &self,
2799 buffer: &Entity<Buffer>,
2800 buffer_position: language::Anchor,
2801 cx: &App,
2802 ) -> bool {
2803 let snapshot = buffer.read(cx).snapshot();
2804 let settings = snapshot.settings_at(buffer_position, cx);
2805
2806 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2807 return false;
2808 };
2809
2810 scope.override_name().map_or(false, |scope_name| {
2811 settings
2812 .edit_predictions_disabled_in
2813 .iter()
2814 .any(|s| s == scope_name)
2815 })
2816 }
2817
2818 pub fn set_use_modal_editing(&mut self, to: bool) {
2819 self.use_modal_editing = to;
2820 }
2821
2822 pub fn use_modal_editing(&self) -> bool {
2823 self.use_modal_editing
2824 }
2825
2826 fn selections_did_change(
2827 &mut self,
2828 local: bool,
2829 old_cursor_position: &Anchor,
2830 effects: SelectionEffects,
2831 window: &mut Window,
2832 cx: &mut Context<Self>,
2833 ) {
2834 window.invalidate_character_coordinates();
2835
2836 // Copy selections to primary selection buffer
2837 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2838 if local {
2839 let selections = self.selections.all::<usize>(cx);
2840 let buffer_handle = self.buffer.read(cx).read(cx);
2841
2842 let mut text = String::new();
2843 for (index, selection) in selections.iter().enumerate() {
2844 let text_for_selection = buffer_handle
2845 .text_for_range(selection.start..selection.end)
2846 .collect::<String>();
2847
2848 text.push_str(&text_for_selection);
2849 if index != selections.len() - 1 {
2850 text.push('\n');
2851 }
2852 }
2853
2854 if !text.is_empty() {
2855 cx.write_to_primary(ClipboardItem::new_string(text));
2856 }
2857 }
2858
2859 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2860 self.buffer.update(cx, |buffer, cx| {
2861 buffer.set_active_selections(
2862 &self.selections.disjoint_anchors(),
2863 self.selections.line_mode,
2864 self.cursor_shape,
2865 cx,
2866 )
2867 });
2868 }
2869 let display_map = self
2870 .display_map
2871 .update(cx, |display_map, cx| display_map.snapshot(cx));
2872 let buffer = &display_map.buffer_snapshot;
2873 if self.selections.count() == 1 {
2874 self.add_selections_state = None;
2875 }
2876 self.select_next_state = None;
2877 self.select_prev_state = None;
2878 self.select_syntax_node_history.try_clear();
2879 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2880 self.snippet_stack
2881 .invalidate(&self.selections.disjoint_anchors(), buffer);
2882 self.take_rename(false, window, cx);
2883
2884 let newest_selection = self.selections.newest_anchor();
2885 let new_cursor_position = newest_selection.head();
2886 let selection_start = newest_selection.start;
2887
2888 if effects.nav_history {
2889 self.push_to_nav_history(
2890 *old_cursor_position,
2891 Some(new_cursor_position.to_point(buffer)),
2892 false,
2893 cx,
2894 );
2895 }
2896
2897 if local {
2898 if let Some(buffer_id) = new_cursor_position.buffer_id {
2899 if !self.registered_buffers.contains_key(&buffer_id) {
2900 if let Some(project) = self.project.as_ref() {
2901 project.update(cx, |project, cx| {
2902 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2903 return;
2904 };
2905 self.registered_buffers.insert(
2906 buffer_id,
2907 project.register_buffer_with_language_servers(&buffer, cx),
2908 );
2909 })
2910 }
2911 }
2912 }
2913
2914 let mut context_menu = self.context_menu.borrow_mut();
2915 let completion_menu = match context_menu.as_ref() {
2916 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2917 Some(CodeContextMenu::CodeActions(_)) => {
2918 *context_menu = None;
2919 None
2920 }
2921 None => None,
2922 };
2923 let completion_position = completion_menu.map(|menu| menu.initial_position);
2924 drop(context_menu);
2925
2926 if effects.completions {
2927 if let Some(completion_position) = completion_position {
2928 let start_offset = selection_start.to_offset(buffer);
2929 let position_matches = start_offset == completion_position.to_offset(buffer);
2930 let continue_showing = if position_matches {
2931 if self.snippet_stack.is_empty() {
2932 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
2933 } else {
2934 // Snippet choices can be shown even when the cursor is in whitespace.
2935 // Dismissing the menu with actions like backspace is handled by
2936 // invalidation regions.
2937 true
2938 }
2939 } else {
2940 false
2941 };
2942
2943 if continue_showing {
2944 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2945 } else {
2946 self.hide_context_menu(window, cx);
2947 }
2948 }
2949 }
2950
2951 hide_hover(self, cx);
2952
2953 if old_cursor_position.to_display_point(&display_map).row()
2954 != new_cursor_position.to_display_point(&display_map).row()
2955 {
2956 self.available_code_actions.take();
2957 }
2958 self.refresh_code_actions(window, cx);
2959 self.refresh_document_highlights(cx);
2960 self.refresh_selected_text_highlights(false, window, cx);
2961 refresh_matching_bracket_highlights(self, window, cx);
2962 self.update_visible_inline_completion(window, cx);
2963 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2964 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2965 self.inline_blame_popover.take();
2966 if self.git_blame_inline_enabled {
2967 self.start_inline_blame_timer(window, cx);
2968 }
2969 }
2970
2971 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2972 cx.emit(EditorEvent::SelectionsChanged { local });
2973
2974 let selections = &self.selections.disjoint;
2975 if selections.len() == 1 {
2976 cx.emit(SearchEvent::ActiveMatchChanged)
2977 }
2978 if local {
2979 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2980 let inmemory_selections = selections
2981 .iter()
2982 .map(|s| {
2983 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2984 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2985 })
2986 .collect();
2987 self.update_restoration_data(cx, |data| {
2988 data.selections = inmemory_selections;
2989 });
2990
2991 if WorkspaceSettings::get(None, cx).restore_on_startup
2992 != RestoreOnStartupBehavior::None
2993 {
2994 if let Some(workspace_id) =
2995 self.workspace.as_ref().and_then(|workspace| workspace.1)
2996 {
2997 let snapshot = self.buffer().read(cx).snapshot(cx);
2998 let selections = selections.clone();
2999 let background_executor = cx.background_executor().clone();
3000 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3001 self.serialize_selections = cx.background_spawn(async move {
3002 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3003 let db_selections = selections
3004 .iter()
3005 .map(|selection| {
3006 (
3007 selection.start.to_offset(&snapshot),
3008 selection.end.to_offset(&snapshot),
3009 )
3010 })
3011 .collect();
3012
3013 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3014 .await
3015 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3016 .log_err();
3017 });
3018 }
3019 }
3020 }
3021 }
3022
3023 cx.notify();
3024 }
3025
3026 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3027 use text::ToOffset as _;
3028 use text::ToPoint as _;
3029
3030 if self.mode.is_minimap()
3031 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3032 {
3033 return;
3034 }
3035
3036 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3037 return;
3038 };
3039
3040 let snapshot = singleton.read(cx).snapshot();
3041 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3042 let display_snapshot = display_map.snapshot(cx);
3043
3044 display_snapshot
3045 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3046 .map(|fold| {
3047 fold.range.start.text_anchor.to_point(&snapshot)
3048 ..fold.range.end.text_anchor.to_point(&snapshot)
3049 })
3050 .collect()
3051 });
3052 self.update_restoration_data(cx, |data| {
3053 data.folds = inmemory_folds;
3054 });
3055
3056 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3057 return;
3058 };
3059 let background_executor = cx.background_executor().clone();
3060 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3061 let db_folds = self.display_map.update(cx, |display_map, cx| {
3062 display_map
3063 .snapshot(cx)
3064 .folds_in_range(0..snapshot.len())
3065 .map(|fold| {
3066 (
3067 fold.range.start.text_anchor.to_offset(&snapshot),
3068 fold.range.end.text_anchor.to_offset(&snapshot),
3069 )
3070 })
3071 .collect()
3072 });
3073 self.serialize_folds = cx.background_spawn(async move {
3074 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3075 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3076 .await
3077 .with_context(|| {
3078 format!(
3079 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3080 )
3081 })
3082 .log_err();
3083 });
3084 }
3085
3086 pub fn sync_selections(
3087 &mut self,
3088 other: Entity<Editor>,
3089 cx: &mut Context<Self>,
3090 ) -> gpui::Subscription {
3091 let other_selections = other.read(cx).selections.disjoint.to_vec();
3092 self.selections.change_with(cx, |selections| {
3093 selections.select_anchors(other_selections);
3094 });
3095
3096 let other_subscription =
3097 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3098 EditorEvent::SelectionsChanged { local: true } => {
3099 let other_selections = other.read(cx).selections.disjoint.to_vec();
3100 if other_selections.is_empty() {
3101 return;
3102 }
3103 this.selections.change_with(cx, |selections| {
3104 selections.select_anchors(other_selections);
3105 });
3106 }
3107 _ => {}
3108 });
3109
3110 let this_subscription =
3111 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3112 EditorEvent::SelectionsChanged { local: true } => {
3113 let these_selections = this.selections.disjoint.to_vec();
3114 if these_selections.is_empty() {
3115 return;
3116 }
3117 other.update(cx, |other_editor, cx| {
3118 other_editor.selections.change_with(cx, |selections| {
3119 selections.select_anchors(these_selections);
3120 })
3121 });
3122 }
3123 _ => {}
3124 });
3125
3126 Subscription::join(other_subscription, this_subscription)
3127 }
3128
3129 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3130 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3131 /// effects of selection change occur at the end of the transaction.
3132 pub fn change_selections<R>(
3133 &mut self,
3134 effects: impl Into<SelectionEffects>,
3135 window: &mut Window,
3136 cx: &mut Context<Self>,
3137 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3138 ) -> R {
3139 let effects = effects.into();
3140 if let Some(state) = &mut self.deferred_selection_effects_state {
3141 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3142 state.effects.completions = effects.completions;
3143 state.effects.nav_history |= effects.nav_history;
3144 let (changed, result) = self.selections.change_with(cx, change);
3145 state.changed |= changed;
3146 return result;
3147 }
3148 let mut state = DeferredSelectionEffectsState {
3149 changed: false,
3150 effects,
3151 old_cursor_position: self.selections.newest_anchor().head(),
3152 history_entry: SelectionHistoryEntry {
3153 selections: self.selections.disjoint_anchors(),
3154 select_next_state: self.select_next_state.clone(),
3155 select_prev_state: self.select_prev_state.clone(),
3156 add_selections_state: self.add_selections_state.clone(),
3157 },
3158 };
3159 let (changed, result) = self.selections.change_with(cx, change);
3160 state.changed = state.changed || changed;
3161 if self.defer_selection_effects {
3162 self.deferred_selection_effects_state = Some(state);
3163 } else {
3164 self.apply_selection_effects(state, window, cx);
3165 }
3166 result
3167 }
3168
3169 /// Defers the effects of selection change, so that the effects of multiple calls to
3170 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3171 /// to selection history and the state of popovers based on selection position aren't
3172 /// erroneously updated.
3173 pub fn with_selection_effects_deferred<R>(
3174 &mut self,
3175 window: &mut Window,
3176 cx: &mut Context<Self>,
3177 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3178 ) -> R {
3179 let already_deferred = self.defer_selection_effects;
3180 self.defer_selection_effects = true;
3181 let result = update(self, window, cx);
3182 if !already_deferred {
3183 self.defer_selection_effects = false;
3184 if let Some(state) = self.deferred_selection_effects_state.take() {
3185 self.apply_selection_effects(state, window, cx);
3186 }
3187 }
3188 result
3189 }
3190
3191 fn apply_selection_effects(
3192 &mut self,
3193 state: DeferredSelectionEffectsState,
3194 window: &mut Window,
3195 cx: &mut Context<Self>,
3196 ) {
3197 if state.changed {
3198 self.selection_history.push(state.history_entry);
3199
3200 if let Some(autoscroll) = state.effects.scroll {
3201 self.request_autoscroll(autoscroll, cx);
3202 }
3203
3204 let old_cursor_position = &state.old_cursor_position;
3205
3206 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3207
3208 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3209 self.show_signature_help(&ShowSignatureHelp, window, cx);
3210 }
3211 }
3212 }
3213
3214 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3215 where
3216 I: IntoIterator<Item = (Range<S>, T)>,
3217 S: ToOffset,
3218 T: Into<Arc<str>>,
3219 {
3220 if self.read_only(cx) {
3221 return;
3222 }
3223
3224 self.buffer
3225 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3226 }
3227
3228 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3229 where
3230 I: IntoIterator<Item = (Range<S>, T)>,
3231 S: ToOffset,
3232 T: Into<Arc<str>>,
3233 {
3234 if self.read_only(cx) {
3235 return;
3236 }
3237
3238 self.buffer.update(cx, |buffer, cx| {
3239 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3240 });
3241 }
3242
3243 pub fn edit_with_block_indent<I, S, T>(
3244 &mut self,
3245 edits: I,
3246 original_indent_columns: Vec<Option<u32>>,
3247 cx: &mut Context<Self>,
3248 ) where
3249 I: IntoIterator<Item = (Range<S>, T)>,
3250 S: ToOffset,
3251 T: Into<Arc<str>>,
3252 {
3253 if self.read_only(cx) {
3254 return;
3255 }
3256
3257 self.buffer.update(cx, |buffer, cx| {
3258 buffer.edit(
3259 edits,
3260 Some(AutoindentMode::Block {
3261 original_indent_columns,
3262 }),
3263 cx,
3264 )
3265 });
3266 }
3267
3268 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3269 self.hide_context_menu(window, cx);
3270
3271 match phase {
3272 SelectPhase::Begin {
3273 position,
3274 add,
3275 click_count,
3276 } => self.begin_selection(position, add, click_count, window, cx),
3277 SelectPhase::BeginColumnar {
3278 position,
3279 goal_column,
3280 reset,
3281 mode,
3282 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3283 SelectPhase::Extend {
3284 position,
3285 click_count,
3286 } => self.extend_selection(position, click_count, window, cx),
3287 SelectPhase::Update {
3288 position,
3289 goal_column,
3290 scroll_delta,
3291 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3292 SelectPhase::End => self.end_selection(window, cx),
3293 }
3294 }
3295
3296 fn extend_selection(
3297 &mut self,
3298 position: DisplayPoint,
3299 click_count: usize,
3300 window: &mut Window,
3301 cx: &mut Context<Self>,
3302 ) {
3303 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3304 let tail = self.selections.newest::<usize>(cx).tail();
3305 self.begin_selection(position, false, click_count, window, cx);
3306
3307 let position = position.to_offset(&display_map, Bias::Left);
3308 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3309
3310 let mut pending_selection = self
3311 .selections
3312 .pending_anchor()
3313 .expect("extend_selection not called with pending selection");
3314 if position >= tail {
3315 pending_selection.start = tail_anchor;
3316 } else {
3317 pending_selection.end = tail_anchor;
3318 pending_selection.reversed = true;
3319 }
3320
3321 let mut pending_mode = self.selections.pending_mode().unwrap();
3322 match &mut pending_mode {
3323 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3324 _ => {}
3325 }
3326
3327 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3328 SelectionEffects::scroll(Autoscroll::fit())
3329 } else {
3330 SelectionEffects::no_scroll()
3331 };
3332
3333 self.change_selections(effects, window, cx, |s| {
3334 s.set_pending(pending_selection, pending_mode)
3335 });
3336 }
3337
3338 fn begin_selection(
3339 &mut self,
3340 position: DisplayPoint,
3341 add: bool,
3342 click_count: usize,
3343 window: &mut Window,
3344 cx: &mut Context<Self>,
3345 ) {
3346 if !self.focus_handle.is_focused(window) {
3347 self.last_focused_descendant = None;
3348 window.focus(&self.focus_handle);
3349 }
3350
3351 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3352 let buffer = &display_map.buffer_snapshot;
3353 let position = display_map.clip_point(position, Bias::Left);
3354
3355 let start;
3356 let end;
3357 let mode;
3358 let mut auto_scroll;
3359 match click_count {
3360 1 => {
3361 start = buffer.anchor_before(position.to_point(&display_map));
3362 end = start;
3363 mode = SelectMode::Character;
3364 auto_scroll = true;
3365 }
3366 2 => {
3367 let range = movement::surrounding_word(&display_map, position);
3368 start = buffer.anchor_before(range.start.to_point(&display_map));
3369 end = buffer.anchor_before(range.end.to_point(&display_map));
3370 mode = SelectMode::Word(start..end);
3371 auto_scroll = true;
3372 }
3373 3 => {
3374 let position = display_map
3375 .clip_point(position, Bias::Left)
3376 .to_point(&display_map);
3377 let line_start = display_map.prev_line_boundary(position).0;
3378 let next_line_start = buffer.clip_point(
3379 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3380 Bias::Left,
3381 );
3382 start = buffer.anchor_before(line_start);
3383 end = buffer.anchor_before(next_line_start);
3384 mode = SelectMode::Line(start..end);
3385 auto_scroll = true;
3386 }
3387 _ => {
3388 start = buffer.anchor_before(0);
3389 end = buffer.anchor_before(buffer.len());
3390 mode = SelectMode::All;
3391 auto_scroll = false;
3392 }
3393 }
3394 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3395
3396 let point_to_delete: Option<usize> = {
3397 let selected_points: Vec<Selection<Point>> =
3398 self.selections.disjoint_in_range(start..end, cx);
3399
3400 if !add || click_count > 1 {
3401 None
3402 } else if !selected_points.is_empty() {
3403 Some(selected_points[0].id)
3404 } else {
3405 let clicked_point_already_selected =
3406 self.selections.disjoint.iter().find(|selection| {
3407 selection.start.to_point(buffer) == start.to_point(buffer)
3408 || selection.end.to_point(buffer) == end.to_point(buffer)
3409 });
3410
3411 clicked_point_already_selected.map(|selection| selection.id)
3412 }
3413 };
3414
3415 let selections_count = self.selections.count();
3416
3417 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3418 if let Some(point_to_delete) = point_to_delete {
3419 s.delete(point_to_delete);
3420
3421 if selections_count == 1 {
3422 s.set_pending_anchor_range(start..end, mode);
3423 }
3424 } else {
3425 if !add {
3426 s.clear_disjoint();
3427 }
3428
3429 s.set_pending_anchor_range(start..end, mode);
3430 }
3431 });
3432 }
3433
3434 fn begin_columnar_selection(
3435 &mut self,
3436 position: DisplayPoint,
3437 goal_column: u32,
3438 reset: bool,
3439 mode: ColumnarMode,
3440 window: &mut Window,
3441 cx: &mut Context<Self>,
3442 ) {
3443 if !self.focus_handle.is_focused(window) {
3444 self.last_focused_descendant = None;
3445 window.focus(&self.focus_handle);
3446 }
3447
3448 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3449
3450 if reset {
3451 let pointer_position = display_map
3452 .buffer_snapshot
3453 .anchor_before(position.to_point(&display_map));
3454
3455 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3456 s.clear_disjoint();
3457 s.set_pending_anchor_range(
3458 pointer_position..pointer_position,
3459 SelectMode::Character,
3460 );
3461 });
3462 };
3463
3464 let tail = self.selections.newest::<Point>(cx).tail();
3465 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3466 self.columnar_selection_state = match mode {
3467 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3468 selection_tail: selection_anchor,
3469 display_point: if reset {
3470 if position.column() != goal_column {
3471 Some(DisplayPoint::new(position.row(), goal_column))
3472 } else {
3473 None
3474 }
3475 } else {
3476 None
3477 },
3478 }),
3479 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3480 selection_tail: selection_anchor,
3481 }),
3482 };
3483
3484 if !reset {
3485 self.select_columns(position, goal_column, &display_map, window, cx);
3486 }
3487 }
3488
3489 fn update_selection(
3490 &mut self,
3491 position: DisplayPoint,
3492 goal_column: u32,
3493 scroll_delta: gpui::Point<f32>,
3494 window: &mut Window,
3495 cx: &mut Context<Self>,
3496 ) {
3497 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3498
3499 if self.columnar_selection_state.is_some() {
3500 self.select_columns(position, goal_column, &display_map, window, cx);
3501 } else if let Some(mut pending) = self.selections.pending_anchor() {
3502 let buffer = self.buffer.read(cx).snapshot(cx);
3503 let head;
3504 let tail;
3505 let mode = self.selections.pending_mode().unwrap();
3506 match &mode {
3507 SelectMode::Character => {
3508 head = position.to_point(&display_map);
3509 tail = pending.tail().to_point(&buffer);
3510 }
3511 SelectMode::Word(original_range) => {
3512 let original_display_range = original_range.start.to_display_point(&display_map)
3513 ..original_range.end.to_display_point(&display_map);
3514 let original_buffer_range = original_display_range.start.to_point(&display_map)
3515 ..original_display_range.end.to_point(&display_map);
3516 if movement::is_inside_word(&display_map, position)
3517 || original_display_range.contains(&position)
3518 {
3519 let word_range = movement::surrounding_word(&display_map, position);
3520 if word_range.start < original_display_range.start {
3521 head = word_range.start.to_point(&display_map);
3522 } else {
3523 head = word_range.end.to_point(&display_map);
3524 }
3525 } else {
3526 head = position.to_point(&display_map);
3527 }
3528
3529 if head <= original_buffer_range.start {
3530 tail = original_buffer_range.end;
3531 } else {
3532 tail = original_buffer_range.start;
3533 }
3534 }
3535 SelectMode::Line(original_range) => {
3536 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3537
3538 let position = display_map
3539 .clip_point(position, Bias::Left)
3540 .to_point(&display_map);
3541 let line_start = display_map.prev_line_boundary(position).0;
3542 let next_line_start = buffer.clip_point(
3543 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3544 Bias::Left,
3545 );
3546
3547 if line_start < original_range.start {
3548 head = line_start
3549 } else {
3550 head = next_line_start
3551 }
3552
3553 if head <= original_range.start {
3554 tail = original_range.end;
3555 } else {
3556 tail = original_range.start;
3557 }
3558 }
3559 SelectMode::All => {
3560 return;
3561 }
3562 };
3563
3564 if head < tail {
3565 pending.start = buffer.anchor_before(head);
3566 pending.end = buffer.anchor_before(tail);
3567 pending.reversed = true;
3568 } else {
3569 pending.start = buffer.anchor_before(tail);
3570 pending.end = buffer.anchor_before(head);
3571 pending.reversed = false;
3572 }
3573
3574 self.change_selections(None, window, cx, |s| {
3575 s.set_pending(pending, mode);
3576 });
3577 } else {
3578 log::error!("update_selection dispatched with no pending selection");
3579 return;
3580 }
3581
3582 self.apply_scroll_delta(scroll_delta, window, cx);
3583 cx.notify();
3584 }
3585
3586 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3587 self.columnar_selection_state.take();
3588 if self.selections.pending_anchor().is_some() {
3589 let selections = self.selections.all::<usize>(cx);
3590 self.change_selections(None, window, cx, |s| {
3591 s.select(selections);
3592 s.clear_pending();
3593 });
3594 }
3595 }
3596
3597 fn select_columns(
3598 &mut self,
3599 head: DisplayPoint,
3600 goal_column: u32,
3601 display_map: &DisplaySnapshot,
3602 window: &mut Window,
3603 cx: &mut Context<Self>,
3604 ) {
3605 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3606 return;
3607 };
3608
3609 let tail = match columnar_state {
3610 ColumnarSelectionState::FromMouse {
3611 selection_tail,
3612 display_point,
3613 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(&display_map)),
3614 ColumnarSelectionState::FromSelection { selection_tail } => {
3615 selection_tail.to_display_point(&display_map)
3616 }
3617 };
3618
3619 let start_row = cmp::min(tail.row(), head.row());
3620 let end_row = cmp::max(tail.row(), head.row());
3621 let start_column = cmp::min(tail.column(), goal_column);
3622 let end_column = cmp::max(tail.column(), goal_column);
3623 let reversed = start_column < tail.column();
3624
3625 let selection_ranges = (start_row.0..=end_row.0)
3626 .map(DisplayRow)
3627 .filter_map(|row| {
3628 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3629 || start_column <= display_map.line_len(row))
3630 && !display_map.is_block_line(row)
3631 {
3632 let start = display_map
3633 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3634 .to_point(display_map);
3635 let end = display_map
3636 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3637 .to_point(display_map);
3638 if reversed {
3639 Some(end..start)
3640 } else {
3641 Some(start..end)
3642 }
3643 } else {
3644 None
3645 }
3646 })
3647 .collect::<Vec<_>>();
3648
3649 let ranges = match columnar_state {
3650 ColumnarSelectionState::FromMouse { .. } => {
3651 let mut non_empty_ranges = selection_ranges
3652 .iter()
3653 .filter(|selection_range| selection_range.start != selection_range.end)
3654 .peekable();
3655 if non_empty_ranges.peek().is_some() {
3656 non_empty_ranges.cloned().collect()
3657 } else {
3658 selection_ranges
3659 }
3660 }
3661 _ => selection_ranges,
3662 };
3663
3664 self.change_selections(None, window, cx, |s| {
3665 s.select_ranges(ranges);
3666 });
3667 cx.notify();
3668 }
3669
3670 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3671 self.selections
3672 .all_adjusted(cx)
3673 .iter()
3674 .any(|selection| !selection.is_empty())
3675 }
3676
3677 pub fn has_pending_nonempty_selection(&self) -> bool {
3678 let pending_nonempty_selection = match self.selections.pending_anchor() {
3679 Some(Selection { start, end, .. }) => start != end,
3680 None => false,
3681 };
3682
3683 pending_nonempty_selection
3684 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3685 }
3686
3687 pub fn has_pending_selection(&self) -> bool {
3688 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3689 }
3690
3691 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3692 self.selection_mark_mode = false;
3693 self.selection_drag_state = SelectionDragState::None;
3694
3695 if self.clear_expanded_diff_hunks(cx) {
3696 cx.notify();
3697 return;
3698 }
3699 if self.dismiss_menus_and_popups(true, window, cx) {
3700 return;
3701 }
3702
3703 if self.mode.is_full()
3704 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3705 {
3706 return;
3707 }
3708
3709 cx.propagate();
3710 }
3711
3712 pub fn dismiss_menus_and_popups(
3713 &mut self,
3714 is_user_requested: bool,
3715 window: &mut Window,
3716 cx: &mut Context<Self>,
3717 ) -> bool {
3718 if self.take_rename(false, window, cx).is_some() {
3719 return true;
3720 }
3721
3722 if hide_hover(self, cx) {
3723 return true;
3724 }
3725
3726 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3727 return true;
3728 }
3729
3730 if self.hide_context_menu(window, cx).is_some() {
3731 return true;
3732 }
3733
3734 if self.mouse_context_menu.take().is_some() {
3735 return true;
3736 }
3737
3738 if is_user_requested && self.discard_inline_completion(true, cx) {
3739 return true;
3740 }
3741
3742 if self.snippet_stack.pop().is_some() {
3743 return true;
3744 }
3745
3746 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3747 self.dismiss_diagnostics(cx);
3748 return true;
3749 }
3750
3751 false
3752 }
3753
3754 fn linked_editing_ranges_for(
3755 &self,
3756 selection: Range<text::Anchor>,
3757 cx: &App,
3758 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3759 if self.linked_edit_ranges.is_empty() {
3760 return None;
3761 }
3762 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3763 selection.end.buffer_id.and_then(|end_buffer_id| {
3764 if selection.start.buffer_id != Some(end_buffer_id) {
3765 return None;
3766 }
3767 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3768 let snapshot = buffer.read(cx).snapshot();
3769 self.linked_edit_ranges
3770 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3771 .map(|ranges| (ranges, snapshot, buffer))
3772 })?;
3773 use text::ToOffset as TO;
3774 // find offset from the start of current range to current cursor position
3775 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3776
3777 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3778 let start_difference = start_offset - start_byte_offset;
3779 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3780 let end_difference = end_offset - start_byte_offset;
3781 // Current range has associated linked ranges.
3782 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3783 for range in linked_ranges.iter() {
3784 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3785 let end_offset = start_offset + end_difference;
3786 let start_offset = start_offset + start_difference;
3787 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3788 continue;
3789 }
3790 if self.selections.disjoint_anchor_ranges().any(|s| {
3791 if s.start.buffer_id != selection.start.buffer_id
3792 || s.end.buffer_id != selection.end.buffer_id
3793 {
3794 return false;
3795 }
3796 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3797 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3798 }) {
3799 continue;
3800 }
3801 let start = buffer_snapshot.anchor_after(start_offset);
3802 let end = buffer_snapshot.anchor_after(end_offset);
3803 linked_edits
3804 .entry(buffer.clone())
3805 .or_default()
3806 .push(start..end);
3807 }
3808 Some(linked_edits)
3809 }
3810
3811 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3812 let text: Arc<str> = text.into();
3813
3814 if self.read_only(cx) {
3815 return;
3816 }
3817
3818 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3819
3820 let selections = self.selections.all_adjusted(cx);
3821 let mut bracket_inserted = false;
3822 let mut edits = Vec::new();
3823 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3824 let mut new_selections = Vec::with_capacity(selections.len());
3825 let mut new_autoclose_regions = Vec::new();
3826 let snapshot = self.buffer.read(cx).read(cx);
3827 let mut clear_linked_edit_ranges = false;
3828
3829 for (selection, autoclose_region) in
3830 self.selections_with_autoclose_regions(selections, &snapshot)
3831 {
3832 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3833 // Determine if the inserted text matches the opening or closing
3834 // bracket of any of this language's bracket pairs.
3835 let mut bracket_pair = None;
3836 let mut is_bracket_pair_start = false;
3837 let mut is_bracket_pair_end = false;
3838 if !text.is_empty() {
3839 let mut bracket_pair_matching_end = None;
3840 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3841 // and they are removing the character that triggered IME popup.
3842 for (pair, enabled) in scope.brackets() {
3843 if !pair.close && !pair.surround {
3844 continue;
3845 }
3846
3847 if enabled && pair.start.ends_with(text.as_ref()) {
3848 let prefix_len = pair.start.len() - text.len();
3849 let preceding_text_matches_prefix = prefix_len == 0
3850 || (selection.start.column >= (prefix_len as u32)
3851 && snapshot.contains_str_at(
3852 Point::new(
3853 selection.start.row,
3854 selection.start.column - (prefix_len as u32),
3855 ),
3856 &pair.start[..prefix_len],
3857 ));
3858 if preceding_text_matches_prefix {
3859 bracket_pair = Some(pair.clone());
3860 is_bracket_pair_start = true;
3861 break;
3862 }
3863 }
3864 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3865 {
3866 // take first bracket pair matching end, but don't break in case a later bracket
3867 // pair matches start
3868 bracket_pair_matching_end = Some(pair.clone());
3869 }
3870 }
3871 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3872 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3873 is_bracket_pair_end = true;
3874 }
3875 }
3876
3877 if let Some(bracket_pair) = bracket_pair {
3878 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3879 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3880 let auto_surround =
3881 self.use_auto_surround && snapshot_settings.use_auto_surround;
3882 if selection.is_empty() {
3883 if is_bracket_pair_start {
3884 // If the inserted text is a suffix of an opening bracket and the
3885 // selection is preceded by the rest of the opening bracket, then
3886 // insert the closing bracket.
3887 let following_text_allows_autoclose = snapshot
3888 .chars_at(selection.start)
3889 .next()
3890 .map_or(true, |c| scope.should_autoclose_before(c));
3891
3892 let preceding_text_allows_autoclose = selection.start.column == 0
3893 || snapshot.reversed_chars_at(selection.start).next().map_or(
3894 true,
3895 |c| {
3896 bracket_pair.start != bracket_pair.end
3897 || !snapshot
3898 .char_classifier_at(selection.start)
3899 .is_word(c)
3900 },
3901 );
3902
3903 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3904 && bracket_pair.start.len() == 1
3905 {
3906 let target = bracket_pair.start.chars().next().unwrap();
3907 let current_line_count = snapshot
3908 .reversed_chars_at(selection.start)
3909 .take_while(|&c| c != '\n')
3910 .filter(|&c| c == target)
3911 .count();
3912 current_line_count % 2 == 1
3913 } else {
3914 false
3915 };
3916
3917 if autoclose
3918 && bracket_pair.close
3919 && following_text_allows_autoclose
3920 && preceding_text_allows_autoclose
3921 && !is_closing_quote
3922 {
3923 let anchor = snapshot.anchor_before(selection.end);
3924 new_selections.push((selection.map(|_| anchor), text.len()));
3925 new_autoclose_regions.push((
3926 anchor,
3927 text.len(),
3928 selection.id,
3929 bracket_pair.clone(),
3930 ));
3931 edits.push((
3932 selection.range(),
3933 format!("{}{}", text, bracket_pair.end).into(),
3934 ));
3935 bracket_inserted = true;
3936 continue;
3937 }
3938 }
3939
3940 if let Some(region) = autoclose_region {
3941 // If the selection is followed by an auto-inserted closing bracket,
3942 // then don't insert that closing bracket again; just move the selection
3943 // past the closing bracket.
3944 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3945 && text.as_ref() == region.pair.end.as_str();
3946 if should_skip {
3947 let anchor = snapshot.anchor_after(selection.end);
3948 new_selections
3949 .push((selection.map(|_| anchor), region.pair.end.len()));
3950 continue;
3951 }
3952 }
3953
3954 let always_treat_brackets_as_autoclosed = snapshot
3955 .language_settings_at(selection.start, cx)
3956 .always_treat_brackets_as_autoclosed;
3957 if always_treat_brackets_as_autoclosed
3958 && is_bracket_pair_end
3959 && snapshot.contains_str_at(selection.end, text.as_ref())
3960 {
3961 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3962 // and the inserted text is a closing bracket and the selection is followed
3963 // by the closing bracket then move the selection past the closing bracket.
3964 let anchor = snapshot.anchor_after(selection.end);
3965 new_selections.push((selection.map(|_| anchor), text.len()));
3966 continue;
3967 }
3968 }
3969 // If an opening bracket is 1 character long and is typed while
3970 // text is selected, then surround that text with the bracket pair.
3971 else if auto_surround
3972 && bracket_pair.surround
3973 && is_bracket_pair_start
3974 && bracket_pair.start.chars().count() == 1
3975 {
3976 edits.push((selection.start..selection.start, text.clone()));
3977 edits.push((
3978 selection.end..selection.end,
3979 bracket_pair.end.as_str().into(),
3980 ));
3981 bracket_inserted = true;
3982 new_selections.push((
3983 Selection {
3984 id: selection.id,
3985 start: snapshot.anchor_after(selection.start),
3986 end: snapshot.anchor_before(selection.end),
3987 reversed: selection.reversed,
3988 goal: selection.goal,
3989 },
3990 0,
3991 ));
3992 continue;
3993 }
3994 }
3995 }
3996
3997 if self.auto_replace_emoji_shortcode
3998 && selection.is_empty()
3999 && text.as_ref().ends_with(':')
4000 {
4001 if let Some(possible_emoji_short_code) =
4002 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4003 {
4004 if !possible_emoji_short_code.is_empty() {
4005 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
4006 let emoji_shortcode_start = Point::new(
4007 selection.start.row,
4008 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4009 );
4010
4011 // Remove shortcode from buffer
4012 edits.push((
4013 emoji_shortcode_start..selection.start,
4014 "".to_string().into(),
4015 ));
4016 new_selections.push((
4017 Selection {
4018 id: selection.id,
4019 start: snapshot.anchor_after(emoji_shortcode_start),
4020 end: snapshot.anchor_before(selection.start),
4021 reversed: selection.reversed,
4022 goal: selection.goal,
4023 },
4024 0,
4025 ));
4026
4027 // Insert emoji
4028 let selection_start_anchor = snapshot.anchor_after(selection.start);
4029 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4030 edits.push((selection.start..selection.end, emoji.to_string().into()));
4031
4032 continue;
4033 }
4034 }
4035 }
4036 }
4037
4038 // If not handling any auto-close operation, then just replace the selected
4039 // text with the given input and move the selection to the end of the
4040 // newly inserted text.
4041 let anchor = snapshot.anchor_after(selection.end);
4042 if !self.linked_edit_ranges.is_empty() {
4043 let start_anchor = snapshot.anchor_before(selection.start);
4044
4045 let is_word_char = text.chars().next().map_or(true, |char| {
4046 let classifier = snapshot
4047 .char_classifier_at(start_anchor.to_offset(&snapshot))
4048 .ignore_punctuation(true);
4049 classifier.is_word(char)
4050 });
4051
4052 if is_word_char {
4053 if let Some(ranges) = self
4054 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4055 {
4056 for (buffer, edits) in ranges {
4057 linked_edits
4058 .entry(buffer.clone())
4059 .or_default()
4060 .extend(edits.into_iter().map(|range| (range, text.clone())));
4061 }
4062 }
4063 } else {
4064 clear_linked_edit_ranges = true;
4065 }
4066 }
4067
4068 new_selections.push((selection.map(|_| anchor), 0));
4069 edits.push((selection.start..selection.end, text.clone()));
4070 }
4071
4072 drop(snapshot);
4073
4074 self.transact(window, cx, |this, window, cx| {
4075 if clear_linked_edit_ranges {
4076 this.linked_edit_ranges.clear();
4077 }
4078 let initial_buffer_versions =
4079 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4080
4081 this.buffer.update(cx, |buffer, cx| {
4082 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4083 });
4084 for (buffer, edits) in linked_edits {
4085 buffer.update(cx, |buffer, cx| {
4086 let snapshot = buffer.snapshot();
4087 let edits = edits
4088 .into_iter()
4089 .map(|(range, text)| {
4090 use text::ToPoint as TP;
4091 let end_point = TP::to_point(&range.end, &snapshot);
4092 let start_point = TP::to_point(&range.start, &snapshot);
4093 (start_point..end_point, text)
4094 })
4095 .sorted_by_key(|(range, _)| range.start);
4096 buffer.edit(edits, None, cx);
4097 })
4098 }
4099 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4100 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4101 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4102 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4103 .zip(new_selection_deltas)
4104 .map(|(selection, delta)| Selection {
4105 id: selection.id,
4106 start: selection.start + delta,
4107 end: selection.end + delta,
4108 reversed: selection.reversed,
4109 goal: SelectionGoal::None,
4110 })
4111 .collect::<Vec<_>>();
4112
4113 let mut i = 0;
4114 for (position, delta, selection_id, pair) in new_autoclose_regions {
4115 let position = position.to_offset(&map.buffer_snapshot) + delta;
4116 let start = map.buffer_snapshot.anchor_before(position);
4117 let end = map.buffer_snapshot.anchor_after(position);
4118 while let Some(existing_state) = this.autoclose_regions.get(i) {
4119 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4120 Ordering::Less => i += 1,
4121 Ordering::Greater => break,
4122 Ordering::Equal => {
4123 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4124 Ordering::Less => i += 1,
4125 Ordering::Equal => break,
4126 Ordering::Greater => break,
4127 }
4128 }
4129 }
4130 }
4131 this.autoclose_regions.insert(
4132 i,
4133 AutocloseRegion {
4134 selection_id,
4135 range: start..end,
4136 pair,
4137 },
4138 );
4139 }
4140
4141 let had_active_inline_completion = this.has_active_inline_completion();
4142 this.change_selections(
4143 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4144 window,
4145 cx,
4146 |s| s.select(new_selections),
4147 );
4148
4149 if !bracket_inserted {
4150 if let Some(on_type_format_task) =
4151 this.trigger_on_type_formatting(text.to_string(), window, cx)
4152 {
4153 on_type_format_task.detach_and_log_err(cx);
4154 }
4155 }
4156
4157 let editor_settings = EditorSettings::get_global(cx);
4158 if bracket_inserted
4159 && (editor_settings.auto_signature_help
4160 || editor_settings.show_signature_help_after_edits)
4161 {
4162 this.show_signature_help(&ShowSignatureHelp, window, cx);
4163 }
4164
4165 let trigger_in_words =
4166 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4167 if this.hard_wrap.is_some() {
4168 let latest: Range<Point> = this.selections.newest(cx).range();
4169 if latest.is_empty()
4170 && this
4171 .buffer()
4172 .read(cx)
4173 .snapshot(cx)
4174 .line_len(MultiBufferRow(latest.start.row))
4175 == latest.start.column
4176 {
4177 this.rewrap_impl(
4178 RewrapOptions {
4179 override_language_settings: true,
4180 preserve_existing_whitespace: true,
4181 },
4182 cx,
4183 )
4184 }
4185 }
4186 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4187 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4188 this.refresh_inline_completion(true, false, window, cx);
4189 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4190 });
4191 }
4192
4193 fn find_possible_emoji_shortcode_at_position(
4194 snapshot: &MultiBufferSnapshot,
4195 position: Point,
4196 ) -> Option<String> {
4197 let mut chars = Vec::new();
4198 let mut found_colon = false;
4199 for char in snapshot.reversed_chars_at(position).take(100) {
4200 // Found a possible emoji shortcode in the middle of the buffer
4201 if found_colon {
4202 if char.is_whitespace() {
4203 chars.reverse();
4204 return Some(chars.iter().collect());
4205 }
4206 // If the previous character is not a whitespace, we are in the middle of a word
4207 // and we only want to complete the shortcode if the word is made up of other emojis
4208 let mut containing_word = String::new();
4209 for ch in snapshot
4210 .reversed_chars_at(position)
4211 .skip(chars.len() + 1)
4212 .take(100)
4213 {
4214 if ch.is_whitespace() {
4215 break;
4216 }
4217 containing_word.push(ch);
4218 }
4219 let containing_word = containing_word.chars().rev().collect::<String>();
4220 if util::word_consists_of_emojis(containing_word.as_str()) {
4221 chars.reverse();
4222 return Some(chars.iter().collect());
4223 }
4224 }
4225
4226 if char.is_whitespace() || !char.is_ascii() {
4227 return None;
4228 }
4229 if char == ':' {
4230 found_colon = true;
4231 } else {
4232 chars.push(char);
4233 }
4234 }
4235 // Found a possible emoji shortcode at the beginning of the buffer
4236 chars.reverse();
4237 Some(chars.iter().collect())
4238 }
4239
4240 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4241 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4242 self.transact(window, cx, |this, window, cx| {
4243 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4244 let selections = this.selections.all::<usize>(cx);
4245 let multi_buffer = this.buffer.read(cx);
4246 let buffer = multi_buffer.snapshot(cx);
4247 selections
4248 .iter()
4249 .map(|selection| {
4250 let start_point = selection.start.to_point(&buffer);
4251 let mut existing_indent =
4252 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4253 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4254 let start = selection.start;
4255 let end = selection.end;
4256 let selection_is_empty = start == end;
4257 let language_scope = buffer.language_scope_at(start);
4258 let (
4259 comment_delimiter,
4260 doc_delimiter,
4261 insert_extra_newline,
4262 indent_on_newline,
4263 indent_on_extra_newline,
4264 ) = if let Some(language) = &language_scope {
4265 let mut insert_extra_newline =
4266 insert_extra_newline_brackets(&buffer, start..end, language)
4267 || insert_extra_newline_tree_sitter(&buffer, start..end);
4268
4269 // Comment extension on newline is allowed only for cursor selections
4270 let comment_delimiter = maybe!({
4271 if !selection_is_empty {
4272 return None;
4273 }
4274
4275 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4276 return None;
4277 }
4278
4279 let delimiters = language.line_comment_prefixes();
4280 let max_len_of_delimiter =
4281 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4282 let (snapshot, range) =
4283 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4284
4285 let num_of_whitespaces = snapshot
4286 .chars_for_range(range.clone())
4287 .take_while(|c| c.is_whitespace())
4288 .count();
4289 let comment_candidate = snapshot
4290 .chars_for_range(range)
4291 .skip(num_of_whitespaces)
4292 .take(max_len_of_delimiter)
4293 .collect::<String>();
4294 let (delimiter, trimmed_len) = delimiters
4295 .iter()
4296 .filter_map(|delimiter| {
4297 let prefix = delimiter.trim_end();
4298 if comment_candidate.starts_with(prefix) {
4299 Some((delimiter, prefix.len()))
4300 } else {
4301 None
4302 }
4303 })
4304 .max_by_key(|(_, len)| *len)?;
4305
4306 let cursor_is_placed_after_comment_marker =
4307 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4308 if cursor_is_placed_after_comment_marker {
4309 Some(delimiter.clone())
4310 } else {
4311 None
4312 }
4313 });
4314
4315 let mut indent_on_newline = IndentSize::spaces(0);
4316 let mut indent_on_extra_newline = IndentSize::spaces(0);
4317
4318 let doc_delimiter = maybe!({
4319 if !selection_is_empty {
4320 return None;
4321 }
4322
4323 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4324 return None;
4325 }
4326
4327 let DocumentationConfig {
4328 start: start_tag,
4329 end: end_tag,
4330 prefix: delimiter,
4331 tab_size: len,
4332 } = language.documentation()?;
4333
4334 let is_within_block_comment = buffer
4335 .language_scope_at(start_point)
4336 .is_some_and(|scope| scope.override_name() == Some("comment"));
4337 if !is_within_block_comment {
4338 return None;
4339 }
4340
4341 let (snapshot, range) =
4342 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4343
4344 let num_of_whitespaces = snapshot
4345 .chars_for_range(range.clone())
4346 .take_while(|c| c.is_whitespace())
4347 .count();
4348
4349 // 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.
4350 let column = start_point.column;
4351 let cursor_is_after_start_tag = {
4352 let start_tag_len = start_tag.len();
4353 let start_tag_line = snapshot
4354 .chars_for_range(range.clone())
4355 .skip(num_of_whitespaces)
4356 .take(start_tag_len)
4357 .collect::<String>();
4358 if start_tag_line.starts_with(start_tag.as_ref()) {
4359 num_of_whitespaces + start_tag_len <= column as usize
4360 } else {
4361 false
4362 }
4363 };
4364
4365 let cursor_is_after_delimiter = {
4366 let delimiter_trim = delimiter.trim_end();
4367 let delimiter_line = snapshot
4368 .chars_for_range(range.clone())
4369 .skip(num_of_whitespaces)
4370 .take(delimiter_trim.len())
4371 .collect::<String>();
4372 if delimiter_line.starts_with(delimiter_trim) {
4373 num_of_whitespaces + delimiter_trim.len() <= column as usize
4374 } else {
4375 false
4376 }
4377 };
4378
4379 let cursor_is_before_end_tag_if_exists = {
4380 let mut char_position = 0u32;
4381 let mut end_tag_offset = None;
4382
4383 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4384 if let Some(byte_pos) = chunk.find(&**end_tag) {
4385 let chars_before_match =
4386 chunk[..byte_pos].chars().count() as u32;
4387 end_tag_offset =
4388 Some(char_position + chars_before_match);
4389 break 'outer;
4390 }
4391 char_position += chunk.chars().count() as u32;
4392 }
4393
4394 if let Some(end_tag_offset) = end_tag_offset {
4395 let cursor_is_before_end_tag = column <= end_tag_offset;
4396 if cursor_is_after_start_tag {
4397 if cursor_is_before_end_tag {
4398 insert_extra_newline = true;
4399 }
4400 let cursor_is_at_start_of_end_tag =
4401 column == end_tag_offset;
4402 if cursor_is_at_start_of_end_tag {
4403 indent_on_extra_newline.len = (*len).into();
4404 }
4405 }
4406 cursor_is_before_end_tag
4407 } else {
4408 true
4409 }
4410 };
4411
4412 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4413 && cursor_is_before_end_tag_if_exists
4414 {
4415 if cursor_is_after_start_tag {
4416 indent_on_newline.len = (*len).into();
4417 }
4418 Some(delimiter.clone())
4419 } else {
4420 None
4421 }
4422 });
4423
4424 (
4425 comment_delimiter,
4426 doc_delimiter,
4427 insert_extra_newline,
4428 indent_on_newline,
4429 indent_on_extra_newline,
4430 )
4431 } else {
4432 (
4433 None,
4434 None,
4435 false,
4436 IndentSize::default(),
4437 IndentSize::default(),
4438 )
4439 };
4440
4441 let prevent_auto_indent = doc_delimiter.is_some();
4442 let delimiter = comment_delimiter.or(doc_delimiter);
4443
4444 let capacity_for_delimiter =
4445 delimiter.as_deref().map(str::len).unwrap_or_default();
4446 let mut new_text = String::with_capacity(
4447 1 + capacity_for_delimiter
4448 + existing_indent.len as usize
4449 + indent_on_newline.len as usize
4450 + indent_on_extra_newline.len as usize,
4451 );
4452 new_text.push('\n');
4453 new_text.extend(existing_indent.chars());
4454 new_text.extend(indent_on_newline.chars());
4455
4456 if let Some(delimiter) = &delimiter {
4457 new_text.push_str(delimiter);
4458 }
4459
4460 if insert_extra_newline {
4461 new_text.push('\n');
4462 new_text.extend(existing_indent.chars());
4463 new_text.extend(indent_on_extra_newline.chars());
4464 }
4465
4466 let anchor = buffer.anchor_after(end);
4467 let new_selection = selection.map(|_| anchor);
4468 (
4469 ((start..end, new_text), prevent_auto_indent),
4470 (insert_extra_newline, new_selection),
4471 )
4472 })
4473 .unzip()
4474 };
4475
4476 let mut auto_indent_edits = Vec::new();
4477 let mut edits = Vec::new();
4478 for (edit, prevent_auto_indent) in edits_with_flags {
4479 if prevent_auto_indent {
4480 edits.push(edit);
4481 } else {
4482 auto_indent_edits.push(edit);
4483 }
4484 }
4485 if !edits.is_empty() {
4486 this.edit(edits, cx);
4487 }
4488 if !auto_indent_edits.is_empty() {
4489 this.edit_with_autoindent(auto_indent_edits, cx);
4490 }
4491
4492 let buffer = this.buffer.read(cx).snapshot(cx);
4493 let new_selections = selection_info
4494 .into_iter()
4495 .map(|(extra_newline_inserted, new_selection)| {
4496 let mut cursor = new_selection.end.to_point(&buffer);
4497 if extra_newline_inserted {
4498 cursor.row -= 1;
4499 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4500 }
4501 new_selection.map(|_| cursor)
4502 })
4503 .collect();
4504
4505 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4506 s.select(new_selections)
4507 });
4508 this.refresh_inline_completion(true, false, window, cx);
4509 });
4510 }
4511
4512 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4513 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4514
4515 let buffer = self.buffer.read(cx);
4516 let snapshot = buffer.snapshot(cx);
4517
4518 let mut edits = Vec::new();
4519 let mut rows = Vec::new();
4520
4521 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4522 let cursor = selection.head();
4523 let row = cursor.row;
4524
4525 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4526
4527 let newline = "\n".to_string();
4528 edits.push((start_of_line..start_of_line, newline));
4529
4530 rows.push(row + rows_inserted as u32);
4531 }
4532
4533 self.transact(window, cx, |editor, window, cx| {
4534 editor.edit(edits, cx);
4535
4536 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4537 let mut index = 0;
4538 s.move_cursors_with(|map, _, _| {
4539 let row = rows[index];
4540 index += 1;
4541
4542 let point = Point::new(row, 0);
4543 let boundary = map.next_line_boundary(point).1;
4544 let clipped = map.clip_point(boundary, Bias::Left);
4545
4546 (clipped, SelectionGoal::None)
4547 });
4548 });
4549
4550 let mut indent_edits = Vec::new();
4551 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4552 for row in rows {
4553 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4554 for (row, indent) in indents {
4555 if indent.len == 0 {
4556 continue;
4557 }
4558
4559 let text = match indent.kind {
4560 IndentKind::Space => " ".repeat(indent.len as usize),
4561 IndentKind::Tab => "\t".repeat(indent.len as usize),
4562 };
4563 let point = Point::new(row.0, 0);
4564 indent_edits.push((point..point, text));
4565 }
4566 }
4567 editor.edit(indent_edits, cx);
4568 });
4569 }
4570
4571 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4572 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4573
4574 let buffer = self.buffer.read(cx);
4575 let snapshot = buffer.snapshot(cx);
4576
4577 let mut edits = Vec::new();
4578 let mut rows = Vec::new();
4579 let mut rows_inserted = 0;
4580
4581 for selection in self.selections.all_adjusted(cx) {
4582 let cursor = selection.head();
4583 let row = cursor.row;
4584
4585 let point = Point::new(row + 1, 0);
4586 let start_of_line = snapshot.clip_point(point, Bias::Left);
4587
4588 let newline = "\n".to_string();
4589 edits.push((start_of_line..start_of_line, newline));
4590
4591 rows_inserted += 1;
4592 rows.push(row + rows_inserted);
4593 }
4594
4595 self.transact(window, cx, |editor, window, cx| {
4596 editor.edit(edits, cx);
4597
4598 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4599 let mut index = 0;
4600 s.move_cursors_with(|map, _, _| {
4601 let row = rows[index];
4602 index += 1;
4603
4604 let point = Point::new(row, 0);
4605 let boundary = map.next_line_boundary(point).1;
4606 let clipped = map.clip_point(boundary, Bias::Left);
4607
4608 (clipped, SelectionGoal::None)
4609 });
4610 });
4611
4612 let mut indent_edits = Vec::new();
4613 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4614 for row in rows {
4615 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4616 for (row, indent) in indents {
4617 if indent.len == 0 {
4618 continue;
4619 }
4620
4621 let text = match indent.kind {
4622 IndentKind::Space => " ".repeat(indent.len as usize),
4623 IndentKind::Tab => "\t".repeat(indent.len as usize),
4624 };
4625 let point = Point::new(row.0, 0);
4626 indent_edits.push((point..point, text));
4627 }
4628 }
4629 editor.edit(indent_edits, cx);
4630 });
4631 }
4632
4633 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4634 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4635 original_indent_columns: Vec::new(),
4636 });
4637 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4638 }
4639
4640 fn insert_with_autoindent_mode(
4641 &mut self,
4642 text: &str,
4643 autoindent_mode: Option<AutoindentMode>,
4644 window: &mut Window,
4645 cx: &mut Context<Self>,
4646 ) {
4647 if self.read_only(cx) {
4648 return;
4649 }
4650
4651 let text: Arc<str> = text.into();
4652 self.transact(window, cx, |this, window, cx| {
4653 let old_selections = this.selections.all_adjusted(cx);
4654 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4655 let anchors = {
4656 let snapshot = buffer.read(cx);
4657 old_selections
4658 .iter()
4659 .map(|s| {
4660 let anchor = snapshot.anchor_after(s.head());
4661 s.map(|_| anchor)
4662 })
4663 .collect::<Vec<_>>()
4664 };
4665 buffer.edit(
4666 old_selections
4667 .iter()
4668 .map(|s| (s.start..s.end, text.clone())),
4669 autoindent_mode,
4670 cx,
4671 );
4672 anchors
4673 });
4674
4675 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4676 s.select_anchors(selection_anchors);
4677 });
4678
4679 cx.notify();
4680 });
4681 }
4682
4683 fn trigger_completion_on_input(
4684 &mut self,
4685 text: &str,
4686 trigger_in_words: bool,
4687 window: &mut Window,
4688 cx: &mut Context<Self>,
4689 ) {
4690 let completions_source = self
4691 .context_menu
4692 .borrow()
4693 .as_ref()
4694 .and_then(|menu| match menu {
4695 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4696 CodeContextMenu::CodeActions(_) => None,
4697 });
4698
4699 match completions_source {
4700 Some(CompletionsMenuSource::Words) => {
4701 self.show_word_completions(&ShowWordCompletions, window, cx)
4702 }
4703 Some(CompletionsMenuSource::Normal)
4704 | Some(CompletionsMenuSource::SnippetChoices)
4705 | None
4706 if self.is_completion_trigger(
4707 text,
4708 trigger_in_words,
4709 completions_source.is_some(),
4710 cx,
4711 ) =>
4712 {
4713 self.show_completions(
4714 &ShowCompletions {
4715 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4716 },
4717 window,
4718 cx,
4719 )
4720 }
4721 _ => {
4722 self.hide_context_menu(window, cx);
4723 }
4724 }
4725 }
4726
4727 fn is_completion_trigger(
4728 &self,
4729 text: &str,
4730 trigger_in_words: bool,
4731 menu_is_open: bool,
4732 cx: &mut Context<Self>,
4733 ) -> bool {
4734 let position = self.selections.newest_anchor().head();
4735 let multibuffer = self.buffer.read(cx);
4736 let Some(buffer) = position
4737 .buffer_id
4738 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4739 else {
4740 return false;
4741 };
4742
4743 if let Some(completion_provider) = &self.completion_provider {
4744 completion_provider.is_completion_trigger(
4745 &buffer,
4746 position.text_anchor,
4747 text,
4748 trigger_in_words,
4749 menu_is_open,
4750 cx,
4751 )
4752 } else {
4753 false
4754 }
4755 }
4756
4757 /// If any empty selections is touching the start of its innermost containing autoclose
4758 /// region, expand it to select the brackets.
4759 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4760 let selections = self.selections.all::<usize>(cx);
4761 let buffer = self.buffer.read(cx).read(cx);
4762 let new_selections = self
4763 .selections_with_autoclose_regions(selections, &buffer)
4764 .map(|(mut selection, region)| {
4765 if !selection.is_empty() {
4766 return selection;
4767 }
4768
4769 if let Some(region) = region {
4770 let mut range = region.range.to_offset(&buffer);
4771 if selection.start == range.start && range.start >= region.pair.start.len() {
4772 range.start -= region.pair.start.len();
4773 if buffer.contains_str_at(range.start, ®ion.pair.start)
4774 && buffer.contains_str_at(range.end, ®ion.pair.end)
4775 {
4776 range.end += region.pair.end.len();
4777 selection.start = range.start;
4778 selection.end = range.end;
4779
4780 return selection;
4781 }
4782 }
4783 }
4784
4785 let always_treat_brackets_as_autoclosed = buffer
4786 .language_settings_at(selection.start, cx)
4787 .always_treat_brackets_as_autoclosed;
4788
4789 if !always_treat_brackets_as_autoclosed {
4790 return selection;
4791 }
4792
4793 if let Some(scope) = buffer.language_scope_at(selection.start) {
4794 for (pair, enabled) in scope.brackets() {
4795 if !enabled || !pair.close {
4796 continue;
4797 }
4798
4799 if buffer.contains_str_at(selection.start, &pair.end) {
4800 let pair_start_len = pair.start.len();
4801 if buffer.contains_str_at(
4802 selection.start.saturating_sub(pair_start_len),
4803 &pair.start,
4804 ) {
4805 selection.start -= pair_start_len;
4806 selection.end += pair.end.len();
4807
4808 return selection;
4809 }
4810 }
4811 }
4812 }
4813
4814 selection
4815 })
4816 .collect();
4817
4818 drop(buffer);
4819 self.change_selections(None, window, cx, |selections| {
4820 selections.select(new_selections)
4821 });
4822 }
4823
4824 /// Iterate the given selections, and for each one, find the smallest surrounding
4825 /// autoclose region. This uses the ordering of the selections and the autoclose
4826 /// regions to avoid repeated comparisons.
4827 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4828 &'a self,
4829 selections: impl IntoIterator<Item = Selection<D>>,
4830 buffer: &'a MultiBufferSnapshot,
4831 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4832 let mut i = 0;
4833 let mut regions = self.autoclose_regions.as_slice();
4834 selections.into_iter().map(move |selection| {
4835 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4836
4837 let mut enclosing = None;
4838 while let Some(pair_state) = regions.get(i) {
4839 if pair_state.range.end.to_offset(buffer) < range.start {
4840 regions = ®ions[i + 1..];
4841 i = 0;
4842 } else if pair_state.range.start.to_offset(buffer) > range.end {
4843 break;
4844 } else {
4845 if pair_state.selection_id == selection.id {
4846 enclosing = Some(pair_state);
4847 }
4848 i += 1;
4849 }
4850 }
4851
4852 (selection, enclosing)
4853 })
4854 }
4855
4856 /// Remove any autoclose regions that no longer contain their selection.
4857 fn invalidate_autoclose_regions(
4858 &mut self,
4859 mut selections: &[Selection<Anchor>],
4860 buffer: &MultiBufferSnapshot,
4861 ) {
4862 self.autoclose_regions.retain(|state| {
4863 let mut i = 0;
4864 while let Some(selection) = selections.get(i) {
4865 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4866 selections = &selections[1..];
4867 continue;
4868 }
4869 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4870 break;
4871 }
4872 if selection.id == state.selection_id {
4873 return true;
4874 } else {
4875 i += 1;
4876 }
4877 }
4878 false
4879 });
4880 }
4881
4882 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4883 let offset = position.to_offset(buffer);
4884 let (word_range, kind) = buffer.surrounding_word(offset, true);
4885 if offset > word_range.start && kind == Some(CharKind::Word) {
4886 Some(
4887 buffer
4888 .text_for_range(word_range.start..offset)
4889 .collect::<String>(),
4890 )
4891 } else {
4892 None
4893 }
4894 }
4895
4896 pub fn toggle_inline_values(
4897 &mut self,
4898 _: &ToggleInlineValues,
4899 _: &mut Window,
4900 cx: &mut Context<Self>,
4901 ) {
4902 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4903
4904 self.refresh_inline_values(cx);
4905 }
4906
4907 pub fn toggle_inlay_hints(
4908 &mut self,
4909 _: &ToggleInlayHints,
4910 _: &mut Window,
4911 cx: &mut Context<Self>,
4912 ) {
4913 self.refresh_inlay_hints(
4914 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4915 cx,
4916 );
4917 }
4918
4919 pub fn inlay_hints_enabled(&self) -> bool {
4920 self.inlay_hint_cache.enabled
4921 }
4922
4923 pub fn inline_values_enabled(&self) -> bool {
4924 self.inline_value_cache.enabled
4925 }
4926
4927 #[cfg(any(test, feature = "test-support"))]
4928 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4929 self.display_map
4930 .read(cx)
4931 .current_inlays()
4932 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4933 .cloned()
4934 .collect()
4935 }
4936
4937 #[cfg(any(test, feature = "test-support"))]
4938 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
4939 self.display_map
4940 .read(cx)
4941 .current_inlays()
4942 .cloned()
4943 .collect()
4944 }
4945
4946 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4947 if self.semantics_provider.is_none() || !self.mode.is_full() {
4948 return;
4949 }
4950
4951 let reason_description = reason.description();
4952 let ignore_debounce = matches!(
4953 reason,
4954 InlayHintRefreshReason::SettingsChange(_)
4955 | InlayHintRefreshReason::Toggle(_)
4956 | InlayHintRefreshReason::ExcerptsRemoved(_)
4957 | InlayHintRefreshReason::ModifiersChanged(_)
4958 );
4959 let (invalidate_cache, required_languages) = match reason {
4960 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4961 match self.inlay_hint_cache.modifiers_override(enabled) {
4962 Some(enabled) => {
4963 if enabled {
4964 (InvalidationStrategy::RefreshRequested, None)
4965 } else {
4966 self.splice_inlays(
4967 &self
4968 .visible_inlay_hints(cx)
4969 .iter()
4970 .map(|inlay| inlay.id)
4971 .collect::<Vec<InlayId>>(),
4972 Vec::new(),
4973 cx,
4974 );
4975 return;
4976 }
4977 }
4978 None => return,
4979 }
4980 }
4981 InlayHintRefreshReason::Toggle(enabled) => {
4982 if self.inlay_hint_cache.toggle(enabled) {
4983 if enabled {
4984 (InvalidationStrategy::RefreshRequested, None)
4985 } else {
4986 self.splice_inlays(
4987 &self
4988 .visible_inlay_hints(cx)
4989 .iter()
4990 .map(|inlay| inlay.id)
4991 .collect::<Vec<InlayId>>(),
4992 Vec::new(),
4993 cx,
4994 );
4995 return;
4996 }
4997 } else {
4998 return;
4999 }
5000 }
5001 InlayHintRefreshReason::SettingsChange(new_settings) => {
5002 match self.inlay_hint_cache.update_settings(
5003 &self.buffer,
5004 new_settings,
5005 self.visible_inlay_hints(cx),
5006 cx,
5007 ) {
5008 ControlFlow::Break(Some(InlaySplice {
5009 to_remove,
5010 to_insert,
5011 })) => {
5012 self.splice_inlays(&to_remove, to_insert, cx);
5013 return;
5014 }
5015 ControlFlow::Break(None) => return,
5016 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5017 }
5018 }
5019 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5020 if let Some(InlaySplice {
5021 to_remove,
5022 to_insert,
5023 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5024 {
5025 self.splice_inlays(&to_remove, to_insert, cx);
5026 }
5027 self.display_map.update(cx, |display_map, _| {
5028 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5029 });
5030 return;
5031 }
5032 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5033 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5034 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5035 }
5036 InlayHintRefreshReason::RefreshRequested => {
5037 (InvalidationStrategy::RefreshRequested, None)
5038 }
5039 };
5040
5041 if let Some(InlaySplice {
5042 to_remove,
5043 to_insert,
5044 }) = self.inlay_hint_cache.spawn_hint_refresh(
5045 reason_description,
5046 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
5047 invalidate_cache,
5048 ignore_debounce,
5049 cx,
5050 ) {
5051 self.splice_inlays(&to_remove, to_insert, cx);
5052 }
5053 }
5054
5055 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5056 self.display_map
5057 .read(cx)
5058 .current_inlays()
5059 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5060 .cloned()
5061 .collect()
5062 }
5063
5064 pub fn excerpts_for_inlay_hints_query(
5065 &self,
5066 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5067 cx: &mut Context<Editor>,
5068 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5069 let Some(project) = self.project.as_ref() else {
5070 return HashMap::default();
5071 };
5072 let project = project.read(cx);
5073 let multi_buffer = self.buffer().read(cx);
5074 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5075 let multi_buffer_visible_start = self
5076 .scroll_manager
5077 .anchor()
5078 .anchor
5079 .to_point(&multi_buffer_snapshot);
5080 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5081 multi_buffer_visible_start
5082 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5083 Bias::Left,
5084 );
5085 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5086 multi_buffer_snapshot
5087 .range_to_buffer_ranges(multi_buffer_visible_range)
5088 .into_iter()
5089 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5090 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5091 let buffer_file = project::File::from_dyn(buffer.file())?;
5092 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5093 let worktree_entry = buffer_worktree
5094 .read(cx)
5095 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5096 if worktree_entry.is_ignored {
5097 return None;
5098 }
5099
5100 let language = buffer.language()?;
5101 if let Some(restrict_to_languages) = restrict_to_languages {
5102 if !restrict_to_languages.contains(language) {
5103 return None;
5104 }
5105 }
5106 Some((
5107 excerpt_id,
5108 (
5109 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5110 buffer.version().clone(),
5111 excerpt_visible_range,
5112 ),
5113 ))
5114 })
5115 .collect()
5116 }
5117
5118 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5119 TextLayoutDetails {
5120 text_system: window.text_system().clone(),
5121 editor_style: self.style.clone().unwrap(),
5122 rem_size: window.rem_size(),
5123 scroll_anchor: self.scroll_manager.anchor(),
5124 visible_rows: self.visible_line_count(),
5125 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5126 }
5127 }
5128
5129 pub fn splice_inlays(
5130 &self,
5131 to_remove: &[InlayId],
5132 to_insert: Vec<Inlay>,
5133 cx: &mut Context<Self>,
5134 ) {
5135 self.display_map.update(cx, |display_map, cx| {
5136 display_map.splice_inlays(to_remove, to_insert, cx)
5137 });
5138 cx.notify();
5139 }
5140
5141 fn trigger_on_type_formatting(
5142 &self,
5143 input: String,
5144 window: &mut Window,
5145 cx: &mut Context<Self>,
5146 ) -> Option<Task<Result<()>>> {
5147 if input.len() != 1 {
5148 return None;
5149 }
5150
5151 let project = self.project.as_ref()?;
5152 let position = self.selections.newest_anchor().head();
5153 let (buffer, buffer_position) = self
5154 .buffer
5155 .read(cx)
5156 .text_anchor_for_position(position, cx)?;
5157
5158 let settings = language_settings::language_settings(
5159 buffer
5160 .read(cx)
5161 .language_at(buffer_position)
5162 .map(|l| l.name()),
5163 buffer.read(cx).file(),
5164 cx,
5165 );
5166 if !settings.use_on_type_format {
5167 return None;
5168 }
5169
5170 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5171 // hence we do LSP request & edit on host side only — add formats to host's history.
5172 let push_to_lsp_host_history = true;
5173 // If this is not the host, append its history with new edits.
5174 let push_to_client_history = project.read(cx).is_via_collab();
5175
5176 let on_type_formatting = project.update(cx, |project, cx| {
5177 project.on_type_format(
5178 buffer.clone(),
5179 buffer_position,
5180 input,
5181 push_to_lsp_host_history,
5182 cx,
5183 )
5184 });
5185 Some(cx.spawn_in(window, async move |editor, cx| {
5186 if let Some(transaction) = on_type_formatting.await? {
5187 if push_to_client_history {
5188 buffer
5189 .update(cx, |buffer, _| {
5190 buffer.push_transaction(transaction, Instant::now());
5191 buffer.finalize_last_transaction();
5192 })
5193 .ok();
5194 }
5195 editor.update(cx, |editor, cx| {
5196 editor.refresh_document_highlights(cx);
5197 })?;
5198 }
5199 Ok(())
5200 }))
5201 }
5202
5203 pub fn show_word_completions(
5204 &mut self,
5205 _: &ShowWordCompletions,
5206 window: &mut Window,
5207 cx: &mut Context<Self>,
5208 ) {
5209 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5210 }
5211
5212 pub fn show_completions(
5213 &mut self,
5214 options: &ShowCompletions,
5215 window: &mut Window,
5216 cx: &mut Context<Self>,
5217 ) {
5218 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5219 }
5220
5221 fn open_or_update_completions_menu(
5222 &mut self,
5223 requested_source: Option<CompletionsMenuSource>,
5224 trigger: Option<&str>,
5225 window: &mut Window,
5226 cx: &mut Context<Self>,
5227 ) {
5228 if self.pending_rename.is_some() {
5229 return;
5230 }
5231
5232 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5233
5234 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5235 // inserted and selected. To handle that case, the start of the selection is used so that
5236 // the menu starts with all choices.
5237 let position = self
5238 .selections
5239 .newest_anchor()
5240 .start
5241 .bias_right(&multibuffer_snapshot);
5242 if position.diff_base_anchor.is_some() {
5243 return;
5244 }
5245 let (buffer, buffer_position) =
5246 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5247 output
5248 } else {
5249 return;
5250 };
5251 let buffer_snapshot = buffer.read(cx).snapshot();
5252
5253 let query: Option<Arc<String>> =
5254 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5255
5256 drop(multibuffer_snapshot);
5257
5258 let provider = match requested_source {
5259 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5260 Some(CompletionsMenuSource::Words) => None,
5261 Some(CompletionsMenuSource::SnippetChoices) => {
5262 log::error!("bug: SnippetChoices requested_source is not handled");
5263 None
5264 }
5265 };
5266
5267 let sort_completions = provider
5268 .as_ref()
5269 .map_or(false, |provider| provider.sort_completions());
5270
5271 let filter_completions = provider
5272 .as_ref()
5273 .map_or(true, |provider| provider.filter_completions());
5274
5275 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5276 if filter_completions {
5277 menu.filter(query.clone(), provider.clone(), window, cx);
5278 }
5279 // When `is_incomplete` is false, no need to re-query completions when the current query
5280 // is a suffix of the initial query.
5281 if !menu.is_incomplete {
5282 // If the new query is a suffix of the old query (typing more characters) and
5283 // the previous result was complete, the existing completions can be filtered.
5284 //
5285 // Note that this is always true for snippet completions.
5286 let query_matches = match (&menu.initial_query, &query) {
5287 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5288 (None, _) => true,
5289 _ => false,
5290 };
5291 if query_matches {
5292 let position_matches = if menu.initial_position == position {
5293 true
5294 } else {
5295 let snapshot = self.buffer.read(cx).read(cx);
5296 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5297 };
5298 if position_matches {
5299 return;
5300 }
5301 }
5302 }
5303 };
5304
5305 let trigger_kind = match trigger {
5306 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5307 CompletionTriggerKind::TRIGGER_CHARACTER
5308 }
5309 _ => CompletionTriggerKind::INVOKED,
5310 };
5311 let completion_context = CompletionContext {
5312 trigger_character: trigger.and_then(|trigger| {
5313 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5314 Some(String::from(trigger))
5315 } else {
5316 None
5317 }
5318 }),
5319 trigger_kind,
5320 };
5321
5322 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5323 buffer_snapshot.surrounding_word(buffer_position)
5324 {
5325 let word_to_exclude = buffer_snapshot
5326 .text_for_range(word_range.clone())
5327 .collect::<String>();
5328 (
5329 buffer_snapshot.anchor_before(word_range.start)
5330 ..buffer_snapshot.anchor_after(buffer_position),
5331 Some(word_to_exclude),
5332 )
5333 } else {
5334 (buffer_position..buffer_position, None)
5335 };
5336
5337 let language = buffer_snapshot
5338 .language_at(buffer_position)
5339 .map(|language| language.name());
5340
5341 let completion_settings =
5342 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5343
5344 let show_completion_documentation = buffer_snapshot
5345 .settings_at(buffer_position, cx)
5346 .show_completion_documentation;
5347
5348 // The document can be large, so stay in reasonable bounds when searching for words,
5349 // otherwise completion pop-up might be slow to appear.
5350 const WORD_LOOKUP_ROWS: u32 = 5_000;
5351 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5352 let min_word_search = buffer_snapshot.clip_point(
5353 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5354 Bias::Left,
5355 );
5356 let max_word_search = buffer_snapshot.clip_point(
5357 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5358 Bias::Right,
5359 );
5360 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5361 ..buffer_snapshot.point_to_offset(max_word_search);
5362
5363 let skip_digits = query
5364 .as_ref()
5365 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5366
5367 let (mut words, provider_responses) = match &provider {
5368 Some(provider) => {
5369 let provider_responses = provider.completions(
5370 position.excerpt_id,
5371 &buffer,
5372 buffer_position,
5373 completion_context,
5374 window,
5375 cx,
5376 );
5377
5378 let words = match completion_settings.words {
5379 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5380 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5381 .background_spawn(async move {
5382 buffer_snapshot.words_in_range(WordsQuery {
5383 fuzzy_contents: None,
5384 range: word_search_range,
5385 skip_digits,
5386 })
5387 }),
5388 };
5389
5390 (words, provider_responses)
5391 }
5392 None => (
5393 cx.background_spawn(async move {
5394 buffer_snapshot.words_in_range(WordsQuery {
5395 fuzzy_contents: None,
5396 range: word_search_range,
5397 skip_digits,
5398 })
5399 }),
5400 Task::ready(Ok(Vec::new())),
5401 ),
5402 };
5403
5404 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5405
5406 let id = post_inc(&mut self.next_completion_id);
5407 let task = cx.spawn_in(window, async move |editor, cx| {
5408 let Ok(()) = editor.update(cx, |this, _| {
5409 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5410 }) else {
5411 return;
5412 };
5413
5414 // TODO: Ideally completions from different sources would be selectively re-queried, so
5415 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5416 let mut completions = Vec::new();
5417 let mut is_incomplete = false;
5418 if let Some(provider_responses) = provider_responses.await.log_err() {
5419 if !provider_responses.is_empty() {
5420 for response in provider_responses {
5421 completions.extend(response.completions);
5422 is_incomplete = is_incomplete || response.is_incomplete;
5423 }
5424 if completion_settings.words == WordsCompletionMode::Fallback {
5425 words = Task::ready(BTreeMap::default());
5426 }
5427 }
5428 }
5429
5430 let mut words = words.await;
5431 if let Some(word_to_exclude) = &word_to_exclude {
5432 words.remove(word_to_exclude);
5433 }
5434 for lsp_completion in &completions {
5435 words.remove(&lsp_completion.new_text);
5436 }
5437 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5438 replace_range: word_replace_range.clone(),
5439 new_text: word.clone(),
5440 label: CodeLabel::plain(word, None),
5441 icon_path: None,
5442 documentation: None,
5443 source: CompletionSource::BufferWord {
5444 word_range,
5445 resolved: false,
5446 },
5447 insert_text_mode: Some(InsertTextMode::AS_IS),
5448 confirm: None,
5449 }));
5450
5451 let menu = if completions.is_empty() {
5452 None
5453 } else {
5454 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5455 let languages = editor
5456 .workspace
5457 .as_ref()
5458 .and_then(|(workspace, _)| workspace.upgrade())
5459 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5460 let menu = CompletionsMenu::new(
5461 id,
5462 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5463 sort_completions,
5464 show_completion_documentation,
5465 position,
5466 query.clone(),
5467 is_incomplete,
5468 buffer.clone(),
5469 completions.into(),
5470 snippet_sort_order,
5471 languages,
5472 language,
5473 cx,
5474 );
5475
5476 let query = if filter_completions { query } else { None };
5477 let matches_task = if let Some(query) = query {
5478 menu.do_async_filtering(query, cx)
5479 } else {
5480 Task::ready(menu.unfiltered_matches())
5481 };
5482 (menu, matches_task)
5483 }) else {
5484 return;
5485 };
5486
5487 let matches = matches_task.await;
5488
5489 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5490 // Newer menu already set, so exit.
5491 match editor.context_menu.borrow().as_ref() {
5492 Some(CodeContextMenu::Completions(prev_menu)) => {
5493 if prev_menu.id > id {
5494 return;
5495 }
5496 }
5497 _ => {}
5498 };
5499
5500 // Only valid to take prev_menu because it the new menu is immediately set
5501 // below, or the menu is hidden.
5502 match editor.context_menu.borrow_mut().take() {
5503 Some(CodeContextMenu::Completions(prev_menu)) => {
5504 let position_matches =
5505 if prev_menu.initial_position == menu.initial_position {
5506 true
5507 } else {
5508 let snapshot = editor.buffer.read(cx).read(cx);
5509 prev_menu.initial_position.to_offset(&snapshot)
5510 == menu.initial_position.to_offset(&snapshot)
5511 };
5512 if position_matches {
5513 // Preserve markdown cache before `set_filter_results` because it will
5514 // try to populate the documentation cache.
5515 menu.preserve_markdown_cache(prev_menu);
5516 }
5517 }
5518 _ => {}
5519 };
5520
5521 menu.set_filter_results(matches, provider, window, cx);
5522 }) else {
5523 return;
5524 };
5525
5526 menu.visible().then_some(menu)
5527 };
5528
5529 editor
5530 .update_in(cx, |editor, window, cx| {
5531 if editor.focus_handle.is_focused(window) {
5532 if let Some(menu) = menu {
5533 *editor.context_menu.borrow_mut() =
5534 Some(CodeContextMenu::Completions(menu));
5535
5536 crate::hover_popover::hide_hover(editor, cx);
5537 if editor.show_edit_predictions_in_menu() {
5538 editor.update_visible_inline_completion(window, cx);
5539 } else {
5540 editor.discard_inline_completion(false, cx);
5541 }
5542
5543 cx.notify();
5544 return;
5545 }
5546 }
5547
5548 if editor.completion_tasks.len() <= 1 {
5549 // If there are no more completion tasks and the last menu was empty, we should hide it.
5550 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5551 // If it was already hidden and we don't show inline completions in the menu, we should
5552 // also show the inline-completion when available.
5553 if was_hidden && editor.show_edit_predictions_in_menu() {
5554 editor.update_visible_inline_completion(window, cx);
5555 }
5556 }
5557 })
5558 .ok();
5559 });
5560
5561 self.completion_tasks.push((id, task));
5562 }
5563
5564 #[cfg(feature = "test-support")]
5565 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5566 let menu = self.context_menu.borrow();
5567 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5568 let completions = menu.completions.borrow();
5569 Some(completions.to_vec())
5570 } else {
5571 None
5572 }
5573 }
5574
5575 pub fn with_completions_menu_matching_id<R>(
5576 &self,
5577 id: CompletionId,
5578 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5579 ) -> R {
5580 let mut context_menu = self.context_menu.borrow_mut();
5581 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5582 return f(None);
5583 };
5584 if completions_menu.id != id {
5585 return f(None);
5586 }
5587 f(Some(completions_menu))
5588 }
5589
5590 pub fn confirm_completion(
5591 &mut self,
5592 action: &ConfirmCompletion,
5593 window: &mut Window,
5594 cx: &mut Context<Self>,
5595 ) -> Option<Task<Result<()>>> {
5596 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5597 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5598 }
5599
5600 pub fn confirm_completion_insert(
5601 &mut self,
5602 _: &ConfirmCompletionInsert,
5603 window: &mut Window,
5604 cx: &mut Context<Self>,
5605 ) -> Option<Task<Result<()>>> {
5606 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5607 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5608 }
5609
5610 pub fn confirm_completion_replace(
5611 &mut self,
5612 _: &ConfirmCompletionReplace,
5613 window: &mut Window,
5614 cx: &mut Context<Self>,
5615 ) -> Option<Task<Result<()>>> {
5616 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5617 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5618 }
5619
5620 pub fn compose_completion(
5621 &mut self,
5622 action: &ComposeCompletion,
5623 window: &mut Window,
5624 cx: &mut Context<Self>,
5625 ) -> Option<Task<Result<()>>> {
5626 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5627 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5628 }
5629
5630 fn do_completion(
5631 &mut self,
5632 item_ix: Option<usize>,
5633 intent: CompletionIntent,
5634 window: &mut Window,
5635 cx: &mut Context<Editor>,
5636 ) -> Option<Task<Result<()>>> {
5637 use language::ToOffset as _;
5638
5639 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5640 else {
5641 return None;
5642 };
5643
5644 let candidate_id = {
5645 let entries = completions_menu.entries.borrow();
5646 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5647 if self.show_edit_predictions_in_menu() {
5648 self.discard_inline_completion(true, cx);
5649 }
5650 mat.candidate_id
5651 };
5652
5653 let completion = completions_menu
5654 .completions
5655 .borrow()
5656 .get(candidate_id)?
5657 .clone();
5658 cx.stop_propagation();
5659
5660 let buffer_handle = completions_menu.buffer.clone();
5661
5662 let CompletionEdit {
5663 new_text,
5664 snippet,
5665 replace_range,
5666 } = process_completion_for_edit(
5667 &completion,
5668 intent,
5669 &buffer_handle,
5670 &completions_menu.initial_position.text_anchor,
5671 cx,
5672 );
5673
5674 let buffer = buffer_handle.read(cx);
5675 let snapshot = self.buffer.read(cx).snapshot(cx);
5676 let newest_anchor = self.selections.newest_anchor();
5677 let replace_range_multibuffer = {
5678 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5679 let multibuffer_anchor = snapshot
5680 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5681 .unwrap()
5682 ..snapshot
5683 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5684 .unwrap();
5685 multibuffer_anchor.start.to_offset(&snapshot)
5686 ..multibuffer_anchor.end.to_offset(&snapshot)
5687 };
5688 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5689 return None;
5690 }
5691
5692 let old_text = buffer
5693 .text_for_range(replace_range.clone())
5694 .collect::<String>();
5695 let lookbehind = newest_anchor
5696 .start
5697 .text_anchor
5698 .to_offset(buffer)
5699 .saturating_sub(replace_range.start);
5700 let lookahead = replace_range
5701 .end
5702 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5703 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5704 let suffix = &old_text[lookbehind.min(old_text.len())..];
5705
5706 let selections = self.selections.all::<usize>(cx);
5707 let mut ranges = Vec::new();
5708 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5709
5710 for selection in &selections {
5711 let range = if selection.id == newest_anchor.id {
5712 replace_range_multibuffer.clone()
5713 } else {
5714 let mut range = selection.range();
5715
5716 // if prefix is present, don't duplicate it
5717 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5718 range.start = range.start.saturating_sub(lookbehind);
5719
5720 // if suffix is also present, mimic the newest cursor and replace it
5721 if selection.id != newest_anchor.id
5722 && snapshot.contains_str_at(range.end, suffix)
5723 {
5724 range.end += lookahead;
5725 }
5726 }
5727 range
5728 };
5729
5730 ranges.push(range.clone());
5731
5732 if !self.linked_edit_ranges.is_empty() {
5733 let start_anchor = snapshot.anchor_before(range.start);
5734 let end_anchor = snapshot.anchor_after(range.end);
5735 if let Some(ranges) = self
5736 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5737 {
5738 for (buffer, edits) in ranges {
5739 linked_edits
5740 .entry(buffer.clone())
5741 .or_default()
5742 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5743 }
5744 }
5745 }
5746 }
5747
5748 let common_prefix_len = old_text
5749 .chars()
5750 .zip(new_text.chars())
5751 .take_while(|(a, b)| a == b)
5752 .map(|(a, _)| a.len_utf8())
5753 .sum::<usize>();
5754
5755 cx.emit(EditorEvent::InputHandled {
5756 utf16_range_to_replace: None,
5757 text: new_text[common_prefix_len..].into(),
5758 });
5759
5760 self.transact(window, cx, |this, window, cx| {
5761 if let Some(mut snippet) = snippet {
5762 snippet.text = new_text.to_string();
5763 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5764 } else {
5765 this.buffer.update(cx, |buffer, cx| {
5766 let auto_indent = match completion.insert_text_mode {
5767 Some(InsertTextMode::AS_IS) => None,
5768 _ => this.autoindent_mode.clone(),
5769 };
5770 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5771 buffer.edit(edits, auto_indent, cx);
5772 });
5773 }
5774 for (buffer, edits) in linked_edits {
5775 buffer.update(cx, |buffer, cx| {
5776 let snapshot = buffer.snapshot();
5777 let edits = edits
5778 .into_iter()
5779 .map(|(range, text)| {
5780 use text::ToPoint as TP;
5781 let end_point = TP::to_point(&range.end, &snapshot);
5782 let start_point = TP::to_point(&range.start, &snapshot);
5783 (start_point..end_point, text)
5784 })
5785 .sorted_by_key(|(range, _)| range.start);
5786 buffer.edit(edits, None, cx);
5787 })
5788 }
5789
5790 this.refresh_inline_completion(true, false, window, cx);
5791 });
5792
5793 let show_new_completions_on_confirm = completion
5794 .confirm
5795 .as_ref()
5796 .map_or(false, |confirm| confirm(intent, window, cx));
5797 if show_new_completions_on_confirm {
5798 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5799 }
5800
5801 let provider = self.completion_provider.as_ref()?;
5802 drop(completion);
5803 let apply_edits = provider.apply_additional_edits_for_completion(
5804 buffer_handle,
5805 completions_menu.completions.clone(),
5806 candidate_id,
5807 true,
5808 cx,
5809 );
5810
5811 let editor_settings = EditorSettings::get_global(cx);
5812 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5813 // After the code completion is finished, users often want to know what signatures are needed.
5814 // so we should automatically call signature_help
5815 self.show_signature_help(&ShowSignatureHelp, window, cx);
5816 }
5817
5818 Some(cx.foreground_executor().spawn(async move {
5819 apply_edits.await?;
5820 Ok(())
5821 }))
5822 }
5823
5824 pub fn toggle_code_actions(
5825 &mut self,
5826 action: &ToggleCodeActions,
5827 window: &mut Window,
5828 cx: &mut Context<Self>,
5829 ) {
5830 let quick_launch = action.quick_launch;
5831 let mut context_menu = self.context_menu.borrow_mut();
5832 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5833 if code_actions.deployed_from == action.deployed_from {
5834 // Toggle if we're selecting the same one
5835 *context_menu = None;
5836 cx.notify();
5837 return;
5838 } else {
5839 // Otherwise, clear it and start a new one
5840 *context_menu = None;
5841 cx.notify();
5842 }
5843 }
5844 drop(context_menu);
5845 let snapshot = self.snapshot(window, cx);
5846 let deployed_from = action.deployed_from.clone();
5847 let action = action.clone();
5848 self.completion_tasks.clear();
5849 self.discard_inline_completion(false, cx);
5850
5851 let multibuffer_point = match &action.deployed_from {
5852 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5853 DisplayPoint::new(*row, 0).to_point(&snapshot)
5854 }
5855 _ => self.selections.newest::<Point>(cx).head(),
5856 };
5857 let Some((buffer, buffer_row)) = snapshot
5858 .buffer_snapshot
5859 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5860 .and_then(|(buffer_snapshot, range)| {
5861 self.buffer()
5862 .read(cx)
5863 .buffer(buffer_snapshot.remote_id())
5864 .map(|buffer| (buffer, range.start.row))
5865 })
5866 else {
5867 return;
5868 };
5869 let buffer_id = buffer.read(cx).remote_id();
5870 let tasks = self
5871 .tasks
5872 .get(&(buffer_id, buffer_row))
5873 .map(|t| Arc::new(t.to_owned()));
5874
5875 if !self.focus_handle.is_focused(window) {
5876 return;
5877 }
5878 let project = self.project.clone();
5879
5880 let code_actions_task = match deployed_from {
5881 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
5882 _ => self.code_actions(buffer_row, window, cx),
5883 };
5884
5885 let runnable_task = match deployed_from {
5886 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
5887 _ => {
5888 let mut task_context_task = Task::ready(None);
5889 if let Some(tasks) = &tasks {
5890 if let Some(project) = project {
5891 task_context_task =
5892 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
5893 }
5894 }
5895
5896 cx.spawn_in(window, {
5897 let buffer = buffer.clone();
5898 async move |editor, cx| {
5899 let task_context = task_context_task.await;
5900
5901 let resolved_tasks =
5902 tasks
5903 .zip(task_context.clone())
5904 .map(|(tasks, task_context)| ResolvedTasks {
5905 templates: tasks.resolve(&task_context).collect(),
5906 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5907 multibuffer_point.row,
5908 tasks.column,
5909 )),
5910 });
5911 let debug_scenarios = editor
5912 .update(cx, |editor, cx| {
5913 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
5914 })?
5915 .await;
5916 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
5917 }
5918 })
5919 }
5920 };
5921
5922 cx.spawn_in(window, async move |editor, cx| {
5923 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
5924 let code_actions = code_actions_task.await;
5925 let spawn_straight_away = quick_launch
5926 && resolved_tasks
5927 .as_ref()
5928 .map_or(false, |tasks| tasks.templates.len() == 1)
5929 && code_actions
5930 .as_ref()
5931 .map_or(true, |actions| actions.is_empty())
5932 && debug_scenarios.is_empty();
5933
5934 editor.update_in(cx, |editor, window, cx| {
5935 crate::hover_popover::hide_hover(editor, cx);
5936 *editor.context_menu.borrow_mut() =
5937 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5938 buffer,
5939 actions: CodeActionContents::new(
5940 resolved_tasks,
5941 code_actions,
5942 debug_scenarios,
5943 task_context.unwrap_or_default(),
5944 ),
5945 selected_item: Default::default(),
5946 scroll_handle: UniformListScrollHandle::default(),
5947 deployed_from,
5948 }));
5949 cx.notify();
5950 if spawn_straight_away {
5951 if let Some(task) = editor.confirm_code_action(
5952 &ConfirmCodeAction { item_ix: Some(0) },
5953 window,
5954 cx,
5955 ) {
5956 return task;
5957 }
5958 }
5959
5960 Task::ready(Ok(()))
5961 })
5962 })
5963 .detach_and_log_err(cx);
5964 }
5965
5966 fn debug_scenarios(
5967 &mut self,
5968 resolved_tasks: &Option<ResolvedTasks>,
5969 buffer: &Entity<Buffer>,
5970 cx: &mut App,
5971 ) -> Task<Vec<task::DebugScenario>> {
5972 if cx.has_flag::<DebuggerFeatureFlag>() {
5973 maybe!({
5974 let project = self.project.as_ref()?;
5975 let dap_store = project.read(cx).dap_store();
5976 let mut scenarios = vec![];
5977 let resolved_tasks = resolved_tasks.as_ref()?;
5978 let buffer = buffer.read(cx);
5979 let language = buffer.language()?;
5980 let file = buffer.file();
5981 let debug_adapter = language_settings(language.name().into(), file, cx)
5982 .debuggers
5983 .first()
5984 .map(SharedString::from)
5985 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
5986
5987 dap_store.update(cx, |dap_store, cx| {
5988 for (_, task) in &resolved_tasks.templates {
5989 let maybe_scenario = dap_store.debug_scenario_for_build_task(
5990 task.original_task().clone(),
5991 debug_adapter.clone().into(),
5992 task.display_label().to_owned().into(),
5993 cx,
5994 );
5995 scenarios.push(maybe_scenario);
5996 }
5997 });
5998 Some(cx.background_spawn(async move {
5999 let scenarios = futures::future::join_all(scenarios)
6000 .await
6001 .into_iter()
6002 .flatten()
6003 .collect::<Vec<_>>();
6004 scenarios
6005 }))
6006 })
6007 .unwrap_or_else(|| Task::ready(vec![]))
6008 } else {
6009 Task::ready(vec![])
6010 }
6011 }
6012
6013 fn code_actions(
6014 &mut self,
6015 buffer_row: u32,
6016 window: &mut Window,
6017 cx: &mut Context<Self>,
6018 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6019 let mut task = self.code_actions_task.take();
6020 cx.spawn_in(window, async move |editor, cx| {
6021 while let Some(prev_task) = task {
6022 prev_task.await.log_err();
6023 task = editor
6024 .update(cx, |this, _| this.code_actions_task.take())
6025 .ok()?;
6026 }
6027
6028 editor
6029 .update(cx, |editor, cx| {
6030 editor
6031 .available_code_actions
6032 .clone()
6033 .and_then(|(location, code_actions)| {
6034 let snapshot = location.buffer.read(cx).snapshot();
6035 let point_range = location.range.to_point(&snapshot);
6036 let point_range = point_range.start.row..=point_range.end.row;
6037 if point_range.contains(&buffer_row) {
6038 Some(code_actions)
6039 } else {
6040 None
6041 }
6042 })
6043 })
6044 .ok()
6045 .flatten()
6046 })
6047 }
6048
6049 pub fn confirm_code_action(
6050 &mut self,
6051 action: &ConfirmCodeAction,
6052 window: &mut Window,
6053 cx: &mut Context<Self>,
6054 ) -> Option<Task<Result<()>>> {
6055 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6056
6057 let actions_menu =
6058 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6059 menu
6060 } else {
6061 return None;
6062 };
6063
6064 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6065 let action = actions_menu.actions.get(action_ix)?;
6066 let title = action.label();
6067 let buffer = actions_menu.buffer;
6068 let workspace = self.workspace()?;
6069
6070 match action {
6071 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6072 workspace.update(cx, |workspace, cx| {
6073 workspace.schedule_resolved_task(
6074 task_source_kind,
6075 resolved_task,
6076 false,
6077 window,
6078 cx,
6079 );
6080
6081 Some(Task::ready(Ok(())))
6082 })
6083 }
6084 CodeActionsItem::CodeAction {
6085 excerpt_id,
6086 action,
6087 provider,
6088 } => {
6089 let apply_code_action =
6090 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6091 let workspace = workspace.downgrade();
6092 Some(cx.spawn_in(window, async move |editor, cx| {
6093 let project_transaction = apply_code_action.await?;
6094 Self::open_project_transaction(
6095 &editor,
6096 workspace,
6097 project_transaction,
6098 title,
6099 cx,
6100 )
6101 .await
6102 }))
6103 }
6104 CodeActionsItem::DebugScenario(scenario) => {
6105 let context = actions_menu.actions.context.clone();
6106
6107 workspace.update(cx, |workspace, cx| {
6108 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6109 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
6110 });
6111 Some(Task::ready(Ok(())))
6112 }
6113 }
6114 }
6115
6116 pub async fn open_project_transaction(
6117 this: &WeakEntity<Editor>,
6118 workspace: WeakEntity<Workspace>,
6119 transaction: ProjectTransaction,
6120 title: String,
6121 cx: &mut AsyncWindowContext,
6122 ) -> Result<()> {
6123 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6124 cx.update(|_, cx| {
6125 entries.sort_unstable_by_key(|(buffer, _)| {
6126 buffer.read(cx).file().map(|f| f.path().clone())
6127 });
6128 })?;
6129
6130 // If the project transaction's edits are all contained within this editor, then
6131 // avoid opening a new editor to display them.
6132
6133 if let Some((buffer, transaction)) = entries.first() {
6134 if entries.len() == 1 {
6135 let excerpt = this.update(cx, |editor, cx| {
6136 editor
6137 .buffer()
6138 .read(cx)
6139 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6140 })?;
6141 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6142 if excerpted_buffer == *buffer {
6143 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6144 let excerpt_range = excerpt_range.to_offset(buffer);
6145 buffer
6146 .edited_ranges_for_transaction::<usize>(transaction)
6147 .all(|range| {
6148 excerpt_range.start <= range.start
6149 && excerpt_range.end >= range.end
6150 })
6151 })?;
6152
6153 if all_edits_within_excerpt {
6154 return Ok(());
6155 }
6156 }
6157 }
6158 }
6159 } else {
6160 return Ok(());
6161 }
6162
6163 let mut ranges_to_highlight = Vec::new();
6164 let excerpt_buffer = cx.new(|cx| {
6165 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6166 for (buffer_handle, transaction) in &entries {
6167 let edited_ranges = buffer_handle
6168 .read(cx)
6169 .edited_ranges_for_transaction::<Point>(transaction)
6170 .collect::<Vec<_>>();
6171 let (ranges, _) = multibuffer.set_excerpts_for_path(
6172 PathKey::for_buffer(buffer_handle, cx),
6173 buffer_handle.clone(),
6174 edited_ranges,
6175 DEFAULT_MULTIBUFFER_CONTEXT,
6176 cx,
6177 );
6178
6179 ranges_to_highlight.extend(ranges);
6180 }
6181 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6182 multibuffer
6183 })?;
6184
6185 workspace.update_in(cx, |workspace, window, cx| {
6186 let project = workspace.project().clone();
6187 let editor =
6188 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6189 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6190 editor.update(cx, |editor, cx| {
6191 editor.highlight_background::<Self>(
6192 &ranges_to_highlight,
6193 |theme| theme.colors().editor_highlighted_line_background,
6194 cx,
6195 );
6196 });
6197 })?;
6198
6199 Ok(())
6200 }
6201
6202 pub fn clear_code_action_providers(&mut self) {
6203 self.code_action_providers.clear();
6204 self.available_code_actions.take();
6205 }
6206
6207 pub fn add_code_action_provider(
6208 &mut self,
6209 provider: Rc<dyn CodeActionProvider>,
6210 window: &mut Window,
6211 cx: &mut Context<Self>,
6212 ) {
6213 if self
6214 .code_action_providers
6215 .iter()
6216 .any(|existing_provider| existing_provider.id() == provider.id())
6217 {
6218 return;
6219 }
6220
6221 self.code_action_providers.push(provider);
6222 self.refresh_code_actions(window, cx);
6223 }
6224
6225 pub fn remove_code_action_provider(
6226 &mut self,
6227 id: Arc<str>,
6228 window: &mut Window,
6229 cx: &mut Context<Self>,
6230 ) {
6231 self.code_action_providers
6232 .retain(|provider| provider.id() != id);
6233 self.refresh_code_actions(window, cx);
6234 }
6235
6236 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6237 !self.code_action_providers.is_empty()
6238 && EditorSettings::get_global(cx).toolbar.code_actions
6239 }
6240
6241 pub fn has_available_code_actions(&self) -> bool {
6242 self.available_code_actions
6243 .as_ref()
6244 .is_some_and(|(_, actions)| !actions.is_empty())
6245 }
6246
6247 fn render_inline_code_actions(
6248 &self,
6249 icon_size: ui::IconSize,
6250 display_row: DisplayRow,
6251 is_active: bool,
6252 cx: &mut Context<Self>,
6253 ) -> AnyElement {
6254 let show_tooltip = !self.context_menu_visible();
6255 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6256 .icon_size(icon_size)
6257 .shape(ui::IconButtonShape::Square)
6258 .style(ButtonStyle::Transparent)
6259 .icon_color(ui::Color::Hidden)
6260 .toggle_state(is_active)
6261 .when(show_tooltip, |this| {
6262 this.tooltip({
6263 let focus_handle = self.focus_handle.clone();
6264 move |window, cx| {
6265 Tooltip::for_action_in(
6266 "Toggle Code Actions",
6267 &ToggleCodeActions {
6268 deployed_from: None,
6269 quick_launch: false,
6270 },
6271 &focus_handle,
6272 window,
6273 cx,
6274 )
6275 }
6276 })
6277 })
6278 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6279 window.focus(&editor.focus_handle(cx));
6280 editor.toggle_code_actions(
6281 &crate::actions::ToggleCodeActions {
6282 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6283 display_row,
6284 )),
6285 quick_launch: false,
6286 },
6287 window,
6288 cx,
6289 );
6290 }))
6291 .into_any_element()
6292 }
6293
6294 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6295 &self.context_menu
6296 }
6297
6298 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6299 let newest_selection = self.selections.newest_anchor().clone();
6300 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6301 let buffer = self.buffer.read(cx);
6302 if newest_selection.head().diff_base_anchor.is_some() {
6303 return None;
6304 }
6305 let (start_buffer, start) =
6306 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6307 let (end_buffer, end) =
6308 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6309 if start_buffer != end_buffer {
6310 return None;
6311 }
6312
6313 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6314 cx.background_executor()
6315 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6316 .await;
6317
6318 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6319 let providers = this.code_action_providers.clone();
6320 let tasks = this
6321 .code_action_providers
6322 .iter()
6323 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6324 .collect::<Vec<_>>();
6325 (providers, tasks)
6326 })?;
6327
6328 let mut actions = Vec::new();
6329 for (provider, provider_actions) in
6330 providers.into_iter().zip(future::join_all(tasks).await)
6331 {
6332 if let Some(provider_actions) = provider_actions.log_err() {
6333 actions.extend(provider_actions.into_iter().map(|action| {
6334 AvailableCodeAction {
6335 excerpt_id: newest_selection.start.excerpt_id,
6336 action,
6337 provider: provider.clone(),
6338 }
6339 }));
6340 }
6341 }
6342
6343 this.update(cx, |this, cx| {
6344 this.available_code_actions = if actions.is_empty() {
6345 None
6346 } else {
6347 Some((
6348 Location {
6349 buffer: start_buffer,
6350 range: start..end,
6351 },
6352 actions.into(),
6353 ))
6354 };
6355 cx.notify();
6356 })
6357 }));
6358 None
6359 }
6360
6361 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6362 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6363 self.show_git_blame_inline = false;
6364
6365 self.show_git_blame_inline_delay_task =
6366 Some(cx.spawn_in(window, async move |this, cx| {
6367 cx.background_executor().timer(delay).await;
6368
6369 this.update(cx, |this, cx| {
6370 this.show_git_blame_inline = true;
6371 cx.notify();
6372 })
6373 .log_err();
6374 }));
6375 }
6376 }
6377
6378 fn show_blame_popover(
6379 &mut self,
6380 blame_entry: &BlameEntry,
6381 position: gpui::Point<Pixels>,
6382 cx: &mut Context<Self>,
6383 ) {
6384 if let Some(state) = &mut self.inline_blame_popover {
6385 state.hide_task.take();
6386 } else {
6387 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6388 let blame_entry = blame_entry.clone();
6389 let show_task = cx.spawn(async move |editor, cx| {
6390 cx.background_executor()
6391 .timer(std::time::Duration::from_millis(delay))
6392 .await;
6393 editor
6394 .update(cx, |editor, cx| {
6395 editor.inline_blame_popover_show_task.take();
6396 let Some(blame) = editor.blame.as_ref() else {
6397 return;
6398 };
6399 let blame = blame.read(cx);
6400 let details = blame.details_for_entry(&blame_entry);
6401 let markdown = cx.new(|cx| {
6402 Markdown::new(
6403 details
6404 .as_ref()
6405 .map(|message| message.message.clone())
6406 .unwrap_or_default(),
6407 None,
6408 None,
6409 cx,
6410 )
6411 });
6412 editor.inline_blame_popover = Some(InlineBlamePopover {
6413 position,
6414 hide_task: None,
6415 popover_bounds: None,
6416 popover_state: InlineBlamePopoverState {
6417 scroll_handle: ScrollHandle::new(),
6418 commit_message: details,
6419 markdown,
6420 },
6421 });
6422 cx.notify();
6423 })
6424 .ok();
6425 });
6426 self.inline_blame_popover_show_task = Some(show_task);
6427 }
6428 }
6429
6430 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6431 self.inline_blame_popover_show_task.take();
6432 if let Some(state) = &mut self.inline_blame_popover {
6433 let hide_task = cx.spawn(async move |editor, cx| {
6434 cx.background_executor()
6435 .timer(std::time::Duration::from_millis(100))
6436 .await;
6437 editor
6438 .update(cx, |editor, cx| {
6439 editor.inline_blame_popover.take();
6440 cx.notify();
6441 })
6442 .ok();
6443 });
6444 state.hide_task = Some(hide_task);
6445 }
6446 }
6447
6448 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6449 if self.pending_rename.is_some() {
6450 return None;
6451 }
6452
6453 let provider = self.semantics_provider.clone()?;
6454 let buffer = self.buffer.read(cx);
6455 let newest_selection = self.selections.newest_anchor().clone();
6456 let cursor_position = newest_selection.head();
6457 let (cursor_buffer, cursor_buffer_position) =
6458 buffer.text_anchor_for_position(cursor_position, cx)?;
6459 let (tail_buffer, tail_buffer_position) =
6460 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6461 if cursor_buffer != tail_buffer {
6462 return None;
6463 }
6464
6465 let snapshot = cursor_buffer.read(cx).snapshot();
6466 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6467 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6468 if start_word_range != end_word_range {
6469 self.document_highlights_task.take();
6470 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6471 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6472 return None;
6473 }
6474
6475 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6476 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6477 cx.background_executor()
6478 .timer(Duration::from_millis(debounce))
6479 .await;
6480
6481 let highlights = if let Some(highlights) = cx
6482 .update(|cx| {
6483 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6484 })
6485 .ok()
6486 .flatten()
6487 {
6488 highlights.await.log_err()
6489 } else {
6490 None
6491 };
6492
6493 if let Some(highlights) = highlights {
6494 this.update(cx, |this, cx| {
6495 if this.pending_rename.is_some() {
6496 return;
6497 }
6498
6499 let buffer_id = cursor_position.buffer_id;
6500 let buffer = this.buffer.read(cx);
6501 if !buffer
6502 .text_anchor_for_position(cursor_position, cx)
6503 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6504 {
6505 return;
6506 }
6507
6508 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6509 let mut write_ranges = Vec::new();
6510 let mut read_ranges = Vec::new();
6511 for highlight in highlights {
6512 for (excerpt_id, excerpt_range) in
6513 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6514 {
6515 let start = highlight
6516 .range
6517 .start
6518 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6519 let end = highlight
6520 .range
6521 .end
6522 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6523 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6524 continue;
6525 }
6526
6527 let range = Anchor {
6528 buffer_id,
6529 excerpt_id,
6530 text_anchor: start,
6531 diff_base_anchor: None,
6532 }..Anchor {
6533 buffer_id,
6534 excerpt_id,
6535 text_anchor: end,
6536 diff_base_anchor: None,
6537 };
6538 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6539 write_ranges.push(range);
6540 } else {
6541 read_ranges.push(range);
6542 }
6543 }
6544 }
6545
6546 this.highlight_background::<DocumentHighlightRead>(
6547 &read_ranges,
6548 |theme| theme.colors().editor_document_highlight_read_background,
6549 cx,
6550 );
6551 this.highlight_background::<DocumentHighlightWrite>(
6552 &write_ranges,
6553 |theme| theme.colors().editor_document_highlight_write_background,
6554 cx,
6555 );
6556 cx.notify();
6557 })
6558 .log_err();
6559 }
6560 }));
6561 None
6562 }
6563
6564 fn prepare_highlight_query_from_selection(
6565 &mut self,
6566 cx: &mut Context<Editor>,
6567 ) -> Option<(String, Range<Anchor>)> {
6568 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6569 return None;
6570 }
6571 if !EditorSettings::get_global(cx).selection_highlight {
6572 return None;
6573 }
6574 if self.selections.count() != 1 || self.selections.line_mode {
6575 return None;
6576 }
6577 let selection = self.selections.newest::<Point>(cx);
6578 if selection.is_empty() || selection.start.row != selection.end.row {
6579 return None;
6580 }
6581 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6582 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6583 let query = multi_buffer_snapshot
6584 .text_for_range(selection_anchor_range.clone())
6585 .collect::<String>();
6586 if query.trim().is_empty() {
6587 return None;
6588 }
6589 Some((query, selection_anchor_range))
6590 }
6591
6592 fn update_selection_occurrence_highlights(
6593 &mut self,
6594 query_text: String,
6595 query_range: Range<Anchor>,
6596 multi_buffer_range_to_query: Range<Point>,
6597 use_debounce: bool,
6598 window: &mut Window,
6599 cx: &mut Context<Editor>,
6600 ) -> Task<()> {
6601 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6602 cx.spawn_in(window, async move |editor, cx| {
6603 if use_debounce {
6604 cx.background_executor()
6605 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6606 .await;
6607 }
6608 let match_task = cx.background_spawn(async move {
6609 let buffer_ranges = multi_buffer_snapshot
6610 .range_to_buffer_ranges(multi_buffer_range_to_query)
6611 .into_iter()
6612 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6613 let mut match_ranges = Vec::new();
6614 let Ok(regex) = project::search::SearchQuery::text(
6615 query_text.clone(),
6616 false,
6617 false,
6618 false,
6619 Default::default(),
6620 Default::default(),
6621 false,
6622 None,
6623 ) else {
6624 return Vec::default();
6625 };
6626 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6627 match_ranges.extend(
6628 regex
6629 .search(&buffer_snapshot, Some(search_range.clone()))
6630 .await
6631 .into_iter()
6632 .filter_map(|match_range| {
6633 let match_start = buffer_snapshot
6634 .anchor_after(search_range.start + match_range.start);
6635 let match_end = buffer_snapshot
6636 .anchor_before(search_range.start + match_range.end);
6637 let match_anchor_range = Anchor::range_in_buffer(
6638 excerpt_id,
6639 buffer_snapshot.remote_id(),
6640 match_start..match_end,
6641 );
6642 (match_anchor_range != query_range).then_some(match_anchor_range)
6643 }),
6644 );
6645 }
6646 match_ranges
6647 });
6648 let match_ranges = match_task.await;
6649 editor
6650 .update_in(cx, |editor, _, cx| {
6651 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6652 if !match_ranges.is_empty() {
6653 editor.highlight_background::<SelectedTextHighlight>(
6654 &match_ranges,
6655 |theme| theme.colors().editor_document_highlight_bracket_background,
6656 cx,
6657 )
6658 }
6659 })
6660 .log_err();
6661 })
6662 }
6663
6664 fn refresh_selected_text_highlights(
6665 &mut self,
6666 on_buffer_edit: bool,
6667 window: &mut Window,
6668 cx: &mut Context<Editor>,
6669 ) {
6670 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6671 else {
6672 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6673 self.quick_selection_highlight_task.take();
6674 self.debounced_selection_highlight_task.take();
6675 return;
6676 };
6677 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6678 if on_buffer_edit
6679 || self
6680 .quick_selection_highlight_task
6681 .as_ref()
6682 .map_or(true, |(prev_anchor_range, _)| {
6683 prev_anchor_range != &query_range
6684 })
6685 {
6686 let multi_buffer_visible_start = self
6687 .scroll_manager
6688 .anchor()
6689 .anchor
6690 .to_point(&multi_buffer_snapshot);
6691 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6692 multi_buffer_visible_start
6693 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6694 Bias::Left,
6695 );
6696 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6697 self.quick_selection_highlight_task = Some((
6698 query_range.clone(),
6699 self.update_selection_occurrence_highlights(
6700 query_text.clone(),
6701 query_range.clone(),
6702 multi_buffer_visible_range,
6703 false,
6704 window,
6705 cx,
6706 ),
6707 ));
6708 }
6709 if on_buffer_edit
6710 || self
6711 .debounced_selection_highlight_task
6712 .as_ref()
6713 .map_or(true, |(prev_anchor_range, _)| {
6714 prev_anchor_range != &query_range
6715 })
6716 {
6717 let multi_buffer_start = multi_buffer_snapshot
6718 .anchor_before(0)
6719 .to_point(&multi_buffer_snapshot);
6720 let multi_buffer_end = multi_buffer_snapshot
6721 .anchor_after(multi_buffer_snapshot.len())
6722 .to_point(&multi_buffer_snapshot);
6723 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6724 self.debounced_selection_highlight_task = Some((
6725 query_range.clone(),
6726 self.update_selection_occurrence_highlights(
6727 query_text,
6728 query_range,
6729 multi_buffer_full_range,
6730 true,
6731 window,
6732 cx,
6733 ),
6734 ));
6735 }
6736 }
6737
6738 pub fn refresh_inline_completion(
6739 &mut self,
6740 debounce: bool,
6741 user_requested: bool,
6742 window: &mut Window,
6743 cx: &mut Context<Self>,
6744 ) -> Option<()> {
6745 let provider = self.edit_prediction_provider()?;
6746 let cursor = self.selections.newest_anchor().head();
6747 let (buffer, cursor_buffer_position) =
6748 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6749
6750 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6751 self.discard_inline_completion(false, cx);
6752 return None;
6753 }
6754
6755 if !user_requested
6756 && (!self.should_show_edit_predictions()
6757 || !self.is_focused(window)
6758 || buffer.read(cx).is_empty())
6759 {
6760 self.discard_inline_completion(false, cx);
6761 return None;
6762 }
6763
6764 self.update_visible_inline_completion(window, cx);
6765 provider.refresh(
6766 self.project.clone(),
6767 buffer,
6768 cursor_buffer_position,
6769 debounce,
6770 cx,
6771 );
6772 Some(())
6773 }
6774
6775 fn show_edit_predictions_in_menu(&self) -> bool {
6776 match self.edit_prediction_settings {
6777 EditPredictionSettings::Disabled => false,
6778 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6779 }
6780 }
6781
6782 pub fn edit_predictions_enabled(&self) -> bool {
6783 match self.edit_prediction_settings {
6784 EditPredictionSettings::Disabled => false,
6785 EditPredictionSettings::Enabled { .. } => true,
6786 }
6787 }
6788
6789 fn edit_prediction_requires_modifier(&self) -> bool {
6790 match self.edit_prediction_settings {
6791 EditPredictionSettings::Disabled => false,
6792 EditPredictionSettings::Enabled {
6793 preview_requires_modifier,
6794 ..
6795 } => preview_requires_modifier,
6796 }
6797 }
6798
6799 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6800 if self.edit_prediction_provider.is_none() {
6801 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6802 } else {
6803 let selection = self.selections.newest_anchor();
6804 let cursor = selection.head();
6805
6806 if let Some((buffer, cursor_buffer_position)) =
6807 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6808 {
6809 self.edit_prediction_settings =
6810 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6811 }
6812 }
6813 }
6814
6815 fn edit_prediction_settings_at_position(
6816 &self,
6817 buffer: &Entity<Buffer>,
6818 buffer_position: language::Anchor,
6819 cx: &App,
6820 ) -> EditPredictionSettings {
6821 if !self.mode.is_full()
6822 || !self.show_inline_completions_override.unwrap_or(true)
6823 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6824 {
6825 return EditPredictionSettings::Disabled;
6826 }
6827
6828 let buffer = buffer.read(cx);
6829
6830 let file = buffer.file();
6831
6832 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6833 return EditPredictionSettings::Disabled;
6834 };
6835
6836 let by_provider = matches!(
6837 self.menu_inline_completions_policy,
6838 MenuInlineCompletionsPolicy::ByProvider
6839 );
6840
6841 let show_in_menu = by_provider
6842 && self
6843 .edit_prediction_provider
6844 .as_ref()
6845 .map_or(false, |provider| {
6846 provider.provider.show_completions_in_menu()
6847 });
6848
6849 let preview_requires_modifier =
6850 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6851
6852 EditPredictionSettings::Enabled {
6853 show_in_menu,
6854 preview_requires_modifier,
6855 }
6856 }
6857
6858 fn should_show_edit_predictions(&self) -> bool {
6859 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6860 }
6861
6862 pub fn edit_prediction_preview_is_active(&self) -> bool {
6863 matches!(
6864 self.edit_prediction_preview,
6865 EditPredictionPreview::Active { .. }
6866 )
6867 }
6868
6869 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6870 let cursor = self.selections.newest_anchor().head();
6871 if let Some((buffer, cursor_position)) =
6872 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6873 {
6874 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6875 } else {
6876 false
6877 }
6878 }
6879
6880 pub fn supports_minimap(&self, cx: &App) -> bool {
6881 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6882 }
6883
6884 fn edit_predictions_enabled_in_buffer(
6885 &self,
6886 buffer: &Entity<Buffer>,
6887 buffer_position: language::Anchor,
6888 cx: &App,
6889 ) -> bool {
6890 maybe!({
6891 if self.read_only(cx) {
6892 return Some(false);
6893 }
6894 let provider = self.edit_prediction_provider()?;
6895 if !provider.is_enabled(&buffer, buffer_position, cx) {
6896 return Some(false);
6897 }
6898 let buffer = buffer.read(cx);
6899 let Some(file) = buffer.file() else {
6900 return Some(true);
6901 };
6902 let settings = all_language_settings(Some(file), cx);
6903 Some(settings.edit_predictions_enabled_for_file(file, cx))
6904 })
6905 .unwrap_or(false)
6906 }
6907
6908 fn cycle_inline_completion(
6909 &mut self,
6910 direction: Direction,
6911 window: &mut Window,
6912 cx: &mut Context<Self>,
6913 ) -> Option<()> {
6914 let provider = self.edit_prediction_provider()?;
6915 let cursor = self.selections.newest_anchor().head();
6916 let (buffer, cursor_buffer_position) =
6917 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6918 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6919 return None;
6920 }
6921
6922 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6923 self.update_visible_inline_completion(window, cx);
6924
6925 Some(())
6926 }
6927
6928 pub fn show_inline_completion(
6929 &mut self,
6930 _: &ShowEditPrediction,
6931 window: &mut Window,
6932 cx: &mut Context<Self>,
6933 ) {
6934 if !self.has_active_inline_completion() {
6935 self.refresh_inline_completion(false, true, window, cx);
6936 return;
6937 }
6938
6939 self.update_visible_inline_completion(window, cx);
6940 }
6941
6942 pub fn display_cursor_names(
6943 &mut self,
6944 _: &DisplayCursorNames,
6945 window: &mut Window,
6946 cx: &mut Context<Self>,
6947 ) {
6948 self.show_cursor_names(window, cx);
6949 }
6950
6951 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6952 self.show_cursor_names = true;
6953 cx.notify();
6954 cx.spawn_in(window, async move |this, cx| {
6955 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6956 this.update(cx, |this, cx| {
6957 this.show_cursor_names = false;
6958 cx.notify()
6959 })
6960 .ok()
6961 })
6962 .detach();
6963 }
6964
6965 pub fn next_edit_prediction(
6966 &mut self,
6967 _: &NextEditPrediction,
6968 window: &mut Window,
6969 cx: &mut Context<Self>,
6970 ) {
6971 if self.has_active_inline_completion() {
6972 self.cycle_inline_completion(Direction::Next, window, cx);
6973 } else {
6974 let is_copilot_disabled = self
6975 .refresh_inline_completion(false, true, window, cx)
6976 .is_none();
6977 if is_copilot_disabled {
6978 cx.propagate();
6979 }
6980 }
6981 }
6982
6983 pub fn previous_edit_prediction(
6984 &mut self,
6985 _: &PreviousEditPrediction,
6986 window: &mut Window,
6987 cx: &mut Context<Self>,
6988 ) {
6989 if self.has_active_inline_completion() {
6990 self.cycle_inline_completion(Direction::Prev, window, cx);
6991 } else {
6992 let is_copilot_disabled = self
6993 .refresh_inline_completion(false, true, window, cx)
6994 .is_none();
6995 if is_copilot_disabled {
6996 cx.propagate();
6997 }
6998 }
6999 }
7000
7001 pub fn accept_edit_prediction(
7002 &mut self,
7003 _: &AcceptEditPrediction,
7004 window: &mut Window,
7005 cx: &mut Context<Self>,
7006 ) {
7007 if self.show_edit_predictions_in_menu() {
7008 self.hide_context_menu(window, cx);
7009 }
7010
7011 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7012 return;
7013 };
7014
7015 self.report_inline_completion_event(
7016 active_inline_completion.completion_id.clone(),
7017 true,
7018 cx,
7019 );
7020
7021 match &active_inline_completion.completion {
7022 InlineCompletion::Move { target, .. } => {
7023 let target = *target;
7024
7025 if let Some(position_map) = &self.last_position_map {
7026 if position_map
7027 .visible_row_range
7028 .contains(&target.to_display_point(&position_map.snapshot).row())
7029 || !self.edit_prediction_requires_modifier()
7030 {
7031 self.unfold_ranges(&[target..target], true, false, cx);
7032 // Note that this is also done in vim's handler of the Tab action.
7033 self.change_selections(
7034 Some(Autoscroll::newest()),
7035 window,
7036 cx,
7037 |selections| {
7038 selections.select_anchor_ranges([target..target]);
7039 },
7040 );
7041 self.clear_row_highlights::<EditPredictionPreview>();
7042
7043 self.edit_prediction_preview
7044 .set_previous_scroll_position(None);
7045 } else {
7046 self.edit_prediction_preview
7047 .set_previous_scroll_position(Some(
7048 position_map.snapshot.scroll_anchor,
7049 ));
7050
7051 self.highlight_rows::<EditPredictionPreview>(
7052 target..target,
7053 cx.theme().colors().editor_highlighted_line_background,
7054 RowHighlightOptions {
7055 autoscroll: true,
7056 ..Default::default()
7057 },
7058 cx,
7059 );
7060 self.request_autoscroll(Autoscroll::fit(), cx);
7061 }
7062 }
7063 }
7064 InlineCompletion::Edit { edits, .. } => {
7065 if let Some(provider) = self.edit_prediction_provider() {
7066 provider.accept(cx);
7067 }
7068
7069 // Store the transaction ID and selections before applying the edit
7070 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7071
7072 let snapshot = self.buffer.read(cx).snapshot(cx);
7073 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7074
7075 self.buffer.update(cx, |buffer, cx| {
7076 buffer.edit(edits.iter().cloned(), None, cx)
7077 });
7078
7079 self.change_selections(None, window, cx, |s| {
7080 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7081 });
7082
7083 let selections = self.selections.disjoint_anchors();
7084 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7085 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7086 if has_new_transaction {
7087 self.selection_history
7088 .insert_transaction(transaction_id_now, selections);
7089 }
7090 }
7091
7092 self.update_visible_inline_completion(window, cx);
7093 if self.active_inline_completion.is_none() {
7094 self.refresh_inline_completion(true, true, window, cx);
7095 }
7096
7097 cx.notify();
7098 }
7099 }
7100
7101 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7102 }
7103
7104 pub fn accept_partial_inline_completion(
7105 &mut self,
7106 _: &AcceptPartialEditPrediction,
7107 window: &mut Window,
7108 cx: &mut Context<Self>,
7109 ) {
7110 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7111 return;
7112 };
7113 if self.selections.count() != 1 {
7114 return;
7115 }
7116
7117 self.report_inline_completion_event(
7118 active_inline_completion.completion_id.clone(),
7119 true,
7120 cx,
7121 );
7122
7123 match &active_inline_completion.completion {
7124 InlineCompletion::Move { target, .. } => {
7125 let target = *target;
7126 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
7127 selections.select_anchor_ranges([target..target]);
7128 });
7129 }
7130 InlineCompletion::Edit { edits, .. } => {
7131 // Find an insertion that starts at the cursor position.
7132 let snapshot = self.buffer.read(cx).snapshot(cx);
7133 let cursor_offset = self.selections.newest::<usize>(cx).head();
7134 let insertion = edits.iter().find_map(|(range, text)| {
7135 let range = range.to_offset(&snapshot);
7136 if range.is_empty() && range.start == cursor_offset {
7137 Some(text)
7138 } else {
7139 None
7140 }
7141 });
7142
7143 if let Some(text) = insertion {
7144 let mut partial_completion = text
7145 .chars()
7146 .by_ref()
7147 .take_while(|c| c.is_alphabetic())
7148 .collect::<String>();
7149 if partial_completion.is_empty() {
7150 partial_completion = text
7151 .chars()
7152 .by_ref()
7153 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7154 .collect::<String>();
7155 }
7156
7157 cx.emit(EditorEvent::InputHandled {
7158 utf16_range_to_replace: None,
7159 text: partial_completion.clone().into(),
7160 });
7161
7162 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7163
7164 self.refresh_inline_completion(true, true, window, cx);
7165 cx.notify();
7166 } else {
7167 self.accept_edit_prediction(&Default::default(), window, cx);
7168 }
7169 }
7170 }
7171 }
7172
7173 fn discard_inline_completion(
7174 &mut self,
7175 should_report_inline_completion_event: bool,
7176 cx: &mut Context<Self>,
7177 ) -> bool {
7178 if should_report_inline_completion_event {
7179 let completion_id = self
7180 .active_inline_completion
7181 .as_ref()
7182 .and_then(|active_completion| active_completion.completion_id.clone());
7183
7184 self.report_inline_completion_event(completion_id, false, cx);
7185 }
7186
7187 if let Some(provider) = self.edit_prediction_provider() {
7188 provider.discard(cx);
7189 }
7190
7191 self.take_active_inline_completion(cx)
7192 }
7193
7194 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7195 let Some(provider) = self.edit_prediction_provider() else {
7196 return;
7197 };
7198
7199 let Some((_, buffer, _)) = self
7200 .buffer
7201 .read(cx)
7202 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7203 else {
7204 return;
7205 };
7206
7207 let extension = buffer
7208 .read(cx)
7209 .file()
7210 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7211
7212 let event_type = match accepted {
7213 true => "Edit Prediction Accepted",
7214 false => "Edit Prediction Discarded",
7215 };
7216 telemetry::event!(
7217 event_type,
7218 provider = provider.name(),
7219 prediction_id = id,
7220 suggestion_accepted = accepted,
7221 file_extension = extension,
7222 );
7223 }
7224
7225 pub fn has_active_inline_completion(&self) -> bool {
7226 self.active_inline_completion.is_some()
7227 }
7228
7229 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7230 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7231 return false;
7232 };
7233
7234 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7235 self.clear_highlights::<InlineCompletionHighlight>(cx);
7236 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7237 true
7238 }
7239
7240 /// Returns true when we're displaying the edit prediction popover below the cursor
7241 /// like we are not previewing and the LSP autocomplete menu is visible
7242 /// or we are in `when_holding_modifier` mode.
7243 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7244 if self.edit_prediction_preview_is_active()
7245 || !self.show_edit_predictions_in_menu()
7246 || !self.edit_predictions_enabled()
7247 {
7248 return false;
7249 }
7250
7251 if self.has_visible_completions_menu() {
7252 return true;
7253 }
7254
7255 has_completion && self.edit_prediction_requires_modifier()
7256 }
7257
7258 fn handle_modifiers_changed(
7259 &mut self,
7260 modifiers: Modifiers,
7261 position_map: &PositionMap,
7262 window: &mut Window,
7263 cx: &mut Context<Self>,
7264 ) {
7265 if self.show_edit_predictions_in_menu() {
7266 self.update_edit_prediction_preview(&modifiers, window, cx);
7267 }
7268
7269 self.update_selection_mode(&modifiers, position_map, window, cx);
7270
7271 let mouse_position = window.mouse_position();
7272 if !position_map.text_hitbox.is_hovered(window) {
7273 return;
7274 }
7275
7276 self.update_hovered_link(
7277 position_map.point_for_position(mouse_position),
7278 &position_map.snapshot,
7279 modifiers,
7280 window,
7281 cx,
7282 )
7283 }
7284
7285 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7286 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7287 if invert {
7288 match multi_cursor_setting {
7289 MultiCursorModifier::Alt => modifiers.alt,
7290 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7291 }
7292 } else {
7293 match multi_cursor_setting {
7294 MultiCursorModifier::Alt => modifiers.secondary(),
7295 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7296 }
7297 }
7298 }
7299
7300 fn columnar_selection_mode(
7301 modifiers: &Modifiers,
7302 cx: &mut Context<Self>,
7303 ) -> Option<ColumnarMode> {
7304 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7305 if Self::multi_cursor_modifier(false, modifiers, cx) {
7306 Some(ColumnarMode::FromMouse)
7307 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7308 Some(ColumnarMode::FromSelection)
7309 } else {
7310 None
7311 }
7312 } else {
7313 None
7314 }
7315 }
7316
7317 fn update_selection_mode(
7318 &mut self,
7319 modifiers: &Modifiers,
7320 position_map: &PositionMap,
7321 window: &mut Window,
7322 cx: &mut Context<Self>,
7323 ) {
7324 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7325 return;
7326 };
7327 if self.selections.pending.is_none() {
7328 return;
7329 }
7330
7331 let mouse_position = window.mouse_position();
7332 let point_for_position = position_map.point_for_position(mouse_position);
7333 let position = point_for_position.previous_valid;
7334
7335 self.select(
7336 SelectPhase::BeginColumnar {
7337 position,
7338 reset: false,
7339 mode,
7340 goal_column: point_for_position.exact_unclipped.column(),
7341 },
7342 window,
7343 cx,
7344 );
7345 }
7346
7347 fn update_edit_prediction_preview(
7348 &mut self,
7349 modifiers: &Modifiers,
7350 window: &mut Window,
7351 cx: &mut Context<Self>,
7352 ) {
7353 let mut modifiers_held = false;
7354 if let Some(accept_keystroke) = self
7355 .accept_edit_prediction_keybind(false, window, cx)
7356 .keystroke()
7357 {
7358 modifiers_held = modifiers_held
7359 || (&accept_keystroke.modifiers == modifiers
7360 && accept_keystroke.modifiers.modified());
7361 };
7362 if let Some(accept_partial_keystroke) = self
7363 .accept_edit_prediction_keybind(true, window, cx)
7364 .keystroke()
7365 {
7366 modifiers_held = modifiers_held
7367 || (&accept_partial_keystroke.modifiers == modifiers
7368 && accept_partial_keystroke.modifiers.modified());
7369 }
7370
7371 if modifiers_held {
7372 if matches!(
7373 self.edit_prediction_preview,
7374 EditPredictionPreview::Inactive { .. }
7375 ) {
7376 self.edit_prediction_preview = EditPredictionPreview::Active {
7377 previous_scroll_position: None,
7378 since: Instant::now(),
7379 };
7380
7381 self.update_visible_inline_completion(window, cx);
7382 cx.notify();
7383 }
7384 } else if let EditPredictionPreview::Active {
7385 previous_scroll_position,
7386 since,
7387 } = self.edit_prediction_preview
7388 {
7389 if let (Some(previous_scroll_position), Some(position_map)) =
7390 (previous_scroll_position, self.last_position_map.as_ref())
7391 {
7392 self.set_scroll_position(
7393 previous_scroll_position
7394 .scroll_position(&position_map.snapshot.display_snapshot),
7395 window,
7396 cx,
7397 );
7398 }
7399
7400 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7401 released_too_fast: since.elapsed() < Duration::from_millis(200),
7402 };
7403 self.clear_row_highlights::<EditPredictionPreview>();
7404 self.update_visible_inline_completion(window, cx);
7405 cx.notify();
7406 }
7407 }
7408
7409 fn update_visible_inline_completion(
7410 &mut self,
7411 _window: &mut Window,
7412 cx: &mut Context<Self>,
7413 ) -> Option<()> {
7414 let selection = self.selections.newest_anchor();
7415 let cursor = selection.head();
7416 let multibuffer = self.buffer.read(cx).snapshot(cx);
7417 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7418 let excerpt_id = cursor.excerpt_id;
7419
7420 let show_in_menu = self.show_edit_predictions_in_menu();
7421 let completions_menu_has_precedence = !show_in_menu
7422 && (self.context_menu.borrow().is_some()
7423 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7424
7425 if completions_menu_has_precedence
7426 || !offset_selection.is_empty()
7427 || self
7428 .active_inline_completion
7429 .as_ref()
7430 .map_or(false, |completion| {
7431 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7432 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7433 !invalidation_range.contains(&offset_selection.head())
7434 })
7435 {
7436 self.discard_inline_completion(false, cx);
7437 return None;
7438 }
7439
7440 self.take_active_inline_completion(cx);
7441 let Some(provider) = self.edit_prediction_provider() else {
7442 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7443 return None;
7444 };
7445
7446 let (buffer, cursor_buffer_position) =
7447 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7448
7449 self.edit_prediction_settings =
7450 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7451
7452 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7453
7454 if self.edit_prediction_indent_conflict {
7455 let cursor_point = cursor.to_point(&multibuffer);
7456
7457 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7458
7459 if let Some((_, indent)) = indents.iter().next() {
7460 if indent.len == cursor_point.column {
7461 self.edit_prediction_indent_conflict = false;
7462 }
7463 }
7464 }
7465
7466 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7467 let edits = inline_completion
7468 .edits
7469 .into_iter()
7470 .flat_map(|(range, new_text)| {
7471 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7472 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7473 Some((start..end, new_text))
7474 })
7475 .collect::<Vec<_>>();
7476 if edits.is_empty() {
7477 return None;
7478 }
7479
7480 let first_edit_start = edits.first().unwrap().0.start;
7481 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7482 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7483
7484 let last_edit_end = edits.last().unwrap().0.end;
7485 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7486 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7487
7488 let cursor_row = cursor.to_point(&multibuffer).row;
7489
7490 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7491
7492 let mut inlay_ids = Vec::new();
7493 let invalidation_row_range;
7494 let move_invalidation_row_range = if cursor_row < edit_start_row {
7495 Some(cursor_row..edit_end_row)
7496 } else if cursor_row > edit_end_row {
7497 Some(edit_start_row..cursor_row)
7498 } else {
7499 None
7500 };
7501 let is_move =
7502 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7503 let completion = if is_move {
7504 invalidation_row_range =
7505 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7506 let target = first_edit_start;
7507 InlineCompletion::Move { target, snapshot }
7508 } else {
7509 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7510 && !self.inline_completions_hidden_for_vim_mode;
7511
7512 if show_completions_in_buffer {
7513 if edits
7514 .iter()
7515 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7516 {
7517 let mut inlays = Vec::new();
7518 for (range, new_text) in &edits {
7519 let inlay = Inlay::inline_completion(
7520 post_inc(&mut self.next_inlay_id),
7521 range.start,
7522 new_text.as_str(),
7523 );
7524 inlay_ids.push(inlay.id);
7525 inlays.push(inlay);
7526 }
7527
7528 self.splice_inlays(&[], inlays, cx);
7529 } else {
7530 let background_color = cx.theme().status().deleted_background;
7531 let style = HighlightStyle {
7532 background_color: Some(background_color),
7533 ..Default::default()
7534 };
7535 self.highlight_text::<InlineCompletionHighlight>(
7536 edits
7537 .iter()
7538 .map(|(range, _)| (range.clone(), style))
7539 .collect(),
7540 cx,
7541 );
7542 }
7543 }
7544
7545 invalidation_row_range = edit_start_row..edit_end_row;
7546
7547 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7548 if provider.show_tab_accept_marker() {
7549 EditDisplayMode::TabAccept
7550 } else {
7551 EditDisplayMode::Inline
7552 }
7553 } else {
7554 EditDisplayMode::DiffPopover
7555 };
7556
7557 InlineCompletion::Edit {
7558 edits,
7559 edit_preview: inline_completion.edit_preview,
7560 display_mode,
7561 snapshot,
7562 }
7563 };
7564
7565 let invalidation_range = multibuffer
7566 .anchor_before(Point::new(invalidation_row_range.start, 0))
7567 ..multibuffer.anchor_after(Point::new(
7568 invalidation_row_range.end,
7569 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7570 ));
7571
7572 self.stale_inline_completion_in_menu = None;
7573 self.active_inline_completion = Some(InlineCompletionState {
7574 inlay_ids,
7575 completion,
7576 completion_id: inline_completion.id,
7577 invalidation_range,
7578 });
7579
7580 cx.notify();
7581
7582 Some(())
7583 }
7584
7585 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7586 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7587 }
7588
7589 fn clear_tasks(&mut self) {
7590 self.tasks.clear()
7591 }
7592
7593 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7594 if self.tasks.insert(key, value).is_some() {
7595 // This case should hopefully be rare, but just in case...
7596 log::error!(
7597 "multiple different run targets found on a single line, only the last target will be rendered"
7598 )
7599 }
7600 }
7601
7602 /// Get all display points of breakpoints that will be rendered within editor
7603 ///
7604 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7605 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7606 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7607 fn active_breakpoints(
7608 &self,
7609 range: Range<DisplayRow>,
7610 window: &mut Window,
7611 cx: &mut Context<Self>,
7612 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7613 let mut breakpoint_display_points = HashMap::default();
7614
7615 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7616 return breakpoint_display_points;
7617 };
7618
7619 let snapshot = self.snapshot(window, cx);
7620
7621 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7622 let Some(project) = self.project.as_ref() else {
7623 return breakpoint_display_points;
7624 };
7625
7626 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7627 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7628
7629 for (buffer_snapshot, range, excerpt_id) in
7630 multi_buffer_snapshot.range_to_buffer_ranges(range)
7631 {
7632 let Some(buffer) = project
7633 .read(cx)
7634 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7635 else {
7636 continue;
7637 };
7638 let breakpoints = breakpoint_store.read(cx).breakpoints(
7639 &buffer,
7640 Some(
7641 buffer_snapshot.anchor_before(range.start)
7642 ..buffer_snapshot.anchor_after(range.end),
7643 ),
7644 buffer_snapshot,
7645 cx,
7646 );
7647 for (breakpoint, state) in breakpoints {
7648 let multi_buffer_anchor =
7649 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7650 let position = multi_buffer_anchor
7651 .to_point(&multi_buffer_snapshot)
7652 .to_display_point(&snapshot);
7653
7654 breakpoint_display_points.insert(
7655 position.row(),
7656 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7657 );
7658 }
7659 }
7660
7661 breakpoint_display_points
7662 }
7663
7664 fn breakpoint_context_menu(
7665 &self,
7666 anchor: Anchor,
7667 window: &mut Window,
7668 cx: &mut Context<Self>,
7669 ) -> Entity<ui::ContextMenu> {
7670 let weak_editor = cx.weak_entity();
7671 let focus_handle = self.focus_handle(cx);
7672
7673 let row = self
7674 .buffer
7675 .read(cx)
7676 .snapshot(cx)
7677 .summary_for_anchor::<Point>(&anchor)
7678 .row;
7679
7680 let breakpoint = self
7681 .breakpoint_at_row(row, window, cx)
7682 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7683
7684 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7685 "Edit Log Breakpoint"
7686 } else {
7687 "Set Log Breakpoint"
7688 };
7689
7690 let condition_breakpoint_msg = if breakpoint
7691 .as_ref()
7692 .is_some_and(|bp| bp.1.condition.is_some())
7693 {
7694 "Edit Condition Breakpoint"
7695 } else {
7696 "Set Condition Breakpoint"
7697 };
7698
7699 let hit_condition_breakpoint_msg = if breakpoint
7700 .as_ref()
7701 .is_some_and(|bp| bp.1.hit_condition.is_some())
7702 {
7703 "Edit Hit Condition Breakpoint"
7704 } else {
7705 "Set Hit Condition Breakpoint"
7706 };
7707
7708 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7709 "Unset Breakpoint"
7710 } else {
7711 "Set Breakpoint"
7712 };
7713
7714 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7715
7716 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7717 BreakpointState::Enabled => Some("Disable"),
7718 BreakpointState::Disabled => Some("Enable"),
7719 });
7720
7721 let (anchor, breakpoint) =
7722 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7723
7724 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7725 menu.on_blur_subscription(Subscription::new(|| {}))
7726 .context(focus_handle)
7727 .when(run_to_cursor, |this| {
7728 let weak_editor = weak_editor.clone();
7729 this.entry("Run to cursor", None, move |window, cx| {
7730 weak_editor
7731 .update(cx, |editor, cx| {
7732 editor.change_selections(None, window, cx, |s| {
7733 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7734 });
7735 })
7736 .ok();
7737
7738 window.dispatch_action(Box::new(RunToCursor), cx);
7739 })
7740 .separator()
7741 })
7742 .when_some(toggle_state_msg, |this, msg| {
7743 this.entry(msg, None, {
7744 let weak_editor = weak_editor.clone();
7745 let breakpoint = breakpoint.clone();
7746 move |_window, cx| {
7747 weak_editor
7748 .update(cx, |this, cx| {
7749 this.edit_breakpoint_at_anchor(
7750 anchor,
7751 breakpoint.as_ref().clone(),
7752 BreakpointEditAction::InvertState,
7753 cx,
7754 );
7755 })
7756 .log_err();
7757 }
7758 })
7759 })
7760 .entry(set_breakpoint_msg, None, {
7761 let weak_editor = weak_editor.clone();
7762 let breakpoint = breakpoint.clone();
7763 move |_window, cx| {
7764 weak_editor
7765 .update(cx, |this, cx| {
7766 this.edit_breakpoint_at_anchor(
7767 anchor,
7768 breakpoint.as_ref().clone(),
7769 BreakpointEditAction::Toggle,
7770 cx,
7771 );
7772 })
7773 .log_err();
7774 }
7775 })
7776 .entry(log_breakpoint_msg, None, {
7777 let breakpoint = breakpoint.clone();
7778 let weak_editor = weak_editor.clone();
7779 move |window, cx| {
7780 weak_editor
7781 .update(cx, |this, cx| {
7782 this.add_edit_breakpoint_block(
7783 anchor,
7784 breakpoint.as_ref(),
7785 BreakpointPromptEditAction::Log,
7786 window,
7787 cx,
7788 );
7789 })
7790 .log_err();
7791 }
7792 })
7793 .entry(condition_breakpoint_msg, None, {
7794 let breakpoint = breakpoint.clone();
7795 let weak_editor = weak_editor.clone();
7796 move |window, cx| {
7797 weak_editor
7798 .update(cx, |this, cx| {
7799 this.add_edit_breakpoint_block(
7800 anchor,
7801 breakpoint.as_ref(),
7802 BreakpointPromptEditAction::Condition,
7803 window,
7804 cx,
7805 );
7806 })
7807 .log_err();
7808 }
7809 })
7810 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7811 weak_editor
7812 .update(cx, |this, cx| {
7813 this.add_edit_breakpoint_block(
7814 anchor,
7815 breakpoint.as_ref(),
7816 BreakpointPromptEditAction::HitCondition,
7817 window,
7818 cx,
7819 );
7820 })
7821 .log_err();
7822 })
7823 })
7824 }
7825
7826 fn render_breakpoint(
7827 &self,
7828 position: Anchor,
7829 row: DisplayRow,
7830 breakpoint: &Breakpoint,
7831 state: Option<BreakpointSessionState>,
7832 cx: &mut Context<Self>,
7833 ) -> IconButton {
7834 let is_rejected = state.is_some_and(|s| !s.verified);
7835 // Is it a breakpoint that shows up when hovering over gutter?
7836 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7837 (false, false),
7838 |PhantomBreakpointIndicator {
7839 is_active,
7840 display_row,
7841 collides_with_existing_breakpoint,
7842 }| {
7843 (
7844 is_active && display_row == row,
7845 collides_with_existing_breakpoint,
7846 )
7847 },
7848 );
7849
7850 let (color, icon) = {
7851 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7852 (false, false) => ui::IconName::DebugBreakpoint,
7853 (true, false) => ui::IconName::DebugLogBreakpoint,
7854 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7855 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7856 };
7857
7858 let color = if is_phantom {
7859 Color::Hint
7860 } else if is_rejected {
7861 Color::Disabled
7862 } else {
7863 Color::Debugger
7864 };
7865
7866 (color, icon)
7867 };
7868
7869 let breakpoint = Arc::from(breakpoint.clone());
7870
7871 let alt_as_text = gpui::Keystroke {
7872 modifiers: Modifiers::secondary_key(),
7873 ..Default::default()
7874 };
7875 let primary_action_text = if breakpoint.is_disabled() {
7876 "Enable breakpoint"
7877 } else if is_phantom && !collides_with_existing {
7878 "Set breakpoint"
7879 } else {
7880 "Unset breakpoint"
7881 };
7882 let focus_handle = self.focus_handle.clone();
7883
7884 let meta = if is_rejected {
7885 SharedString::from("No executable code is associated with this line.")
7886 } else if collides_with_existing && !breakpoint.is_disabled() {
7887 SharedString::from(format!(
7888 "{alt_as_text}-click to disable,\nright-click for more options."
7889 ))
7890 } else {
7891 SharedString::from("Right-click for more options.")
7892 };
7893 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7894 .icon_size(IconSize::XSmall)
7895 .size(ui::ButtonSize::None)
7896 .when(is_rejected, |this| {
7897 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7898 })
7899 .icon_color(color)
7900 .style(ButtonStyle::Transparent)
7901 .on_click(cx.listener({
7902 let breakpoint = breakpoint.clone();
7903
7904 move |editor, event: &ClickEvent, window, cx| {
7905 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7906 BreakpointEditAction::InvertState
7907 } else {
7908 BreakpointEditAction::Toggle
7909 };
7910
7911 window.focus(&editor.focus_handle(cx));
7912 editor.edit_breakpoint_at_anchor(
7913 position,
7914 breakpoint.as_ref().clone(),
7915 edit_action,
7916 cx,
7917 );
7918 }
7919 }))
7920 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7921 editor.set_breakpoint_context_menu(
7922 row,
7923 Some(position),
7924 event.down.position,
7925 window,
7926 cx,
7927 );
7928 }))
7929 .tooltip(move |window, cx| {
7930 Tooltip::with_meta_in(
7931 primary_action_text,
7932 Some(&ToggleBreakpoint),
7933 meta.clone(),
7934 &focus_handle,
7935 window,
7936 cx,
7937 )
7938 })
7939 }
7940
7941 fn build_tasks_context(
7942 project: &Entity<Project>,
7943 buffer: &Entity<Buffer>,
7944 buffer_row: u32,
7945 tasks: &Arc<RunnableTasks>,
7946 cx: &mut Context<Self>,
7947 ) -> Task<Option<task::TaskContext>> {
7948 let position = Point::new(buffer_row, tasks.column);
7949 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7950 let location = Location {
7951 buffer: buffer.clone(),
7952 range: range_start..range_start,
7953 };
7954 // Fill in the environmental variables from the tree-sitter captures
7955 let mut captured_task_variables = TaskVariables::default();
7956 for (capture_name, value) in tasks.extra_variables.clone() {
7957 captured_task_variables.insert(
7958 task::VariableName::Custom(capture_name.into()),
7959 value.clone(),
7960 );
7961 }
7962 project.update(cx, |project, cx| {
7963 project.task_store().update(cx, |task_store, cx| {
7964 task_store.task_context_for_location(captured_task_variables, location, cx)
7965 })
7966 })
7967 }
7968
7969 pub fn spawn_nearest_task(
7970 &mut self,
7971 action: &SpawnNearestTask,
7972 window: &mut Window,
7973 cx: &mut Context<Self>,
7974 ) {
7975 let Some((workspace, _)) = self.workspace.clone() else {
7976 return;
7977 };
7978 let Some(project) = self.project.clone() else {
7979 return;
7980 };
7981
7982 // Try to find a closest, enclosing node using tree-sitter that has a
7983 // task
7984 let Some((buffer, buffer_row, tasks)) = self
7985 .find_enclosing_node_task(cx)
7986 // Or find the task that's closest in row-distance.
7987 .or_else(|| self.find_closest_task(cx))
7988 else {
7989 return;
7990 };
7991
7992 let reveal_strategy = action.reveal;
7993 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7994 cx.spawn_in(window, async move |_, cx| {
7995 let context = task_context.await?;
7996 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7997
7998 let resolved = &mut resolved_task.resolved;
7999 resolved.reveal = reveal_strategy;
8000
8001 workspace
8002 .update_in(cx, |workspace, window, cx| {
8003 workspace.schedule_resolved_task(
8004 task_source_kind,
8005 resolved_task,
8006 false,
8007 window,
8008 cx,
8009 );
8010 })
8011 .ok()
8012 })
8013 .detach();
8014 }
8015
8016 fn find_closest_task(
8017 &mut self,
8018 cx: &mut Context<Self>,
8019 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8020 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8021
8022 let ((buffer_id, row), tasks) = self
8023 .tasks
8024 .iter()
8025 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8026
8027 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8028 let tasks = Arc::new(tasks.to_owned());
8029 Some((buffer, *row, tasks))
8030 }
8031
8032 fn find_enclosing_node_task(
8033 &mut self,
8034 cx: &mut Context<Self>,
8035 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8036 let snapshot = self.buffer.read(cx).snapshot(cx);
8037 let offset = self.selections.newest::<usize>(cx).head();
8038 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8039 let buffer_id = excerpt.buffer().remote_id();
8040
8041 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8042 let mut cursor = layer.node().walk();
8043
8044 while cursor.goto_first_child_for_byte(offset).is_some() {
8045 if cursor.node().end_byte() == offset {
8046 cursor.goto_next_sibling();
8047 }
8048 }
8049
8050 // Ascend to the smallest ancestor that contains the range and has a task.
8051 loop {
8052 let node = cursor.node();
8053 let node_range = node.byte_range();
8054 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8055
8056 // Check if this node contains our offset
8057 if node_range.start <= offset && node_range.end >= offset {
8058 // If it contains offset, check for task
8059 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8060 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8061 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8062 }
8063 }
8064
8065 if !cursor.goto_parent() {
8066 break;
8067 }
8068 }
8069 None
8070 }
8071
8072 fn render_run_indicator(
8073 &self,
8074 _style: &EditorStyle,
8075 is_active: bool,
8076 row: DisplayRow,
8077 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8078 cx: &mut Context<Self>,
8079 ) -> IconButton {
8080 let color = Color::Muted;
8081 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8082
8083 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
8084 .shape(ui::IconButtonShape::Square)
8085 .icon_size(IconSize::XSmall)
8086 .icon_color(color)
8087 .toggle_state(is_active)
8088 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8089 let quick_launch = e.down.button == MouseButton::Left;
8090 window.focus(&editor.focus_handle(cx));
8091 editor.toggle_code_actions(
8092 &ToggleCodeActions {
8093 deployed_from: Some(CodeActionSource::RunMenu(row)),
8094 quick_launch,
8095 },
8096 window,
8097 cx,
8098 );
8099 }))
8100 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8101 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
8102 }))
8103 }
8104
8105 pub fn context_menu_visible(&self) -> bool {
8106 !self.edit_prediction_preview_is_active()
8107 && self
8108 .context_menu
8109 .borrow()
8110 .as_ref()
8111 .map_or(false, |menu| menu.visible())
8112 }
8113
8114 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8115 self.context_menu
8116 .borrow()
8117 .as_ref()
8118 .map(|menu| menu.origin())
8119 }
8120
8121 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8122 self.context_menu_options = Some(options);
8123 }
8124
8125 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8126 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8127
8128 fn render_edit_prediction_popover(
8129 &mut self,
8130 text_bounds: &Bounds<Pixels>,
8131 content_origin: gpui::Point<Pixels>,
8132 right_margin: Pixels,
8133 editor_snapshot: &EditorSnapshot,
8134 visible_row_range: Range<DisplayRow>,
8135 scroll_top: f32,
8136 scroll_bottom: f32,
8137 line_layouts: &[LineWithInvisibles],
8138 line_height: Pixels,
8139 scroll_pixel_position: gpui::Point<Pixels>,
8140 newest_selection_head: Option<DisplayPoint>,
8141 editor_width: Pixels,
8142 style: &EditorStyle,
8143 window: &mut Window,
8144 cx: &mut App,
8145 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8146 if self.mode().is_minimap() {
8147 return None;
8148 }
8149 let active_inline_completion = self.active_inline_completion.as_ref()?;
8150
8151 if self.edit_prediction_visible_in_cursor_popover(true) {
8152 return None;
8153 }
8154
8155 match &active_inline_completion.completion {
8156 InlineCompletion::Move { target, .. } => {
8157 let target_display_point = target.to_display_point(editor_snapshot);
8158
8159 if self.edit_prediction_requires_modifier() {
8160 if !self.edit_prediction_preview_is_active() {
8161 return None;
8162 }
8163
8164 self.render_edit_prediction_modifier_jump_popover(
8165 text_bounds,
8166 content_origin,
8167 visible_row_range,
8168 line_layouts,
8169 line_height,
8170 scroll_pixel_position,
8171 newest_selection_head,
8172 target_display_point,
8173 window,
8174 cx,
8175 )
8176 } else {
8177 self.render_edit_prediction_eager_jump_popover(
8178 text_bounds,
8179 content_origin,
8180 editor_snapshot,
8181 visible_row_range,
8182 scroll_top,
8183 scroll_bottom,
8184 line_height,
8185 scroll_pixel_position,
8186 target_display_point,
8187 editor_width,
8188 window,
8189 cx,
8190 )
8191 }
8192 }
8193 InlineCompletion::Edit {
8194 display_mode: EditDisplayMode::Inline,
8195 ..
8196 } => None,
8197 InlineCompletion::Edit {
8198 display_mode: EditDisplayMode::TabAccept,
8199 edits,
8200 ..
8201 } => {
8202 let range = &edits.first()?.0;
8203 let target_display_point = range.end.to_display_point(editor_snapshot);
8204
8205 self.render_edit_prediction_end_of_line_popover(
8206 "Accept",
8207 editor_snapshot,
8208 visible_row_range,
8209 target_display_point,
8210 line_height,
8211 scroll_pixel_position,
8212 content_origin,
8213 editor_width,
8214 window,
8215 cx,
8216 )
8217 }
8218 InlineCompletion::Edit {
8219 edits,
8220 edit_preview,
8221 display_mode: EditDisplayMode::DiffPopover,
8222 snapshot,
8223 } => self.render_edit_prediction_diff_popover(
8224 text_bounds,
8225 content_origin,
8226 right_margin,
8227 editor_snapshot,
8228 visible_row_range,
8229 line_layouts,
8230 line_height,
8231 scroll_pixel_position,
8232 newest_selection_head,
8233 editor_width,
8234 style,
8235 edits,
8236 edit_preview,
8237 snapshot,
8238 window,
8239 cx,
8240 ),
8241 }
8242 }
8243
8244 fn render_edit_prediction_modifier_jump_popover(
8245 &mut self,
8246 text_bounds: &Bounds<Pixels>,
8247 content_origin: gpui::Point<Pixels>,
8248 visible_row_range: Range<DisplayRow>,
8249 line_layouts: &[LineWithInvisibles],
8250 line_height: Pixels,
8251 scroll_pixel_position: gpui::Point<Pixels>,
8252 newest_selection_head: Option<DisplayPoint>,
8253 target_display_point: DisplayPoint,
8254 window: &mut Window,
8255 cx: &mut App,
8256 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8257 let scrolled_content_origin =
8258 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8259
8260 const SCROLL_PADDING_Y: Pixels = px(12.);
8261
8262 if target_display_point.row() < visible_row_range.start {
8263 return self.render_edit_prediction_scroll_popover(
8264 |_| SCROLL_PADDING_Y,
8265 IconName::ArrowUp,
8266 visible_row_range,
8267 line_layouts,
8268 newest_selection_head,
8269 scrolled_content_origin,
8270 window,
8271 cx,
8272 );
8273 } else if target_display_point.row() >= visible_row_range.end {
8274 return self.render_edit_prediction_scroll_popover(
8275 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8276 IconName::ArrowDown,
8277 visible_row_range,
8278 line_layouts,
8279 newest_selection_head,
8280 scrolled_content_origin,
8281 window,
8282 cx,
8283 );
8284 }
8285
8286 const POLE_WIDTH: Pixels = px(2.);
8287
8288 let line_layout =
8289 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8290 let target_column = target_display_point.column() as usize;
8291
8292 let target_x = line_layout.x_for_index(target_column);
8293 let target_y =
8294 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8295
8296 let flag_on_right = target_x < text_bounds.size.width / 2.;
8297
8298 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8299 border_color.l += 0.001;
8300
8301 let mut element = v_flex()
8302 .items_end()
8303 .when(flag_on_right, |el| el.items_start())
8304 .child(if flag_on_right {
8305 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8306 .rounded_bl(px(0.))
8307 .rounded_tl(px(0.))
8308 .border_l_2()
8309 .border_color(border_color)
8310 } else {
8311 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8312 .rounded_br(px(0.))
8313 .rounded_tr(px(0.))
8314 .border_r_2()
8315 .border_color(border_color)
8316 })
8317 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8318 .into_any();
8319
8320 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8321
8322 let mut origin = scrolled_content_origin + point(target_x, target_y)
8323 - point(
8324 if flag_on_right {
8325 POLE_WIDTH
8326 } else {
8327 size.width - POLE_WIDTH
8328 },
8329 size.height - line_height,
8330 );
8331
8332 origin.x = origin.x.max(content_origin.x);
8333
8334 element.prepaint_at(origin, window, cx);
8335
8336 Some((element, origin))
8337 }
8338
8339 fn render_edit_prediction_scroll_popover(
8340 &mut self,
8341 to_y: impl Fn(Size<Pixels>) -> Pixels,
8342 scroll_icon: IconName,
8343 visible_row_range: Range<DisplayRow>,
8344 line_layouts: &[LineWithInvisibles],
8345 newest_selection_head: Option<DisplayPoint>,
8346 scrolled_content_origin: gpui::Point<Pixels>,
8347 window: &mut Window,
8348 cx: &mut App,
8349 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8350 let mut element = self
8351 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8352 .into_any();
8353
8354 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8355
8356 let cursor = newest_selection_head?;
8357 let cursor_row_layout =
8358 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8359 let cursor_column = cursor.column() as usize;
8360
8361 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8362
8363 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8364
8365 element.prepaint_at(origin, window, cx);
8366 Some((element, origin))
8367 }
8368
8369 fn render_edit_prediction_eager_jump_popover(
8370 &mut self,
8371 text_bounds: &Bounds<Pixels>,
8372 content_origin: gpui::Point<Pixels>,
8373 editor_snapshot: &EditorSnapshot,
8374 visible_row_range: Range<DisplayRow>,
8375 scroll_top: f32,
8376 scroll_bottom: f32,
8377 line_height: Pixels,
8378 scroll_pixel_position: gpui::Point<Pixels>,
8379 target_display_point: DisplayPoint,
8380 editor_width: Pixels,
8381 window: &mut Window,
8382 cx: &mut App,
8383 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8384 if target_display_point.row().as_f32() < scroll_top {
8385 let mut element = self
8386 .render_edit_prediction_line_popover(
8387 "Jump to Edit",
8388 Some(IconName::ArrowUp),
8389 window,
8390 cx,
8391 )?
8392 .into_any();
8393
8394 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8395 let offset = point(
8396 (text_bounds.size.width - size.width) / 2.,
8397 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8398 );
8399
8400 let origin = text_bounds.origin + offset;
8401 element.prepaint_at(origin, window, cx);
8402 Some((element, origin))
8403 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8404 let mut element = self
8405 .render_edit_prediction_line_popover(
8406 "Jump to Edit",
8407 Some(IconName::ArrowDown),
8408 window,
8409 cx,
8410 )?
8411 .into_any();
8412
8413 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8414 let offset = point(
8415 (text_bounds.size.width - size.width) / 2.,
8416 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8417 );
8418
8419 let origin = text_bounds.origin + offset;
8420 element.prepaint_at(origin, window, cx);
8421 Some((element, origin))
8422 } else {
8423 self.render_edit_prediction_end_of_line_popover(
8424 "Jump to Edit",
8425 editor_snapshot,
8426 visible_row_range,
8427 target_display_point,
8428 line_height,
8429 scroll_pixel_position,
8430 content_origin,
8431 editor_width,
8432 window,
8433 cx,
8434 )
8435 }
8436 }
8437
8438 fn render_edit_prediction_end_of_line_popover(
8439 self: &mut Editor,
8440 label: &'static str,
8441 editor_snapshot: &EditorSnapshot,
8442 visible_row_range: Range<DisplayRow>,
8443 target_display_point: DisplayPoint,
8444 line_height: Pixels,
8445 scroll_pixel_position: gpui::Point<Pixels>,
8446 content_origin: gpui::Point<Pixels>,
8447 editor_width: Pixels,
8448 window: &mut Window,
8449 cx: &mut App,
8450 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8451 let target_line_end = DisplayPoint::new(
8452 target_display_point.row(),
8453 editor_snapshot.line_len(target_display_point.row()),
8454 );
8455
8456 let mut element = self
8457 .render_edit_prediction_line_popover(label, None, window, cx)?
8458 .into_any();
8459
8460 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8461
8462 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8463
8464 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8465 let mut origin = start_point
8466 + line_origin
8467 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8468 origin.x = origin.x.max(content_origin.x);
8469
8470 let max_x = content_origin.x + editor_width - size.width;
8471
8472 if origin.x > max_x {
8473 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8474
8475 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8476 origin.y += offset;
8477 IconName::ArrowUp
8478 } else {
8479 origin.y -= offset;
8480 IconName::ArrowDown
8481 };
8482
8483 element = self
8484 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8485 .into_any();
8486
8487 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8488
8489 origin.x = content_origin.x + editor_width - size.width - px(2.);
8490 }
8491
8492 element.prepaint_at(origin, window, cx);
8493 Some((element, origin))
8494 }
8495
8496 fn render_edit_prediction_diff_popover(
8497 self: &Editor,
8498 text_bounds: &Bounds<Pixels>,
8499 content_origin: gpui::Point<Pixels>,
8500 right_margin: Pixels,
8501 editor_snapshot: &EditorSnapshot,
8502 visible_row_range: Range<DisplayRow>,
8503 line_layouts: &[LineWithInvisibles],
8504 line_height: Pixels,
8505 scroll_pixel_position: gpui::Point<Pixels>,
8506 newest_selection_head: Option<DisplayPoint>,
8507 editor_width: Pixels,
8508 style: &EditorStyle,
8509 edits: &Vec<(Range<Anchor>, String)>,
8510 edit_preview: &Option<language::EditPreview>,
8511 snapshot: &language::BufferSnapshot,
8512 window: &mut Window,
8513 cx: &mut App,
8514 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8515 let edit_start = edits
8516 .first()
8517 .unwrap()
8518 .0
8519 .start
8520 .to_display_point(editor_snapshot);
8521 let edit_end = edits
8522 .last()
8523 .unwrap()
8524 .0
8525 .end
8526 .to_display_point(editor_snapshot);
8527
8528 let is_visible = visible_row_range.contains(&edit_start.row())
8529 || visible_row_range.contains(&edit_end.row());
8530 if !is_visible {
8531 return None;
8532 }
8533
8534 let highlighted_edits =
8535 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8536
8537 let styled_text = highlighted_edits.to_styled_text(&style.text);
8538 let line_count = highlighted_edits.text.lines().count();
8539
8540 const BORDER_WIDTH: Pixels = px(1.);
8541
8542 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8543 let has_keybind = keybind.is_some();
8544
8545 let mut element = h_flex()
8546 .items_start()
8547 .child(
8548 h_flex()
8549 .bg(cx.theme().colors().editor_background)
8550 .border(BORDER_WIDTH)
8551 .shadow_sm()
8552 .border_color(cx.theme().colors().border)
8553 .rounded_l_lg()
8554 .when(line_count > 1, |el| el.rounded_br_lg())
8555 .pr_1()
8556 .child(styled_text),
8557 )
8558 .child(
8559 h_flex()
8560 .h(line_height + BORDER_WIDTH * 2.)
8561 .px_1p5()
8562 .gap_1()
8563 // Workaround: For some reason, there's a gap if we don't do this
8564 .ml(-BORDER_WIDTH)
8565 .shadow(vec![gpui::BoxShadow {
8566 color: gpui::black().opacity(0.05),
8567 offset: point(px(1.), px(1.)),
8568 blur_radius: px(2.),
8569 spread_radius: px(0.),
8570 }])
8571 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8572 .border(BORDER_WIDTH)
8573 .border_color(cx.theme().colors().border)
8574 .rounded_r_lg()
8575 .id("edit_prediction_diff_popover_keybind")
8576 .when(!has_keybind, |el| {
8577 let status_colors = cx.theme().status();
8578
8579 el.bg(status_colors.error_background)
8580 .border_color(status_colors.error.opacity(0.6))
8581 .child(Icon::new(IconName::Info).color(Color::Error))
8582 .cursor_default()
8583 .hoverable_tooltip(move |_window, cx| {
8584 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8585 })
8586 })
8587 .children(keybind),
8588 )
8589 .into_any();
8590
8591 let longest_row =
8592 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8593 let longest_line_width = if visible_row_range.contains(&longest_row) {
8594 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8595 } else {
8596 layout_line(
8597 longest_row,
8598 editor_snapshot,
8599 style,
8600 editor_width,
8601 |_| false,
8602 window,
8603 cx,
8604 )
8605 .width
8606 };
8607
8608 let viewport_bounds =
8609 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8610 right: -right_margin,
8611 ..Default::default()
8612 });
8613
8614 let x_after_longest =
8615 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8616 - scroll_pixel_position.x;
8617
8618 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8619
8620 // Fully visible if it can be displayed within the window (allow overlapping other
8621 // panes). However, this is only allowed if the popover starts within text_bounds.
8622 let can_position_to_the_right = x_after_longest < text_bounds.right()
8623 && x_after_longest + element_bounds.width < viewport_bounds.right();
8624
8625 let mut origin = if can_position_to_the_right {
8626 point(
8627 x_after_longest,
8628 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8629 - scroll_pixel_position.y,
8630 )
8631 } else {
8632 let cursor_row = newest_selection_head.map(|head| head.row());
8633 let above_edit = edit_start
8634 .row()
8635 .0
8636 .checked_sub(line_count as u32)
8637 .map(DisplayRow);
8638 let below_edit = Some(edit_end.row() + 1);
8639 let above_cursor =
8640 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8641 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8642
8643 // Place the edit popover adjacent to the edit if there is a location
8644 // available that is onscreen and does not obscure the cursor. Otherwise,
8645 // place it adjacent to the cursor.
8646 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8647 .into_iter()
8648 .flatten()
8649 .find(|&start_row| {
8650 let end_row = start_row + line_count as u32;
8651 visible_row_range.contains(&start_row)
8652 && visible_row_range.contains(&end_row)
8653 && cursor_row.map_or(true, |cursor_row| {
8654 !((start_row..end_row).contains(&cursor_row))
8655 })
8656 })?;
8657
8658 content_origin
8659 + point(
8660 -scroll_pixel_position.x,
8661 row_target.as_f32() * line_height - scroll_pixel_position.y,
8662 )
8663 };
8664
8665 origin.x -= BORDER_WIDTH;
8666
8667 window.defer_draw(element, origin, 1);
8668
8669 // Do not return an element, since it will already be drawn due to defer_draw.
8670 None
8671 }
8672
8673 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8674 px(30.)
8675 }
8676
8677 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8678 if self.read_only(cx) {
8679 cx.theme().players().read_only()
8680 } else {
8681 self.style.as_ref().unwrap().local_player
8682 }
8683 }
8684
8685 fn render_edit_prediction_accept_keybind(
8686 &self,
8687 window: &mut Window,
8688 cx: &App,
8689 ) -> Option<AnyElement> {
8690 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8691 let accept_keystroke = accept_binding.keystroke()?;
8692
8693 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8694
8695 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8696 Color::Accent
8697 } else {
8698 Color::Muted
8699 };
8700
8701 h_flex()
8702 .px_0p5()
8703 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8704 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8705 .text_size(TextSize::XSmall.rems(cx))
8706 .child(h_flex().children(ui::render_modifiers(
8707 &accept_keystroke.modifiers,
8708 PlatformStyle::platform(),
8709 Some(modifiers_color),
8710 Some(IconSize::XSmall.rems().into()),
8711 true,
8712 )))
8713 .when(is_platform_style_mac, |parent| {
8714 parent.child(accept_keystroke.key.clone())
8715 })
8716 .when(!is_platform_style_mac, |parent| {
8717 parent.child(
8718 Key::new(
8719 util::capitalize(&accept_keystroke.key),
8720 Some(Color::Default),
8721 )
8722 .size(Some(IconSize::XSmall.rems().into())),
8723 )
8724 })
8725 .into_any()
8726 .into()
8727 }
8728
8729 fn render_edit_prediction_line_popover(
8730 &self,
8731 label: impl Into<SharedString>,
8732 icon: Option<IconName>,
8733 window: &mut Window,
8734 cx: &App,
8735 ) -> Option<Stateful<Div>> {
8736 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8737
8738 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8739 let has_keybind = keybind.is_some();
8740
8741 let result = h_flex()
8742 .id("ep-line-popover")
8743 .py_0p5()
8744 .pl_1()
8745 .pr(padding_right)
8746 .gap_1()
8747 .rounded_md()
8748 .border_1()
8749 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8750 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8751 .shadow_sm()
8752 .when(!has_keybind, |el| {
8753 let status_colors = cx.theme().status();
8754
8755 el.bg(status_colors.error_background)
8756 .border_color(status_colors.error.opacity(0.6))
8757 .pl_2()
8758 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8759 .cursor_default()
8760 .hoverable_tooltip(move |_window, cx| {
8761 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8762 })
8763 })
8764 .children(keybind)
8765 .child(
8766 Label::new(label)
8767 .size(LabelSize::Small)
8768 .when(!has_keybind, |el| {
8769 el.color(cx.theme().status().error.into()).strikethrough()
8770 }),
8771 )
8772 .when(!has_keybind, |el| {
8773 el.child(
8774 h_flex().ml_1().child(
8775 Icon::new(IconName::Info)
8776 .size(IconSize::Small)
8777 .color(cx.theme().status().error.into()),
8778 ),
8779 )
8780 })
8781 .when_some(icon, |element, icon| {
8782 element.child(
8783 div()
8784 .mt(px(1.5))
8785 .child(Icon::new(icon).size(IconSize::Small)),
8786 )
8787 });
8788
8789 Some(result)
8790 }
8791
8792 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8793 let accent_color = cx.theme().colors().text_accent;
8794 let editor_bg_color = cx.theme().colors().editor_background;
8795 editor_bg_color.blend(accent_color.opacity(0.1))
8796 }
8797
8798 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8799 let accent_color = cx.theme().colors().text_accent;
8800 let editor_bg_color = cx.theme().colors().editor_background;
8801 editor_bg_color.blend(accent_color.opacity(0.6))
8802 }
8803
8804 fn render_edit_prediction_cursor_popover(
8805 &self,
8806 min_width: Pixels,
8807 max_width: Pixels,
8808 cursor_point: Point,
8809 style: &EditorStyle,
8810 accept_keystroke: Option<&gpui::Keystroke>,
8811 _window: &Window,
8812 cx: &mut Context<Editor>,
8813 ) -> Option<AnyElement> {
8814 let provider = self.edit_prediction_provider.as_ref()?;
8815
8816 if provider.provider.needs_terms_acceptance(cx) {
8817 return Some(
8818 h_flex()
8819 .min_w(min_width)
8820 .flex_1()
8821 .px_2()
8822 .py_1()
8823 .gap_3()
8824 .elevation_2(cx)
8825 .hover(|style| style.bg(cx.theme().colors().element_hover))
8826 .id("accept-terms")
8827 .cursor_pointer()
8828 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8829 .on_click(cx.listener(|this, _event, window, cx| {
8830 cx.stop_propagation();
8831 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8832 window.dispatch_action(
8833 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8834 cx,
8835 );
8836 }))
8837 .child(
8838 h_flex()
8839 .flex_1()
8840 .gap_2()
8841 .child(Icon::new(IconName::ZedPredict))
8842 .child(Label::new("Accept Terms of Service"))
8843 .child(div().w_full())
8844 .child(
8845 Icon::new(IconName::ArrowUpRight)
8846 .color(Color::Muted)
8847 .size(IconSize::Small),
8848 )
8849 .into_any_element(),
8850 )
8851 .into_any(),
8852 );
8853 }
8854
8855 let is_refreshing = provider.provider.is_refreshing(cx);
8856
8857 fn pending_completion_container() -> Div {
8858 h_flex()
8859 .h_full()
8860 .flex_1()
8861 .gap_2()
8862 .child(Icon::new(IconName::ZedPredict))
8863 }
8864
8865 let completion = match &self.active_inline_completion {
8866 Some(prediction) => {
8867 if !self.has_visible_completions_menu() {
8868 const RADIUS: Pixels = px(6.);
8869 const BORDER_WIDTH: Pixels = px(1.);
8870
8871 return Some(
8872 h_flex()
8873 .elevation_2(cx)
8874 .border(BORDER_WIDTH)
8875 .border_color(cx.theme().colors().border)
8876 .when(accept_keystroke.is_none(), |el| {
8877 el.border_color(cx.theme().status().error)
8878 })
8879 .rounded(RADIUS)
8880 .rounded_tl(px(0.))
8881 .overflow_hidden()
8882 .child(div().px_1p5().child(match &prediction.completion {
8883 InlineCompletion::Move { target, snapshot } => {
8884 use text::ToPoint as _;
8885 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8886 {
8887 Icon::new(IconName::ZedPredictDown)
8888 } else {
8889 Icon::new(IconName::ZedPredictUp)
8890 }
8891 }
8892 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8893 }))
8894 .child(
8895 h_flex()
8896 .gap_1()
8897 .py_1()
8898 .px_2()
8899 .rounded_r(RADIUS - BORDER_WIDTH)
8900 .border_l_1()
8901 .border_color(cx.theme().colors().border)
8902 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8903 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8904 el.child(
8905 Label::new("Hold")
8906 .size(LabelSize::Small)
8907 .when(accept_keystroke.is_none(), |el| {
8908 el.strikethrough()
8909 })
8910 .line_height_style(LineHeightStyle::UiLabel),
8911 )
8912 })
8913 .id("edit_prediction_cursor_popover_keybind")
8914 .when(accept_keystroke.is_none(), |el| {
8915 let status_colors = cx.theme().status();
8916
8917 el.bg(status_colors.error_background)
8918 .border_color(status_colors.error.opacity(0.6))
8919 .child(Icon::new(IconName::Info).color(Color::Error))
8920 .cursor_default()
8921 .hoverable_tooltip(move |_window, cx| {
8922 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8923 .into()
8924 })
8925 })
8926 .when_some(
8927 accept_keystroke.as_ref(),
8928 |el, accept_keystroke| {
8929 el.child(h_flex().children(ui::render_modifiers(
8930 &accept_keystroke.modifiers,
8931 PlatformStyle::platform(),
8932 Some(Color::Default),
8933 Some(IconSize::XSmall.rems().into()),
8934 false,
8935 )))
8936 },
8937 ),
8938 )
8939 .into_any(),
8940 );
8941 }
8942
8943 self.render_edit_prediction_cursor_popover_preview(
8944 prediction,
8945 cursor_point,
8946 style,
8947 cx,
8948 )?
8949 }
8950
8951 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8952 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8953 stale_completion,
8954 cursor_point,
8955 style,
8956 cx,
8957 )?,
8958
8959 None => {
8960 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8961 }
8962 },
8963
8964 None => pending_completion_container().child(Label::new("No Prediction")),
8965 };
8966
8967 let completion = if is_refreshing {
8968 completion
8969 .with_animation(
8970 "loading-completion",
8971 Animation::new(Duration::from_secs(2))
8972 .repeat()
8973 .with_easing(pulsating_between(0.4, 0.8)),
8974 |label, delta| label.opacity(delta),
8975 )
8976 .into_any_element()
8977 } else {
8978 completion.into_any_element()
8979 };
8980
8981 let has_completion = self.active_inline_completion.is_some();
8982
8983 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8984 Some(
8985 h_flex()
8986 .min_w(min_width)
8987 .max_w(max_width)
8988 .flex_1()
8989 .elevation_2(cx)
8990 .border_color(cx.theme().colors().border)
8991 .child(
8992 div()
8993 .flex_1()
8994 .py_1()
8995 .px_2()
8996 .overflow_hidden()
8997 .child(completion),
8998 )
8999 .when_some(accept_keystroke, |el, accept_keystroke| {
9000 if !accept_keystroke.modifiers.modified() {
9001 return el;
9002 }
9003
9004 el.child(
9005 h_flex()
9006 .h_full()
9007 .border_l_1()
9008 .rounded_r_lg()
9009 .border_color(cx.theme().colors().border)
9010 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9011 .gap_1()
9012 .py_1()
9013 .px_2()
9014 .child(
9015 h_flex()
9016 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9017 .when(is_platform_style_mac, |parent| parent.gap_1())
9018 .child(h_flex().children(ui::render_modifiers(
9019 &accept_keystroke.modifiers,
9020 PlatformStyle::platform(),
9021 Some(if !has_completion {
9022 Color::Muted
9023 } else {
9024 Color::Default
9025 }),
9026 None,
9027 false,
9028 ))),
9029 )
9030 .child(Label::new("Preview").into_any_element())
9031 .opacity(if has_completion { 1.0 } else { 0.4 }),
9032 )
9033 })
9034 .into_any(),
9035 )
9036 }
9037
9038 fn render_edit_prediction_cursor_popover_preview(
9039 &self,
9040 completion: &InlineCompletionState,
9041 cursor_point: Point,
9042 style: &EditorStyle,
9043 cx: &mut Context<Editor>,
9044 ) -> Option<Div> {
9045 use text::ToPoint as _;
9046
9047 fn render_relative_row_jump(
9048 prefix: impl Into<String>,
9049 current_row: u32,
9050 target_row: u32,
9051 ) -> Div {
9052 let (row_diff, arrow) = if target_row < current_row {
9053 (current_row - target_row, IconName::ArrowUp)
9054 } else {
9055 (target_row - current_row, IconName::ArrowDown)
9056 };
9057
9058 h_flex()
9059 .child(
9060 Label::new(format!("{}{}", prefix.into(), row_diff))
9061 .color(Color::Muted)
9062 .size(LabelSize::Small),
9063 )
9064 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9065 }
9066
9067 match &completion.completion {
9068 InlineCompletion::Move {
9069 target, snapshot, ..
9070 } => Some(
9071 h_flex()
9072 .px_2()
9073 .gap_2()
9074 .flex_1()
9075 .child(
9076 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9077 Icon::new(IconName::ZedPredictDown)
9078 } else {
9079 Icon::new(IconName::ZedPredictUp)
9080 },
9081 )
9082 .child(Label::new("Jump to Edit")),
9083 ),
9084
9085 InlineCompletion::Edit {
9086 edits,
9087 edit_preview,
9088 snapshot,
9089 display_mode: _,
9090 } => {
9091 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9092
9093 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
9094 &snapshot,
9095 &edits,
9096 edit_preview.as_ref()?,
9097 true,
9098 cx,
9099 )
9100 .first_line_preview();
9101
9102 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9103 .with_default_highlights(&style.text, highlighted_edits.highlights);
9104
9105 let preview = h_flex()
9106 .gap_1()
9107 .min_w_16()
9108 .child(styled_text)
9109 .when(has_more_lines, |parent| parent.child("…"));
9110
9111 let left = if first_edit_row != cursor_point.row {
9112 render_relative_row_jump("", cursor_point.row, first_edit_row)
9113 .into_any_element()
9114 } else {
9115 Icon::new(IconName::ZedPredict).into_any_element()
9116 };
9117
9118 Some(
9119 h_flex()
9120 .h_full()
9121 .flex_1()
9122 .gap_2()
9123 .pr_1()
9124 .overflow_x_hidden()
9125 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9126 .child(left)
9127 .child(preview),
9128 )
9129 }
9130 }
9131 }
9132
9133 pub fn render_context_menu(
9134 &self,
9135 style: &EditorStyle,
9136 max_height_in_lines: u32,
9137 window: &mut Window,
9138 cx: &mut Context<Editor>,
9139 ) -> Option<AnyElement> {
9140 let menu = self.context_menu.borrow();
9141 let menu = menu.as_ref()?;
9142 if !menu.visible() {
9143 return None;
9144 };
9145 Some(menu.render(style, max_height_in_lines, window, cx))
9146 }
9147
9148 fn render_context_menu_aside(
9149 &mut self,
9150 max_size: Size<Pixels>,
9151 window: &mut Window,
9152 cx: &mut Context<Editor>,
9153 ) -> Option<AnyElement> {
9154 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9155 if menu.visible() {
9156 menu.render_aside(max_size, window, cx)
9157 } else {
9158 None
9159 }
9160 })
9161 }
9162
9163 fn hide_context_menu(
9164 &mut self,
9165 window: &mut Window,
9166 cx: &mut Context<Self>,
9167 ) -> Option<CodeContextMenu> {
9168 cx.notify();
9169 self.completion_tasks.clear();
9170 let context_menu = self.context_menu.borrow_mut().take();
9171 self.stale_inline_completion_in_menu.take();
9172 self.update_visible_inline_completion(window, cx);
9173 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9174 if let Some(completion_provider) = &self.completion_provider {
9175 completion_provider.selection_changed(None, window, cx);
9176 }
9177 }
9178 context_menu
9179 }
9180
9181 fn show_snippet_choices(
9182 &mut self,
9183 choices: &Vec<String>,
9184 selection: Range<Anchor>,
9185 cx: &mut Context<Self>,
9186 ) {
9187 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9188 (Some(a), Some(b)) if a == b => a,
9189 _ => {
9190 log::error!("expected anchor range to have matching buffer IDs");
9191 return;
9192 }
9193 };
9194 let multi_buffer = self.buffer().read(cx);
9195 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9196 return;
9197 };
9198
9199 let id = post_inc(&mut self.next_completion_id);
9200 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9201 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9202 CompletionsMenu::new_snippet_choices(
9203 id,
9204 true,
9205 choices,
9206 selection,
9207 buffer,
9208 snippet_sort_order,
9209 ),
9210 ));
9211 }
9212
9213 pub fn insert_snippet(
9214 &mut self,
9215 insertion_ranges: &[Range<usize>],
9216 snippet: Snippet,
9217 window: &mut Window,
9218 cx: &mut Context<Self>,
9219 ) -> Result<()> {
9220 struct Tabstop<T> {
9221 is_end_tabstop: bool,
9222 ranges: Vec<Range<T>>,
9223 choices: Option<Vec<String>>,
9224 }
9225
9226 let tabstops = self.buffer.update(cx, |buffer, cx| {
9227 let snippet_text: Arc<str> = snippet.text.clone().into();
9228 let edits = insertion_ranges
9229 .iter()
9230 .cloned()
9231 .map(|range| (range, snippet_text.clone()));
9232 let autoindent_mode = AutoindentMode::Block {
9233 original_indent_columns: Vec::new(),
9234 };
9235 buffer.edit(edits, Some(autoindent_mode), cx);
9236
9237 let snapshot = &*buffer.read(cx);
9238 let snippet = &snippet;
9239 snippet
9240 .tabstops
9241 .iter()
9242 .map(|tabstop| {
9243 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9244 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9245 });
9246 let mut tabstop_ranges = tabstop
9247 .ranges
9248 .iter()
9249 .flat_map(|tabstop_range| {
9250 let mut delta = 0_isize;
9251 insertion_ranges.iter().map(move |insertion_range| {
9252 let insertion_start = insertion_range.start as isize + delta;
9253 delta +=
9254 snippet.text.len() as isize - insertion_range.len() as isize;
9255
9256 let start = ((insertion_start + tabstop_range.start) as usize)
9257 .min(snapshot.len());
9258 let end = ((insertion_start + tabstop_range.end) as usize)
9259 .min(snapshot.len());
9260 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9261 })
9262 })
9263 .collect::<Vec<_>>();
9264 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9265
9266 Tabstop {
9267 is_end_tabstop,
9268 ranges: tabstop_ranges,
9269 choices: tabstop.choices.clone(),
9270 }
9271 })
9272 .collect::<Vec<_>>()
9273 });
9274 if let Some(tabstop) = tabstops.first() {
9275 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9276 // Reverse order so that the first range is the newest created selection.
9277 // Completions will use it and autoscroll will prioritize it.
9278 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9279 });
9280
9281 if let Some(choices) = &tabstop.choices {
9282 if let Some(selection) = tabstop.ranges.first() {
9283 self.show_snippet_choices(choices, selection.clone(), cx)
9284 }
9285 }
9286
9287 // If we're already at the last tabstop and it's at the end of the snippet,
9288 // we're done, we don't need to keep the state around.
9289 if !tabstop.is_end_tabstop {
9290 let choices = tabstops
9291 .iter()
9292 .map(|tabstop| tabstop.choices.clone())
9293 .collect();
9294
9295 let ranges = tabstops
9296 .into_iter()
9297 .map(|tabstop| tabstop.ranges)
9298 .collect::<Vec<_>>();
9299
9300 self.snippet_stack.push(SnippetState {
9301 active_index: 0,
9302 ranges,
9303 choices,
9304 });
9305 }
9306
9307 // Check whether the just-entered snippet ends with an auto-closable bracket.
9308 if self.autoclose_regions.is_empty() {
9309 let snapshot = self.buffer.read(cx).snapshot(cx);
9310 for selection in &mut self.selections.all::<Point>(cx) {
9311 let selection_head = selection.head();
9312 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9313 continue;
9314 };
9315
9316 let mut bracket_pair = None;
9317 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9318 let prev_chars = snapshot
9319 .reversed_chars_at(selection_head)
9320 .collect::<String>();
9321 for (pair, enabled) in scope.brackets() {
9322 if enabled
9323 && pair.close
9324 && prev_chars.starts_with(pair.start.as_str())
9325 && next_chars.starts_with(pair.end.as_str())
9326 {
9327 bracket_pair = Some(pair.clone());
9328 break;
9329 }
9330 }
9331 if let Some(pair) = bracket_pair {
9332 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9333 let autoclose_enabled =
9334 self.use_autoclose && snapshot_settings.use_autoclose;
9335 if autoclose_enabled {
9336 let start = snapshot.anchor_after(selection_head);
9337 let end = snapshot.anchor_after(selection_head);
9338 self.autoclose_regions.push(AutocloseRegion {
9339 selection_id: selection.id,
9340 range: start..end,
9341 pair,
9342 });
9343 }
9344 }
9345 }
9346 }
9347 }
9348 Ok(())
9349 }
9350
9351 pub fn move_to_next_snippet_tabstop(
9352 &mut self,
9353 window: &mut Window,
9354 cx: &mut Context<Self>,
9355 ) -> bool {
9356 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9357 }
9358
9359 pub fn move_to_prev_snippet_tabstop(
9360 &mut self,
9361 window: &mut Window,
9362 cx: &mut Context<Self>,
9363 ) -> bool {
9364 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9365 }
9366
9367 pub fn move_to_snippet_tabstop(
9368 &mut self,
9369 bias: Bias,
9370 window: &mut Window,
9371 cx: &mut Context<Self>,
9372 ) -> bool {
9373 if let Some(mut snippet) = self.snippet_stack.pop() {
9374 match bias {
9375 Bias::Left => {
9376 if snippet.active_index > 0 {
9377 snippet.active_index -= 1;
9378 } else {
9379 self.snippet_stack.push(snippet);
9380 return false;
9381 }
9382 }
9383 Bias::Right => {
9384 if snippet.active_index + 1 < snippet.ranges.len() {
9385 snippet.active_index += 1;
9386 } else {
9387 self.snippet_stack.push(snippet);
9388 return false;
9389 }
9390 }
9391 }
9392 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9393 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9394 // Reverse order so that the first range is the newest created selection.
9395 // Completions will use it and autoscroll will prioritize it.
9396 s.select_ranges(current_ranges.iter().rev().cloned())
9397 });
9398
9399 if let Some(choices) = &snippet.choices[snippet.active_index] {
9400 if let Some(selection) = current_ranges.first() {
9401 self.show_snippet_choices(&choices, selection.clone(), cx);
9402 }
9403 }
9404
9405 // If snippet state is not at the last tabstop, push it back on the stack
9406 if snippet.active_index + 1 < snippet.ranges.len() {
9407 self.snippet_stack.push(snippet);
9408 }
9409 return true;
9410 }
9411 }
9412
9413 false
9414 }
9415
9416 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9417 self.transact(window, cx, |this, window, cx| {
9418 this.select_all(&SelectAll, window, cx);
9419 this.insert("", window, cx);
9420 });
9421 }
9422
9423 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9424 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9425 self.transact(window, cx, |this, window, cx| {
9426 this.select_autoclose_pair(window, cx);
9427 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9428 if !this.linked_edit_ranges.is_empty() {
9429 let selections = this.selections.all::<MultiBufferPoint>(cx);
9430 let snapshot = this.buffer.read(cx).snapshot(cx);
9431
9432 for selection in selections.iter() {
9433 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9434 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9435 if selection_start.buffer_id != selection_end.buffer_id {
9436 continue;
9437 }
9438 if let Some(ranges) =
9439 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9440 {
9441 for (buffer, entries) in ranges {
9442 linked_ranges.entry(buffer).or_default().extend(entries);
9443 }
9444 }
9445 }
9446 }
9447
9448 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9449 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9450 for selection in &mut selections {
9451 if selection.is_empty() {
9452 let old_head = selection.head();
9453 let mut new_head =
9454 movement::left(&display_map, old_head.to_display_point(&display_map))
9455 .to_point(&display_map);
9456 if let Some((buffer, line_buffer_range)) = display_map
9457 .buffer_snapshot
9458 .buffer_line_for_row(MultiBufferRow(old_head.row))
9459 {
9460 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9461 let indent_len = match indent_size.kind {
9462 IndentKind::Space => {
9463 buffer.settings_at(line_buffer_range.start, cx).tab_size
9464 }
9465 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9466 };
9467 if old_head.column <= indent_size.len && old_head.column > 0 {
9468 let indent_len = indent_len.get();
9469 new_head = cmp::min(
9470 new_head,
9471 MultiBufferPoint::new(
9472 old_head.row,
9473 ((old_head.column - 1) / indent_len) * indent_len,
9474 ),
9475 );
9476 }
9477 }
9478
9479 selection.set_head(new_head, SelectionGoal::None);
9480 }
9481 }
9482
9483 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9484 s.select(selections)
9485 });
9486 this.insert("", window, cx);
9487 let empty_str: Arc<str> = Arc::from("");
9488 for (buffer, edits) in linked_ranges {
9489 let snapshot = buffer.read(cx).snapshot();
9490 use text::ToPoint as TP;
9491
9492 let edits = edits
9493 .into_iter()
9494 .map(|range| {
9495 let end_point = TP::to_point(&range.end, &snapshot);
9496 let mut start_point = TP::to_point(&range.start, &snapshot);
9497
9498 if end_point == start_point {
9499 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9500 .saturating_sub(1);
9501 start_point =
9502 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9503 };
9504
9505 (start_point..end_point, empty_str.clone())
9506 })
9507 .sorted_by_key(|(range, _)| range.start)
9508 .collect::<Vec<_>>();
9509 buffer.update(cx, |this, cx| {
9510 this.edit(edits, None, cx);
9511 })
9512 }
9513 this.refresh_inline_completion(true, false, window, cx);
9514 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9515 });
9516 }
9517
9518 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9519 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9520 self.transact(window, cx, |this, window, cx| {
9521 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9522 s.move_with(|map, selection| {
9523 if selection.is_empty() {
9524 let cursor = movement::right(map, selection.head());
9525 selection.end = cursor;
9526 selection.reversed = true;
9527 selection.goal = SelectionGoal::None;
9528 }
9529 })
9530 });
9531 this.insert("", window, cx);
9532 this.refresh_inline_completion(true, false, window, cx);
9533 });
9534 }
9535
9536 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9537 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9538 if self.move_to_prev_snippet_tabstop(window, cx) {
9539 return;
9540 }
9541 self.outdent(&Outdent, window, cx);
9542 }
9543
9544 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9545 if self.move_to_next_snippet_tabstop(window, cx) {
9546 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9547 return;
9548 }
9549 if self.read_only(cx) {
9550 return;
9551 }
9552 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9553 let mut selections = self.selections.all_adjusted(cx);
9554 let buffer = self.buffer.read(cx);
9555 let snapshot = buffer.snapshot(cx);
9556 let rows_iter = selections.iter().map(|s| s.head().row);
9557 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9558
9559 let has_some_cursor_in_whitespace = selections
9560 .iter()
9561 .filter(|selection| selection.is_empty())
9562 .any(|selection| {
9563 let cursor = selection.head();
9564 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9565 cursor.column < current_indent.len
9566 });
9567
9568 let mut edits = Vec::new();
9569 let mut prev_edited_row = 0;
9570 let mut row_delta = 0;
9571 for selection in &mut selections {
9572 if selection.start.row != prev_edited_row {
9573 row_delta = 0;
9574 }
9575 prev_edited_row = selection.end.row;
9576
9577 // If the selection is non-empty, then increase the indentation of the selected lines.
9578 if !selection.is_empty() {
9579 row_delta =
9580 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9581 continue;
9582 }
9583
9584 let cursor = selection.head();
9585 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9586 if let Some(suggested_indent) =
9587 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9588 {
9589 // Don't do anything if already at suggested indent
9590 // and there is any other cursor which is not
9591 if has_some_cursor_in_whitespace
9592 && cursor.column == current_indent.len
9593 && current_indent.len == suggested_indent.len
9594 {
9595 continue;
9596 }
9597
9598 // Adjust line and move cursor to suggested indent
9599 // if cursor is not at suggested indent
9600 if cursor.column < suggested_indent.len
9601 && cursor.column <= current_indent.len
9602 && current_indent.len <= suggested_indent.len
9603 {
9604 selection.start = Point::new(cursor.row, suggested_indent.len);
9605 selection.end = selection.start;
9606 if row_delta == 0 {
9607 edits.extend(Buffer::edit_for_indent_size_adjustment(
9608 cursor.row,
9609 current_indent,
9610 suggested_indent,
9611 ));
9612 row_delta = suggested_indent.len - current_indent.len;
9613 }
9614 continue;
9615 }
9616
9617 // If current indent is more than suggested indent
9618 // only move cursor to current indent and skip indent
9619 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9620 selection.start = Point::new(cursor.row, current_indent.len);
9621 selection.end = selection.start;
9622 continue;
9623 }
9624 }
9625
9626 // Otherwise, insert a hard or soft tab.
9627 let settings = buffer.language_settings_at(cursor, cx);
9628 let tab_size = if settings.hard_tabs {
9629 IndentSize::tab()
9630 } else {
9631 let tab_size = settings.tab_size.get();
9632 let indent_remainder = snapshot
9633 .text_for_range(Point::new(cursor.row, 0)..cursor)
9634 .flat_map(str::chars)
9635 .fold(row_delta % tab_size, |counter: u32, c| {
9636 if c == '\t' {
9637 0
9638 } else {
9639 (counter + 1) % tab_size
9640 }
9641 });
9642
9643 let chars_to_next_tab_stop = tab_size - indent_remainder;
9644 IndentSize::spaces(chars_to_next_tab_stop)
9645 };
9646 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9647 selection.end = selection.start;
9648 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9649 row_delta += tab_size.len;
9650 }
9651
9652 self.transact(window, cx, |this, window, cx| {
9653 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9654 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9655 s.select(selections)
9656 });
9657 this.refresh_inline_completion(true, false, window, cx);
9658 });
9659 }
9660
9661 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9662 if self.read_only(cx) {
9663 return;
9664 }
9665 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9666 let mut selections = self.selections.all::<Point>(cx);
9667 let mut prev_edited_row = 0;
9668 let mut row_delta = 0;
9669 let mut edits = Vec::new();
9670 let buffer = self.buffer.read(cx);
9671 let snapshot = buffer.snapshot(cx);
9672 for selection in &mut selections {
9673 if selection.start.row != prev_edited_row {
9674 row_delta = 0;
9675 }
9676 prev_edited_row = selection.end.row;
9677
9678 row_delta =
9679 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9680 }
9681
9682 self.transact(window, cx, |this, window, cx| {
9683 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9684 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9685 s.select(selections)
9686 });
9687 });
9688 }
9689
9690 fn indent_selection(
9691 buffer: &MultiBuffer,
9692 snapshot: &MultiBufferSnapshot,
9693 selection: &mut Selection<Point>,
9694 edits: &mut Vec<(Range<Point>, String)>,
9695 delta_for_start_row: u32,
9696 cx: &App,
9697 ) -> u32 {
9698 let settings = buffer.language_settings_at(selection.start, cx);
9699 let tab_size = settings.tab_size.get();
9700 let indent_kind = if settings.hard_tabs {
9701 IndentKind::Tab
9702 } else {
9703 IndentKind::Space
9704 };
9705 let mut start_row = selection.start.row;
9706 let mut end_row = selection.end.row + 1;
9707
9708 // If a selection ends at the beginning of a line, don't indent
9709 // that last line.
9710 if selection.end.column == 0 && selection.end.row > selection.start.row {
9711 end_row -= 1;
9712 }
9713
9714 // Avoid re-indenting a row that has already been indented by a
9715 // previous selection, but still update this selection's column
9716 // to reflect that indentation.
9717 if delta_for_start_row > 0 {
9718 start_row += 1;
9719 selection.start.column += delta_for_start_row;
9720 if selection.end.row == selection.start.row {
9721 selection.end.column += delta_for_start_row;
9722 }
9723 }
9724
9725 let mut delta_for_end_row = 0;
9726 let has_multiple_rows = start_row + 1 != end_row;
9727 for row in start_row..end_row {
9728 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9729 let indent_delta = match (current_indent.kind, indent_kind) {
9730 (IndentKind::Space, IndentKind::Space) => {
9731 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9732 IndentSize::spaces(columns_to_next_tab_stop)
9733 }
9734 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9735 (_, IndentKind::Tab) => IndentSize::tab(),
9736 };
9737
9738 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9739 0
9740 } else {
9741 selection.start.column
9742 };
9743 let row_start = Point::new(row, start);
9744 edits.push((
9745 row_start..row_start,
9746 indent_delta.chars().collect::<String>(),
9747 ));
9748
9749 // Update this selection's endpoints to reflect the indentation.
9750 if row == selection.start.row {
9751 selection.start.column += indent_delta.len;
9752 }
9753 if row == selection.end.row {
9754 selection.end.column += indent_delta.len;
9755 delta_for_end_row = indent_delta.len;
9756 }
9757 }
9758
9759 if selection.start.row == selection.end.row {
9760 delta_for_start_row + delta_for_end_row
9761 } else {
9762 delta_for_end_row
9763 }
9764 }
9765
9766 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9767 if self.read_only(cx) {
9768 return;
9769 }
9770 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9771 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9772 let selections = self.selections.all::<Point>(cx);
9773 let mut deletion_ranges = Vec::new();
9774 let mut last_outdent = None;
9775 {
9776 let buffer = self.buffer.read(cx);
9777 let snapshot = buffer.snapshot(cx);
9778 for selection in &selections {
9779 let settings = buffer.language_settings_at(selection.start, cx);
9780 let tab_size = settings.tab_size.get();
9781 let mut rows = selection.spanned_rows(false, &display_map);
9782
9783 // Avoid re-outdenting a row that has already been outdented by a
9784 // previous selection.
9785 if let Some(last_row) = last_outdent {
9786 if last_row == rows.start {
9787 rows.start = rows.start.next_row();
9788 }
9789 }
9790 let has_multiple_rows = rows.len() > 1;
9791 for row in rows.iter_rows() {
9792 let indent_size = snapshot.indent_size_for_line(row);
9793 if indent_size.len > 0 {
9794 let deletion_len = match indent_size.kind {
9795 IndentKind::Space => {
9796 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9797 if columns_to_prev_tab_stop == 0 {
9798 tab_size
9799 } else {
9800 columns_to_prev_tab_stop
9801 }
9802 }
9803 IndentKind::Tab => 1,
9804 };
9805 let start = if has_multiple_rows
9806 || deletion_len > selection.start.column
9807 || indent_size.len < selection.start.column
9808 {
9809 0
9810 } else {
9811 selection.start.column - deletion_len
9812 };
9813 deletion_ranges.push(
9814 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9815 );
9816 last_outdent = Some(row);
9817 }
9818 }
9819 }
9820 }
9821
9822 self.transact(window, cx, |this, window, cx| {
9823 this.buffer.update(cx, |buffer, cx| {
9824 let empty_str: Arc<str> = Arc::default();
9825 buffer.edit(
9826 deletion_ranges
9827 .into_iter()
9828 .map(|range| (range, empty_str.clone())),
9829 None,
9830 cx,
9831 );
9832 });
9833 let selections = this.selections.all::<usize>(cx);
9834 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9835 s.select(selections)
9836 });
9837 });
9838 }
9839
9840 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9841 if self.read_only(cx) {
9842 return;
9843 }
9844 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9845 let selections = self
9846 .selections
9847 .all::<usize>(cx)
9848 .into_iter()
9849 .map(|s| s.range());
9850
9851 self.transact(window, cx, |this, window, cx| {
9852 this.buffer.update(cx, |buffer, cx| {
9853 buffer.autoindent_ranges(selections, cx);
9854 });
9855 let selections = this.selections.all::<usize>(cx);
9856 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9857 s.select(selections)
9858 });
9859 });
9860 }
9861
9862 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9863 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9864 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9865 let selections = self.selections.all::<Point>(cx);
9866
9867 let mut new_cursors = Vec::new();
9868 let mut edit_ranges = Vec::new();
9869 let mut selections = selections.iter().peekable();
9870 while let Some(selection) = selections.next() {
9871 let mut rows = selection.spanned_rows(false, &display_map);
9872 let goal_display_column = selection.head().to_display_point(&display_map).column();
9873
9874 // Accumulate contiguous regions of rows that we want to delete.
9875 while let Some(next_selection) = selections.peek() {
9876 let next_rows = next_selection.spanned_rows(false, &display_map);
9877 if next_rows.start <= rows.end {
9878 rows.end = next_rows.end;
9879 selections.next().unwrap();
9880 } else {
9881 break;
9882 }
9883 }
9884
9885 let buffer = &display_map.buffer_snapshot;
9886 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9887 let edit_end;
9888 let cursor_buffer_row;
9889 if buffer.max_point().row >= rows.end.0 {
9890 // If there's a line after the range, delete the \n from the end of the row range
9891 // and position the cursor on the next line.
9892 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9893 cursor_buffer_row = rows.end;
9894 } else {
9895 // If there isn't a line after the range, delete the \n from the line before the
9896 // start of the row range and position the cursor there.
9897 edit_start = edit_start.saturating_sub(1);
9898 edit_end = buffer.len();
9899 cursor_buffer_row = rows.start.previous_row();
9900 }
9901
9902 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9903 *cursor.column_mut() =
9904 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9905
9906 new_cursors.push((
9907 selection.id,
9908 buffer.anchor_after(cursor.to_point(&display_map)),
9909 ));
9910 edit_ranges.push(edit_start..edit_end);
9911 }
9912
9913 self.transact(window, cx, |this, window, cx| {
9914 let buffer = this.buffer.update(cx, |buffer, cx| {
9915 let empty_str: Arc<str> = Arc::default();
9916 buffer.edit(
9917 edit_ranges
9918 .into_iter()
9919 .map(|range| (range, empty_str.clone())),
9920 None,
9921 cx,
9922 );
9923 buffer.snapshot(cx)
9924 });
9925 let new_selections = new_cursors
9926 .into_iter()
9927 .map(|(id, cursor)| {
9928 let cursor = cursor.to_point(&buffer);
9929 Selection {
9930 id,
9931 start: cursor,
9932 end: cursor,
9933 reversed: false,
9934 goal: SelectionGoal::None,
9935 }
9936 })
9937 .collect();
9938
9939 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9940 s.select(new_selections);
9941 });
9942 });
9943 }
9944
9945 pub fn join_lines_impl(
9946 &mut self,
9947 insert_whitespace: bool,
9948 window: &mut Window,
9949 cx: &mut Context<Self>,
9950 ) {
9951 if self.read_only(cx) {
9952 return;
9953 }
9954 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9955 for selection in self.selections.all::<Point>(cx) {
9956 let start = MultiBufferRow(selection.start.row);
9957 // Treat single line selections as if they include the next line. Otherwise this action
9958 // would do nothing for single line selections individual cursors.
9959 let end = if selection.start.row == selection.end.row {
9960 MultiBufferRow(selection.start.row + 1)
9961 } else {
9962 MultiBufferRow(selection.end.row)
9963 };
9964
9965 if let Some(last_row_range) = row_ranges.last_mut() {
9966 if start <= last_row_range.end {
9967 last_row_range.end = end;
9968 continue;
9969 }
9970 }
9971 row_ranges.push(start..end);
9972 }
9973
9974 let snapshot = self.buffer.read(cx).snapshot(cx);
9975 let mut cursor_positions = Vec::new();
9976 for row_range in &row_ranges {
9977 let anchor = snapshot.anchor_before(Point::new(
9978 row_range.end.previous_row().0,
9979 snapshot.line_len(row_range.end.previous_row()),
9980 ));
9981 cursor_positions.push(anchor..anchor);
9982 }
9983
9984 self.transact(window, cx, |this, window, cx| {
9985 for row_range in row_ranges.into_iter().rev() {
9986 for row in row_range.iter_rows().rev() {
9987 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9988 let next_line_row = row.next_row();
9989 let indent = snapshot.indent_size_for_line(next_line_row);
9990 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9991
9992 let replace =
9993 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9994 " "
9995 } else {
9996 ""
9997 };
9998
9999 this.buffer.update(cx, |buffer, cx| {
10000 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10001 });
10002 }
10003 }
10004
10005 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10006 s.select_anchor_ranges(cursor_positions)
10007 });
10008 });
10009 }
10010
10011 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10012 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10013 self.join_lines_impl(true, window, cx);
10014 }
10015
10016 pub fn sort_lines_case_sensitive(
10017 &mut self,
10018 _: &SortLinesCaseSensitive,
10019 window: &mut Window,
10020 cx: &mut Context<Self>,
10021 ) {
10022 self.manipulate_lines(window, cx, |lines| lines.sort())
10023 }
10024
10025 pub fn sort_lines_case_insensitive(
10026 &mut self,
10027 _: &SortLinesCaseInsensitive,
10028 window: &mut Window,
10029 cx: &mut Context<Self>,
10030 ) {
10031 self.manipulate_lines(window, cx, |lines| {
10032 lines.sort_by_key(|line| line.to_lowercase())
10033 })
10034 }
10035
10036 pub fn unique_lines_case_insensitive(
10037 &mut self,
10038 _: &UniqueLinesCaseInsensitive,
10039 window: &mut Window,
10040 cx: &mut Context<Self>,
10041 ) {
10042 self.manipulate_lines(window, cx, |lines| {
10043 let mut seen = HashSet::default();
10044 lines.retain(|line| seen.insert(line.to_lowercase()));
10045 })
10046 }
10047
10048 pub fn unique_lines_case_sensitive(
10049 &mut self,
10050 _: &UniqueLinesCaseSensitive,
10051 window: &mut Window,
10052 cx: &mut Context<Self>,
10053 ) {
10054 self.manipulate_lines(window, cx, |lines| {
10055 let mut seen = HashSet::default();
10056 lines.retain(|line| seen.insert(*line));
10057 })
10058 }
10059
10060 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10061 let Some(project) = self.project.clone() else {
10062 return;
10063 };
10064 self.reload(project, window, cx)
10065 .detach_and_notify_err(window, cx);
10066 }
10067
10068 pub fn restore_file(
10069 &mut self,
10070 _: &::git::RestoreFile,
10071 window: &mut Window,
10072 cx: &mut Context<Self>,
10073 ) {
10074 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10075 let mut buffer_ids = HashSet::default();
10076 let snapshot = self.buffer().read(cx).snapshot(cx);
10077 for selection in self.selections.all::<usize>(cx) {
10078 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10079 }
10080
10081 let buffer = self.buffer().read(cx);
10082 let ranges = buffer_ids
10083 .into_iter()
10084 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10085 .collect::<Vec<_>>();
10086
10087 self.restore_hunks_in_ranges(ranges, window, cx);
10088 }
10089
10090 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10091 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10092 let selections = self
10093 .selections
10094 .all(cx)
10095 .into_iter()
10096 .map(|s| s.range())
10097 .collect();
10098 self.restore_hunks_in_ranges(selections, window, cx);
10099 }
10100
10101 pub fn restore_hunks_in_ranges(
10102 &mut self,
10103 ranges: Vec<Range<Point>>,
10104 window: &mut Window,
10105 cx: &mut Context<Editor>,
10106 ) {
10107 let mut revert_changes = HashMap::default();
10108 let chunk_by = self
10109 .snapshot(window, cx)
10110 .hunks_for_ranges(ranges)
10111 .into_iter()
10112 .chunk_by(|hunk| hunk.buffer_id);
10113 for (buffer_id, hunks) in &chunk_by {
10114 let hunks = hunks.collect::<Vec<_>>();
10115 for hunk in &hunks {
10116 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10117 }
10118 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10119 }
10120 drop(chunk_by);
10121 if !revert_changes.is_empty() {
10122 self.transact(window, cx, |editor, window, cx| {
10123 editor.restore(revert_changes, window, cx);
10124 });
10125 }
10126 }
10127
10128 pub fn open_active_item_in_terminal(
10129 &mut self,
10130 _: &OpenInTerminal,
10131 window: &mut Window,
10132 cx: &mut Context<Self>,
10133 ) {
10134 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10135 let project_path = buffer.read(cx).project_path(cx)?;
10136 let project = self.project.as_ref()?.read(cx);
10137 let entry = project.entry_for_path(&project_path, cx)?;
10138 let parent = match &entry.canonical_path {
10139 Some(canonical_path) => canonical_path.to_path_buf(),
10140 None => project.absolute_path(&project_path, cx)?,
10141 }
10142 .parent()?
10143 .to_path_buf();
10144 Some(parent)
10145 }) {
10146 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10147 }
10148 }
10149
10150 fn set_breakpoint_context_menu(
10151 &mut self,
10152 display_row: DisplayRow,
10153 position: Option<Anchor>,
10154 clicked_point: gpui::Point<Pixels>,
10155 window: &mut Window,
10156 cx: &mut Context<Self>,
10157 ) {
10158 if !cx.has_flag::<DebuggerFeatureFlag>() {
10159 return;
10160 }
10161 let source = self
10162 .buffer
10163 .read(cx)
10164 .snapshot(cx)
10165 .anchor_before(Point::new(display_row.0, 0u32));
10166
10167 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10168
10169 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10170 self,
10171 source,
10172 clicked_point,
10173 context_menu,
10174 window,
10175 cx,
10176 );
10177 }
10178
10179 fn add_edit_breakpoint_block(
10180 &mut self,
10181 anchor: Anchor,
10182 breakpoint: &Breakpoint,
10183 edit_action: BreakpointPromptEditAction,
10184 window: &mut Window,
10185 cx: &mut Context<Self>,
10186 ) {
10187 let weak_editor = cx.weak_entity();
10188 let bp_prompt = cx.new(|cx| {
10189 BreakpointPromptEditor::new(
10190 weak_editor,
10191 anchor,
10192 breakpoint.clone(),
10193 edit_action,
10194 window,
10195 cx,
10196 )
10197 });
10198
10199 let height = bp_prompt.update(cx, |this, cx| {
10200 this.prompt
10201 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10202 });
10203 let cloned_prompt = bp_prompt.clone();
10204 let blocks = vec![BlockProperties {
10205 style: BlockStyle::Sticky,
10206 placement: BlockPlacement::Above(anchor),
10207 height: Some(height),
10208 render: Arc::new(move |cx| {
10209 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10210 cloned_prompt.clone().into_any_element()
10211 }),
10212 priority: 0,
10213 render_in_minimap: true,
10214 }];
10215
10216 let focus_handle = bp_prompt.focus_handle(cx);
10217 window.focus(&focus_handle);
10218
10219 let block_ids = self.insert_blocks(blocks, None, cx);
10220 bp_prompt.update(cx, |prompt, _| {
10221 prompt.add_block_ids(block_ids);
10222 });
10223 }
10224
10225 pub(crate) fn breakpoint_at_row(
10226 &self,
10227 row: u32,
10228 window: &mut Window,
10229 cx: &mut Context<Self>,
10230 ) -> Option<(Anchor, Breakpoint)> {
10231 let snapshot = self.snapshot(window, cx);
10232 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10233
10234 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10235 }
10236
10237 pub(crate) fn breakpoint_at_anchor(
10238 &self,
10239 breakpoint_position: Anchor,
10240 snapshot: &EditorSnapshot,
10241 cx: &mut Context<Self>,
10242 ) -> Option<(Anchor, Breakpoint)> {
10243 let project = self.project.clone()?;
10244
10245 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10246 snapshot
10247 .buffer_snapshot
10248 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10249 })?;
10250
10251 let enclosing_excerpt = breakpoint_position.excerpt_id;
10252 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10253 let buffer_snapshot = buffer.read(cx).snapshot();
10254
10255 let row = buffer_snapshot
10256 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10257 .row;
10258
10259 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10260 let anchor_end = snapshot
10261 .buffer_snapshot
10262 .anchor_after(Point::new(row, line_len));
10263
10264 let bp = self
10265 .breakpoint_store
10266 .as_ref()?
10267 .read_with(cx, |breakpoint_store, cx| {
10268 breakpoint_store
10269 .breakpoints(
10270 &buffer,
10271 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10272 &buffer_snapshot,
10273 cx,
10274 )
10275 .next()
10276 .and_then(|(bp, _)| {
10277 let breakpoint_row = buffer_snapshot
10278 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10279 .row;
10280
10281 if breakpoint_row == row {
10282 snapshot
10283 .buffer_snapshot
10284 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10285 .map(|position| (position, bp.bp.clone()))
10286 } else {
10287 None
10288 }
10289 })
10290 });
10291 bp
10292 }
10293
10294 pub fn edit_log_breakpoint(
10295 &mut self,
10296 _: &EditLogBreakpoint,
10297 window: &mut Window,
10298 cx: &mut Context<Self>,
10299 ) {
10300 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10301 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10302 message: None,
10303 state: BreakpointState::Enabled,
10304 condition: None,
10305 hit_condition: None,
10306 });
10307
10308 self.add_edit_breakpoint_block(
10309 anchor,
10310 &breakpoint,
10311 BreakpointPromptEditAction::Log,
10312 window,
10313 cx,
10314 );
10315 }
10316 }
10317
10318 fn breakpoints_at_cursors(
10319 &self,
10320 window: &mut Window,
10321 cx: &mut Context<Self>,
10322 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10323 let snapshot = self.snapshot(window, cx);
10324 let cursors = self
10325 .selections
10326 .disjoint_anchors()
10327 .into_iter()
10328 .map(|selection| {
10329 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10330
10331 let breakpoint_position = self
10332 .breakpoint_at_row(cursor_position.row, window, cx)
10333 .map(|bp| bp.0)
10334 .unwrap_or_else(|| {
10335 snapshot
10336 .display_snapshot
10337 .buffer_snapshot
10338 .anchor_after(Point::new(cursor_position.row, 0))
10339 });
10340
10341 let breakpoint = self
10342 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10343 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10344
10345 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10346 })
10347 // 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.
10348 .collect::<HashMap<Anchor, _>>();
10349
10350 cursors.into_iter().collect()
10351 }
10352
10353 pub fn enable_breakpoint(
10354 &mut self,
10355 _: &crate::actions::EnableBreakpoint,
10356 window: &mut Window,
10357 cx: &mut Context<Self>,
10358 ) {
10359 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10360 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10361 continue;
10362 };
10363 self.edit_breakpoint_at_anchor(
10364 anchor,
10365 breakpoint,
10366 BreakpointEditAction::InvertState,
10367 cx,
10368 );
10369 }
10370 }
10371
10372 pub fn disable_breakpoint(
10373 &mut self,
10374 _: &crate::actions::DisableBreakpoint,
10375 window: &mut Window,
10376 cx: &mut Context<Self>,
10377 ) {
10378 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10379 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10380 continue;
10381 };
10382 self.edit_breakpoint_at_anchor(
10383 anchor,
10384 breakpoint,
10385 BreakpointEditAction::InvertState,
10386 cx,
10387 );
10388 }
10389 }
10390
10391 pub fn toggle_breakpoint(
10392 &mut self,
10393 _: &crate::actions::ToggleBreakpoint,
10394 window: &mut Window,
10395 cx: &mut Context<Self>,
10396 ) {
10397 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10398 if let Some(breakpoint) = breakpoint {
10399 self.edit_breakpoint_at_anchor(
10400 anchor,
10401 breakpoint,
10402 BreakpointEditAction::Toggle,
10403 cx,
10404 );
10405 } else {
10406 self.edit_breakpoint_at_anchor(
10407 anchor,
10408 Breakpoint::new_standard(),
10409 BreakpointEditAction::Toggle,
10410 cx,
10411 );
10412 }
10413 }
10414 }
10415
10416 pub fn edit_breakpoint_at_anchor(
10417 &mut self,
10418 breakpoint_position: Anchor,
10419 breakpoint: Breakpoint,
10420 edit_action: BreakpointEditAction,
10421 cx: &mut Context<Self>,
10422 ) {
10423 let Some(breakpoint_store) = &self.breakpoint_store else {
10424 return;
10425 };
10426
10427 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10428 if breakpoint_position == Anchor::min() {
10429 self.buffer()
10430 .read(cx)
10431 .excerpt_buffer_ids()
10432 .into_iter()
10433 .next()
10434 } else {
10435 None
10436 }
10437 }) else {
10438 return;
10439 };
10440
10441 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10442 return;
10443 };
10444
10445 breakpoint_store.update(cx, |breakpoint_store, cx| {
10446 breakpoint_store.toggle_breakpoint(
10447 buffer,
10448 BreakpointWithPosition {
10449 position: breakpoint_position.text_anchor,
10450 bp: breakpoint,
10451 },
10452 edit_action,
10453 cx,
10454 );
10455 });
10456
10457 cx.notify();
10458 }
10459
10460 #[cfg(any(test, feature = "test-support"))]
10461 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10462 self.breakpoint_store.clone()
10463 }
10464
10465 pub fn prepare_restore_change(
10466 &self,
10467 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10468 hunk: &MultiBufferDiffHunk,
10469 cx: &mut App,
10470 ) -> Option<()> {
10471 if hunk.is_created_file() {
10472 return None;
10473 }
10474 let buffer = self.buffer.read(cx);
10475 let diff = buffer.diff_for(hunk.buffer_id)?;
10476 let buffer = buffer.buffer(hunk.buffer_id)?;
10477 let buffer = buffer.read(cx);
10478 let original_text = diff
10479 .read(cx)
10480 .base_text()
10481 .as_rope()
10482 .slice(hunk.diff_base_byte_range.clone());
10483 let buffer_snapshot = buffer.snapshot();
10484 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10485 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10486 probe
10487 .0
10488 .start
10489 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10490 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10491 }) {
10492 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10493 Some(())
10494 } else {
10495 None
10496 }
10497 }
10498
10499 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10500 self.manipulate_lines(window, cx, |lines| lines.reverse())
10501 }
10502
10503 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10504 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10505 }
10506
10507 fn manipulate_lines<Fn>(
10508 &mut self,
10509 window: &mut Window,
10510 cx: &mut Context<Self>,
10511 mut callback: Fn,
10512 ) where
10513 Fn: FnMut(&mut Vec<&str>),
10514 {
10515 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10516
10517 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10518 let buffer = self.buffer.read(cx).snapshot(cx);
10519
10520 let mut edits = Vec::new();
10521
10522 let selections = self.selections.all::<Point>(cx);
10523 let mut selections = selections.iter().peekable();
10524 let mut contiguous_row_selections = Vec::new();
10525 let mut new_selections = Vec::new();
10526 let mut added_lines = 0;
10527 let mut removed_lines = 0;
10528
10529 while let Some(selection) = selections.next() {
10530 let (start_row, end_row) = consume_contiguous_rows(
10531 &mut contiguous_row_selections,
10532 selection,
10533 &display_map,
10534 &mut selections,
10535 );
10536
10537 let start_point = Point::new(start_row.0, 0);
10538 let end_point = Point::new(
10539 end_row.previous_row().0,
10540 buffer.line_len(end_row.previous_row()),
10541 );
10542 let text = buffer
10543 .text_for_range(start_point..end_point)
10544 .collect::<String>();
10545
10546 let mut lines = text.split('\n').collect_vec();
10547
10548 let lines_before = lines.len();
10549 callback(&mut lines);
10550 let lines_after = lines.len();
10551
10552 edits.push((start_point..end_point, lines.join("\n")));
10553
10554 // Selections must change based on added and removed line count
10555 let start_row =
10556 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10557 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10558 new_selections.push(Selection {
10559 id: selection.id,
10560 start: start_row,
10561 end: end_row,
10562 goal: SelectionGoal::None,
10563 reversed: selection.reversed,
10564 });
10565
10566 if lines_after > lines_before {
10567 added_lines += lines_after - lines_before;
10568 } else if lines_before > lines_after {
10569 removed_lines += lines_before - lines_after;
10570 }
10571 }
10572
10573 self.transact(window, cx, |this, window, cx| {
10574 let buffer = this.buffer.update(cx, |buffer, cx| {
10575 buffer.edit(edits, None, cx);
10576 buffer.snapshot(cx)
10577 });
10578
10579 // Recalculate offsets on newly edited buffer
10580 let new_selections = new_selections
10581 .iter()
10582 .map(|s| {
10583 let start_point = Point::new(s.start.0, 0);
10584 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10585 Selection {
10586 id: s.id,
10587 start: buffer.point_to_offset(start_point),
10588 end: buffer.point_to_offset(end_point),
10589 goal: s.goal,
10590 reversed: s.reversed,
10591 }
10592 })
10593 .collect();
10594
10595 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10596 s.select(new_selections);
10597 });
10598
10599 this.request_autoscroll(Autoscroll::fit(), cx);
10600 });
10601 }
10602
10603 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10604 self.manipulate_text(window, cx, |text| {
10605 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10606 if has_upper_case_characters {
10607 text.to_lowercase()
10608 } else {
10609 text.to_uppercase()
10610 }
10611 })
10612 }
10613
10614 pub fn convert_to_upper_case(
10615 &mut self,
10616 _: &ConvertToUpperCase,
10617 window: &mut Window,
10618 cx: &mut Context<Self>,
10619 ) {
10620 self.manipulate_text(window, cx, |text| text.to_uppercase())
10621 }
10622
10623 pub fn convert_to_lower_case(
10624 &mut self,
10625 _: &ConvertToLowerCase,
10626 window: &mut Window,
10627 cx: &mut Context<Self>,
10628 ) {
10629 self.manipulate_text(window, cx, |text| text.to_lowercase())
10630 }
10631
10632 pub fn convert_to_title_case(
10633 &mut self,
10634 _: &ConvertToTitleCase,
10635 window: &mut Window,
10636 cx: &mut Context<Self>,
10637 ) {
10638 self.manipulate_text(window, cx, |text| {
10639 text.split('\n')
10640 .map(|line| line.to_case(Case::Title))
10641 .join("\n")
10642 })
10643 }
10644
10645 pub fn convert_to_snake_case(
10646 &mut self,
10647 _: &ConvertToSnakeCase,
10648 window: &mut Window,
10649 cx: &mut Context<Self>,
10650 ) {
10651 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10652 }
10653
10654 pub fn convert_to_kebab_case(
10655 &mut self,
10656 _: &ConvertToKebabCase,
10657 window: &mut Window,
10658 cx: &mut Context<Self>,
10659 ) {
10660 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10661 }
10662
10663 pub fn convert_to_upper_camel_case(
10664 &mut self,
10665 _: &ConvertToUpperCamelCase,
10666 window: &mut Window,
10667 cx: &mut Context<Self>,
10668 ) {
10669 self.manipulate_text(window, cx, |text| {
10670 text.split('\n')
10671 .map(|line| line.to_case(Case::UpperCamel))
10672 .join("\n")
10673 })
10674 }
10675
10676 pub fn convert_to_lower_camel_case(
10677 &mut self,
10678 _: &ConvertToLowerCamelCase,
10679 window: &mut Window,
10680 cx: &mut Context<Self>,
10681 ) {
10682 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10683 }
10684
10685 pub fn convert_to_opposite_case(
10686 &mut self,
10687 _: &ConvertToOppositeCase,
10688 window: &mut Window,
10689 cx: &mut Context<Self>,
10690 ) {
10691 self.manipulate_text(window, cx, |text| {
10692 text.chars()
10693 .fold(String::with_capacity(text.len()), |mut t, c| {
10694 if c.is_uppercase() {
10695 t.extend(c.to_lowercase());
10696 } else {
10697 t.extend(c.to_uppercase());
10698 }
10699 t
10700 })
10701 })
10702 }
10703
10704 pub fn convert_to_rot13(
10705 &mut self,
10706 _: &ConvertToRot13,
10707 window: &mut Window,
10708 cx: &mut Context<Self>,
10709 ) {
10710 self.manipulate_text(window, cx, |text| {
10711 text.chars()
10712 .map(|c| match c {
10713 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10714 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10715 _ => c,
10716 })
10717 .collect()
10718 })
10719 }
10720
10721 pub fn convert_to_rot47(
10722 &mut self,
10723 _: &ConvertToRot47,
10724 window: &mut Window,
10725 cx: &mut Context<Self>,
10726 ) {
10727 self.manipulate_text(window, cx, |text| {
10728 text.chars()
10729 .map(|c| {
10730 let code_point = c as u32;
10731 if code_point >= 33 && code_point <= 126 {
10732 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10733 }
10734 c
10735 })
10736 .collect()
10737 })
10738 }
10739
10740 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10741 where
10742 Fn: FnMut(&str) -> String,
10743 {
10744 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10745 let buffer = self.buffer.read(cx).snapshot(cx);
10746
10747 let mut new_selections = Vec::new();
10748 let mut edits = Vec::new();
10749 let mut selection_adjustment = 0i32;
10750
10751 for selection in self.selections.all::<usize>(cx) {
10752 let selection_is_empty = selection.is_empty();
10753
10754 let (start, end) = if selection_is_empty {
10755 let word_range = movement::surrounding_word(
10756 &display_map,
10757 selection.start.to_display_point(&display_map),
10758 );
10759 let start = word_range.start.to_offset(&display_map, Bias::Left);
10760 let end = word_range.end.to_offset(&display_map, Bias::Left);
10761 (start, end)
10762 } else {
10763 (selection.start, selection.end)
10764 };
10765
10766 let text = buffer.text_for_range(start..end).collect::<String>();
10767 let old_length = text.len() as i32;
10768 let text = callback(&text);
10769
10770 new_selections.push(Selection {
10771 start: (start as i32 - selection_adjustment) as usize,
10772 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10773 goal: SelectionGoal::None,
10774 ..selection
10775 });
10776
10777 selection_adjustment += old_length - text.len() as i32;
10778
10779 edits.push((start..end, text));
10780 }
10781
10782 self.transact(window, cx, |this, window, cx| {
10783 this.buffer.update(cx, |buffer, cx| {
10784 buffer.edit(edits, None, cx);
10785 });
10786
10787 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10788 s.select(new_selections);
10789 });
10790
10791 this.request_autoscroll(Autoscroll::fit(), cx);
10792 });
10793 }
10794
10795 pub fn move_selection_on_drop(
10796 &mut self,
10797 selection: &Selection<Anchor>,
10798 target: DisplayPoint,
10799 is_cut: bool,
10800 window: &mut Window,
10801 cx: &mut Context<Self>,
10802 ) {
10803 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10804 let buffer = &display_map.buffer_snapshot;
10805 let mut edits = Vec::new();
10806 let insert_point = display_map
10807 .clip_point(target, Bias::Left)
10808 .to_point(&display_map);
10809 let text = buffer
10810 .text_for_range(selection.start..selection.end)
10811 .collect::<String>();
10812 if is_cut {
10813 edits.push(((selection.start..selection.end), String::new()));
10814 }
10815 let insert_anchor = buffer.anchor_before(insert_point);
10816 edits.push(((insert_anchor..insert_anchor), text));
10817 let last_edit_start = insert_anchor.bias_left(buffer);
10818 let last_edit_end = insert_anchor.bias_right(buffer);
10819 self.transact(window, cx, |this, window, cx| {
10820 this.buffer.update(cx, |buffer, cx| {
10821 buffer.edit(edits, None, cx);
10822 });
10823 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10824 s.select_anchor_ranges([last_edit_start..last_edit_end]);
10825 });
10826 });
10827 }
10828
10829 pub fn clear_selection_drag_state(&mut self) {
10830 self.selection_drag_state = SelectionDragState::None;
10831 }
10832
10833 pub fn duplicate(
10834 &mut self,
10835 upwards: bool,
10836 whole_lines: bool,
10837 window: &mut Window,
10838 cx: &mut Context<Self>,
10839 ) {
10840 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10841
10842 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10843 let buffer = &display_map.buffer_snapshot;
10844 let selections = self.selections.all::<Point>(cx);
10845
10846 let mut edits = Vec::new();
10847 let mut selections_iter = selections.iter().peekable();
10848 while let Some(selection) = selections_iter.next() {
10849 let mut rows = selection.spanned_rows(false, &display_map);
10850 // duplicate line-wise
10851 if whole_lines || selection.start == selection.end {
10852 // Avoid duplicating the same lines twice.
10853 while let Some(next_selection) = selections_iter.peek() {
10854 let next_rows = next_selection.spanned_rows(false, &display_map);
10855 if next_rows.start < rows.end {
10856 rows.end = next_rows.end;
10857 selections_iter.next().unwrap();
10858 } else {
10859 break;
10860 }
10861 }
10862
10863 // Copy the text from the selected row region and splice it either at the start
10864 // or end of the region.
10865 let start = Point::new(rows.start.0, 0);
10866 let end = Point::new(
10867 rows.end.previous_row().0,
10868 buffer.line_len(rows.end.previous_row()),
10869 );
10870 let text = buffer
10871 .text_for_range(start..end)
10872 .chain(Some("\n"))
10873 .collect::<String>();
10874 let insert_location = if upwards {
10875 Point::new(rows.end.0, 0)
10876 } else {
10877 start
10878 };
10879 edits.push((insert_location..insert_location, text));
10880 } else {
10881 // duplicate character-wise
10882 let start = selection.start;
10883 let end = selection.end;
10884 let text = buffer.text_for_range(start..end).collect::<String>();
10885 edits.push((selection.end..selection.end, text));
10886 }
10887 }
10888
10889 self.transact(window, cx, |this, _, cx| {
10890 this.buffer.update(cx, |buffer, cx| {
10891 buffer.edit(edits, None, cx);
10892 });
10893
10894 this.request_autoscroll(Autoscroll::fit(), cx);
10895 });
10896 }
10897
10898 pub fn duplicate_line_up(
10899 &mut self,
10900 _: &DuplicateLineUp,
10901 window: &mut Window,
10902 cx: &mut Context<Self>,
10903 ) {
10904 self.duplicate(true, true, window, cx);
10905 }
10906
10907 pub fn duplicate_line_down(
10908 &mut self,
10909 _: &DuplicateLineDown,
10910 window: &mut Window,
10911 cx: &mut Context<Self>,
10912 ) {
10913 self.duplicate(false, true, window, cx);
10914 }
10915
10916 pub fn duplicate_selection(
10917 &mut self,
10918 _: &DuplicateSelection,
10919 window: &mut Window,
10920 cx: &mut Context<Self>,
10921 ) {
10922 self.duplicate(false, false, window, cx);
10923 }
10924
10925 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10926 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10927
10928 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10929 let buffer = self.buffer.read(cx).snapshot(cx);
10930
10931 let mut edits = Vec::new();
10932 let mut unfold_ranges = Vec::new();
10933 let mut refold_creases = Vec::new();
10934
10935 let selections = self.selections.all::<Point>(cx);
10936 let mut selections = selections.iter().peekable();
10937 let mut contiguous_row_selections = Vec::new();
10938 let mut new_selections = Vec::new();
10939
10940 while let Some(selection) = selections.next() {
10941 // Find all the selections that span a contiguous row range
10942 let (start_row, end_row) = consume_contiguous_rows(
10943 &mut contiguous_row_selections,
10944 selection,
10945 &display_map,
10946 &mut selections,
10947 );
10948
10949 // Move the text spanned by the row range to be before the line preceding the row range
10950 if start_row.0 > 0 {
10951 let range_to_move = Point::new(
10952 start_row.previous_row().0,
10953 buffer.line_len(start_row.previous_row()),
10954 )
10955 ..Point::new(
10956 end_row.previous_row().0,
10957 buffer.line_len(end_row.previous_row()),
10958 );
10959 let insertion_point = display_map
10960 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10961 .0;
10962
10963 // Don't move lines across excerpts
10964 if buffer
10965 .excerpt_containing(insertion_point..range_to_move.end)
10966 .is_some()
10967 {
10968 let text = buffer
10969 .text_for_range(range_to_move.clone())
10970 .flat_map(|s| s.chars())
10971 .skip(1)
10972 .chain(['\n'])
10973 .collect::<String>();
10974
10975 edits.push((
10976 buffer.anchor_after(range_to_move.start)
10977 ..buffer.anchor_before(range_to_move.end),
10978 String::new(),
10979 ));
10980 let insertion_anchor = buffer.anchor_after(insertion_point);
10981 edits.push((insertion_anchor..insertion_anchor, text));
10982
10983 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10984
10985 // Move selections up
10986 new_selections.extend(contiguous_row_selections.drain(..).map(
10987 |mut selection| {
10988 selection.start.row -= row_delta;
10989 selection.end.row -= row_delta;
10990 selection
10991 },
10992 ));
10993
10994 // Move folds up
10995 unfold_ranges.push(range_to_move.clone());
10996 for fold in display_map.folds_in_range(
10997 buffer.anchor_before(range_to_move.start)
10998 ..buffer.anchor_after(range_to_move.end),
10999 ) {
11000 let mut start = fold.range.start.to_point(&buffer);
11001 let mut end = fold.range.end.to_point(&buffer);
11002 start.row -= row_delta;
11003 end.row -= row_delta;
11004 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11005 }
11006 }
11007 }
11008
11009 // If we didn't move line(s), preserve the existing selections
11010 new_selections.append(&mut contiguous_row_selections);
11011 }
11012
11013 self.transact(window, cx, |this, window, cx| {
11014 this.unfold_ranges(&unfold_ranges, true, true, cx);
11015 this.buffer.update(cx, |buffer, cx| {
11016 for (range, text) in edits {
11017 buffer.edit([(range, text)], None, cx);
11018 }
11019 });
11020 this.fold_creases(refold_creases, true, window, cx);
11021 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11022 s.select(new_selections);
11023 })
11024 });
11025 }
11026
11027 pub fn move_line_down(
11028 &mut self,
11029 _: &MoveLineDown,
11030 window: &mut Window,
11031 cx: &mut Context<Self>,
11032 ) {
11033 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11034
11035 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11036 let buffer = self.buffer.read(cx).snapshot(cx);
11037
11038 let mut edits = Vec::new();
11039 let mut unfold_ranges = Vec::new();
11040 let mut refold_creases = Vec::new();
11041
11042 let selections = self.selections.all::<Point>(cx);
11043 let mut selections = selections.iter().peekable();
11044 let mut contiguous_row_selections = Vec::new();
11045 let mut new_selections = Vec::new();
11046
11047 while let Some(selection) = selections.next() {
11048 // Find all the selections that span a contiguous row range
11049 let (start_row, end_row) = consume_contiguous_rows(
11050 &mut contiguous_row_selections,
11051 selection,
11052 &display_map,
11053 &mut selections,
11054 );
11055
11056 // Move the text spanned by the row range to be after the last line of the row range
11057 if end_row.0 <= buffer.max_point().row {
11058 let range_to_move =
11059 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11060 let insertion_point = display_map
11061 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11062 .0;
11063
11064 // Don't move lines across excerpt boundaries
11065 if buffer
11066 .excerpt_containing(range_to_move.start..insertion_point)
11067 .is_some()
11068 {
11069 let mut text = String::from("\n");
11070 text.extend(buffer.text_for_range(range_to_move.clone()));
11071 text.pop(); // Drop trailing newline
11072 edits.push((
11073 buffer.anchor_after(range_to_move.start)
11074 ..buffer.anchor_before(range_to_move.end),
11075 String::new(),
11076 ));
11077 let insertion_anchor = buffer.anchor_after(insertion_point);
11078 edits.push((insertion_anchor..insertion_anchor, text));
11079
11080 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11081
11082 // Move selections down
11083 new_selections.extend(contiguous_row_selections.drain(..).map(
11084 |mut selection| {
11085 selection.start.row += row_delta;
11086 selection.end.row += row_delta;
11087 selection
11088 },
11089 ));
11090
11091 // Move folds down
11092 unfold_ranges.push(range_to_move.clone());
11093 for fold in display_map.folds_in_range(
11094 buffer.anchor_before(range_to_move.start)
11095 ..buffer.anchor_after(range_to_move.end),
11096 ) {
11097 let mut start = fold.range.start.to_point(&buffer);
11098 let mut end = fold.range.end.to_point(&buffer);
11099 start.row += row_delta;
11100 end.row += row_delta;
11101 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11102 }
11103 }
11104 }
11105
11106 // If we didn't move line(s), preserve the existing selections
11107 new_selections.append(&mut contiguous_row_selections);
11108 }
11109
11110 self.transact(window, cx, |this, window, cx| {
11111 this.unfold_ranges(&unfold_ranges, true, true, cx);
11112 this.buffer.update(cx, |buffer, cx| {
11113 for (range, text) in edits {
11114 buffer.edit([(range, text)], None, cx);
11115 }
11116 });
11117 this.fold_creases(refold_creases, true, window, cx);
11118 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11119 s.select(new_selections)
11120 });
11121 });
11122 }
11123
11124 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11125 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11126 let text_layout_details = &self.text_layout_details(window);
11127 self.transact(window, cx, |this, window, cx| {
11128 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11129 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11130 s.move_with(|display_map, selection| {
11131 if !selection.is_empty() {
11132 return;
11133 }
11134
11135 let mut head = selection.head();
11136 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11137 if head.column() == display_map.line_len(head.row()) {
11138 transpose_offset = display_map
11139 .buffer_snapshot
11140 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11141 }
11142
11143 if transpose_offset == 0 {
11144 return;
11145 }
11146
11147 *head.column_mut() += 1;
11148 head = display_map.clip_point(head, Bias::Right);
11149 let goal = SelectionGoal::HorizontalPosition(
11150 display_map
11151 .x_for_display_point(head, text_layout_details)
11152 .into(),
11153 );
11154 selection.collapse_to(head, goal);
11155
11156 let transpose_start = display_map
11157 .buffer_snapshot
11158 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11159 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11160 let transpose_end = display_map
11161 .buffer_snapshot
11162 .clip_offset(transpose_offset + 1, Bias::Right);
11163 if let Some(ch) =
11164 display_map.buffer_snapshot.chars_at(transpose_start).next()
11165 {
11166 edits.push((transpose_start..transpose_offset, String::new()));
11167 edits.push((transpose_end..transpose_end, ch.to_string()));
11168 }
11169 }
11170 });
11171 edits
11172 });
11173 this.buffer
11174 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11175 let selections = this.selections.all::<usize>(cx);
11176 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11177 s.select(selections);
11178 });
11179 });
11180 }
11181
11182 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11183 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11184 self.rewrap_impl(RewrapOptions::default(), cx)
11185 }
11186
11187 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11188 let buffer = self.buffer.read(cx).snapshot(cx);
11189 let selections = self.selections.all::<Point>(cx);
11190
11191 // Shrink and split selections to respect paragraph boundaries.
11192 let ranges = selections.into_iter().flat_map(|selection| {
11193 let language_settings = buffer.language_settings_at(selection.head(), cx);
11194 let language_scope = buffer.language_scope_at(selection.head());
11195
11196 let Some(start_row) = (selection.start.row..=selection.end.row)
11197 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11198 else {
11199 return vec![];
11200 };
11201 let Some(end_row) = (selection.start.row..=selection.end.row)
11202 .rev()
11203 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11204 else {
11205 return vec![];
11206 };
11207
11208 let mut row = start_row;
11209 let mut ranges = Vec::new();
11210 while let Some(blank_row) =
11211 (row..end_row).find(|row| buffer.is_line_blank(MultiBufferRow(*row)))
11212 {
11213 let next_paragraph_start = (blank_row + 1..=end_row)
11214 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11215 .unwrap();
11216 ranges.push((
11217 language_settings.clone(),
11218 language_scope.clone(),
11219 Point::new(row, 0)..Point::new(blank_row - 1, 0),
11220 ));
11221 row = next_paragraph_start;
11222 }
11223 ranges.push((
11224 language_settings.clone(),
11225 language_scope.clone(),
11226 Point::new(row, 0)..Point::new(end_row, 0),
11227 ));
11228
11229 ranges
11230 });
11231
11232 let mut edits = Vec::new();
11233 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11234
11235 for (language_settings, language_scope, range) in ranges {
11236 let mut start_row = range.start.row;
11237 let mut end_row = range.end.row;
11238
11239 // Skip selections that overlap with a range that has already been rewrapped.
11240 let selection_range = start_row..end_row;
11241 if rewrapped_row_ranges
11242 .iter()
11243 .any(|range| range.overlaps(&selection_range))
11244 {
11245 continue;
11246 }
11247
11248 let tab_size = language_settings.tab_size;
11249
11250 // Since not all lines in the selection may be at the same indent
11251 // level, choose the indent size that is the most common between all
11252 // of the lines.
11253 //
11254 // If there is a tie, we use the deepest indent.
11255 let (indent_size, indent_end) = {
11256 let mut indent_size_occurrences = HashMap::default();
11257 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
11258
11259 for row in start_row..=end_row {
11260 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11261 rows_by_indent_size.entry(indent).or_default().push(row);
11262 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
11263 }
11264
11265 let indent_size = indent_size_occurrences
11266 .into_iter()
11267 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
11268 .map(|(indent, _)| indent)
11269 .unwrap_or_default();
11270 let row = rows_by_indent_size[&indent_size][0];
11271 let indent_end = Point::new(row, indent_size.len);
11272
11273 (indent_size, indent_end)
11274 };
11275
11276 let mut line_prefix = indent_size.chars().collect::<String>();
11277
11278 let mut inside_comment = false;
11279 if let Some(comment_prefix) = language_scope.and_then(|language| {
11280 language
11281 .line_comment_prefixes()
11282 .iter()
11283 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11284 .cloned()
11285 }) {
11286 line_prefix.push_str(&comment_prefix);
11287 inside_comment = true;
11288 }
11289
11290 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11291 RewrapBehavior::InComments => inside_comment,
11292 RewrapBehavior::InSelections => !range.is_empty(),
11293 RewrapBehavior::Anywhere => true,
11294 };
11295
11296 let should_rewrap = options.override_language_settings
11297 || allow_rewrap_based_on_language
11298 || self.hard_wrap.is_some();
11299 if !should_rewrap {
11300 continue;
11301 }
11302
11303 if range.is_empty() {
11304 'expand_upwards: while start_row > 0 {
11305 let prev_row = start_row - 1;
11306 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11307 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11308 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11309 {
11310 start_row = prev_row;
11311 } else {
11312 break 'expand_upwards;
11313 }
11314 }
11315
11316 'expand_downwards: while end_row < buffer.max_point().row {
11317 let next_row = end_row + 1;
11318 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11319 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11320 && !buffer.is_line_blank(MultiBufferRow(next_row))
11321 {
11322 end_row = next_row;
11323 } else {
11324 break 'expand_downwards;
11325 }
11326 }
11327 }
11328
11329 let start = Point::new(start_row, 0);
11330 let start_offset = start.to_offset(&buffer);
11331 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11332 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11333 let Some(lines_without_prefixes) = selection_text
11334 .lines()
11335 .map(|line| {
11336 line.strip_prefix(&line_prefix)
11337 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
11338 .with_context(|| {
11339 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11340 })
11341 })
11342 .collect::<Result<Vec<_>, _>>()
11343 .log_err()
11344 else {
11345 continue;
11346 };
11347
11348 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11349 buffer
11350 .language_settings_at(Point::new(start_row, 0), cx)
11351 .preferred_line_length as usize
11352 });
11353 let wrapped_text = wrap_with_prefix(
11354 line_prefix,
11355 lines_without_prefixes.join("\n"),
11356 wrap_column,
11357 tab_size,
11358 options.preserve_existing_whitespace,
11359 );
11360
11361 // TODO: should always use char-based diff while still supporting cursor behavior that
11362 // matches vim.
11363 let mut diff_options = DiffOptions::default();
11364 if options.override_language_settings {
11365 diff_options.max_word_diff_len = 0;
11366 diff_options.max_word_diff_line_count = 0;
11367 } else {
11368 diff_options.max_word_diff_len = usize::MAX;
11369 diff_options.max_word_diff_line_count = usize::MAX;
11370 }
11371
11372 for (old_range, new_text) in
11373 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11374 {
11375 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11376 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11377 edits.push((edit_start..edit_end, new_text));
11378 }
11379
11380 rewrapped_row_ranges.push(start_row..=end_row);
11381 }
11382
11383 self.buffer
11384 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11385 }
11386
11387 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11388 let mut text = String::new();
11389 let buffer = self.buffer.read(cx).snapshot(cx);
11390 let mut selections = self.selections.all::<Point>(cx);
11391 let mut clipboard_selections = Vec::with_capacity(selections.len());
11392 {
11393 let max_point = buffer.max_point();
11394 let mut is_first = true;
11395 for selection in &mut selections {
11396 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11397 if is_entire_line {
11398 selection.start = Point::new(selection.start.row, 0);
11399 if !selection.is_empty() && selection.end.column == 0 {
11400 selection.end = cmp::min(max_point, selection.end);
11401 } else {
11402 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11403 }
11404 selection.goal = SelectionGoal::None;
11405 }
11406 if is_first {
11407 is_first = false;
11408 } else {
11409 text += "\n";
11410 }
11411 let mut len = 0;
11412 for chunk in buffer.text_for_range(selection.start..selection.end) {
11413 text.push_str(chunk);
11414 len += chunk.len();
11415 }
11416 clipboard_selections.push(ClipboardSelection {
11417 len,
11418 is_entire_line,
11419 first_line_indent: buffer
11420 .indent_size_for_line(MultiBufferRow(selection.start.row))
11421 .len,
11422 });
11423 }
11424 }
11425
11426 self.transact(window, cx, |this, window, cx| {
11427 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11428 s.select(selections);
11429 });
11430 this.insert("", window, cx);
11431 });
11432 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11433 }
11434
11435 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11436 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11437 let item = self.cut_common(window, cx);
11438 cx.write_to_clipboard(item);
11439 }
11440
11441 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11442 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11443 self.change_selections(None, window, cx, |s| {
11444 s.move_with(|snapshot, sel| {
11445 if sel.is_empty() {
11446 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11447 }
11448 });
11449 });
11450 let item = self.cut_common(window, cx);
11451 cx.set_global(KillRing(item))
11452 }
11453
11454 pub fn kill_ring_yank(
11455 &mut self,
11456 _: &KillRingYank,
11457 window: &mut Window,
11458 cx: &mut Context<Self>,
11459 ) {
11460 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11461 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11462 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11463 (kill_ring.text().to_string(), kill_ring.metadata_json())
11464 } else {
11465 return;
11466 }
11467 } else {
11468 return;
11469 };
11470 self.do_paste(&text, metadata, false, window, cx);
11471 }
11472
11473 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11474 self.do_copy(true, cx);
11475 }
11476
11477 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11478 self.do_copy(false, cx);
11479 }
11480
11481 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11482 let selections = self.selections.all::<Point>(cx);
11483 let buffer = self.buffer.read(cx).read(cx);
11484 let mut text = String::new();
11485
11486 let mut clipboard_selections = Vec::with_capacity(selections.len());
11487 {
11488 let max_point = buffer.max_point();
11489 let mut is_first = true;
11490 for selection in &selections {
11491 let mut start = selection.start;
11492 let mut end = selection.end;
11493 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11494 if is_entire_line {
11495 start = Point::new(start.row, 0);
11496 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11497 }
11498
11499 let mut trimmed_selections = Vec::new();
11500 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11501 let row = MultiBufferRow(start.row);
11502 let first_indent = buffer.indent_size_for_line(row);
11503 if first_indent.len == 0 || start.column > first_indent.len {
11504 trimmed_selections.push(start..end);
11505 } else {
11506 trimmed_selections.push(
11507 Point::new(row.0, first_indent.len)
11508 ..Point::new(row.0, buffer.line_len(row)),
11509 );
11510 for row in start.row + 1..=end.row {
11511 let mut line_len = buffer.line_len(MultiBufferRow(row));
11512 if row == end.row {
11513 line_len = end.column;
11514 }
11515 if line_len == 0 {
11516 trimmed_selections
11517 .push(Point::new(row, 0)..Point::new(row, line_len));
11518 continue;
11519 }
11520 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11521 if row_indent_size.len >= first_indent.len {
11522 trimmed_selections.push(
11523 Point::new(row, first_indent.len)..Point::new(row, line_len),
11524 );
11525 } else {
11526 trimmed_selections.clear();
11527 trimmed_selections.push(start..end);
11528 break;
11529 }
11530 }
11531 }
11532 } else {
11533 trimmed_selections.push(start..end);
11534 }
11535
11536 for trimmed_range in trimmed_selections {
11537 if is_first {
11538 is_first = false;
11539 } else {
11540 text += "\n";
11541 }
11542 let mut len = 0;
11543 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11544 text.push_str(chunk);
11545 len += chunk.len();
11546 }
11547 clipboard_selections.push(ClipboardSelection {
11548 len,
11549 is_entire_line,
11550 first_line_indent: buffer
11551 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11552 .len,
11553 });
11554 }
11555 }
11556 }
11557
11558 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11559 text,
11560 clipboard_selections,
11561 ));
11562 }
11563
11564 pub fn do_paste(
11565 &mut self,
11566 text: &String,
11567 clipboard_selections: Option<Vec<ClipboardSelection>>,
11568 handle_entire_lines: bool,
11569 window: &mut Window,
11570 cx: &mut Context<Self>,
11571 ) {
11572 if self.read_only(cx) {
11573 return;
11574 }
11575
11576 let clipboard_text = Cow::Borrowed(text);
11577
11578 self.transact(window, cx, |this, window, cx| {
11579 if let Some(mut clipboard_selections) = clipboard_selections {
11580 let old_selections = this.selections.all::<usize>(cx);
11581 let all_selections_were_entire_line =
11582 clipboard_selections.iter().all(|s| s.is_entire_line);
11583 let first_selection_indent_column =
11584 clipboard_selections.first().map(|s| s.first_line_indent);
11585 if clipboard_selections.len() != old_selections.len() {
11586 clipboard_selections.drain(..);
11587 }
11588 let cursor_offset = this.selections.last::<usize>(cx).head();
11589 let mut auto_indent_on_paste = true;
11590
11591 this.buffer.update(cx, |buffer, cx| {
11592 let snapshot = buffer.read(cx);
11593 auto_indent_on_paste = snapshot
11594 .language_settings_at(cursor_offset, cx)
11595 .auto_indent_on_paste;
11596
11597 let mut start_offset = 0;
11598 let mut edits = Vec::new();
11599 let mut original_indent_columns = Vec::new();
11600 for (ix, selection) in old_selections.iter().enumerate() {
11601 let to_insert;
11602 let entire_line;
11603 let original_indent_column;
11604 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11605 let end_offset = start_offset + clipboard_selection.len;
11606 to_insert = &clipboard_text[start_offset..end_offset];
11607 entire_line = clipboard_selection.is_entire_line;
11608 start_offset = end_offset + 1;
11609 original_indent_column = Some(clipboard_selection.first_line_indent);
11610 } else {
11611 to_insert = clipboard_text.as_str();
11612 entire_line = all_selections_were_entire_line;
11613 original_indent_column = first_selection_indent_column
11614 }
11615
11616 // If the corresponding selection was empty when this slice of the
11617 // clipboard text was written, then the entire line containing the
11618 // selection was copied. If this selection is also currently empty,
11619 // then paste the line before the current line of the buffer.
11620 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11621 let column = selection.start.to_point(&snapshot).column as usize;
11622 let line_start = selection.start - column;
11623 line_start..line_start
11624 } else {
11625 selection.range()
11626 };
11627
11628 edits.push((range, to_insert));
11629 original_indent_columns.push(original_indent_column);
11630 }
11631 drop(snapshot);
11632
11633 buffer.edit(
11634 edits,
11635 if auto_indent_on_paste {
11636 Some(AutoindentMode::Block {
11637 original_indent_columns,
11638 })
11639 } else {
11640 None
11641 },
11642 cx,
11643 );
11644 });
11645
11646 let selections = this.selections.all::<usize>(cx);
11647 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11648 s.select(selections)
11649 });
11650 } else {
11651 this.insert(&clipboard_text, window, cx);
11652 }
11653 });
11654 }
11655
11656 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11657 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11658 if let Some(item) = cx.read_from_clipboard() {
11659 let entries = item.entries();
11660
11661 match entries.first() {
11662 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11663 // of all the pasted entries.
11664 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11665 .do_paste(
11666 clipboard_string.text(),
11667 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11668 true,
11669 window,
11670 cx,
11671 ),
11672 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11673 }
11674 }
11675 }
11676
11677 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11678 if self.read_only(cx) {
11679 return;
11680 }
11681
11682 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11683
11684 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11685 if let Some((selections, _)) =
11686 self.selection_history.transaction(transaction_id).cloned()
11687 {
11688 self.change_selections(None, window, cx, |s| {
11689 s.select_anchors(selections.to_vec());
11690 });
11691 } else {
11692 log::error!(
11693 "No entry in selection_history found for undo. \
11694 This may correspond to a bug where undo does not update the selection. \
11695 If this is occurring, please add details to \
11696 https://github.com/zed-industries/zed/issues/22692"
11697 );
11698 }
11699 self.request_autoscroll(Autoscroll::fit(), cx);
11700 self.unmark_text(window, cx);
11701 self.refresh_inline_completion(true, false, window, cx);
11702 cx.emit(EditorEvent::Edited { transaction_id });
11703 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11704 }
11705 }
11706
11707 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11708 if self.read_only(cx) {
11709 return;
11710 }
11711
11712 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11713
11714 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11715 if let Some((_, Some(selections))) =
11716 self.selection_history.transaction(transaction_id).cloned()
11717 {
11718 self.change_selections(None, window, cx, |s| {
11719 s.select_anchors(selections.to_vec());
11720 });
11721 } else {
11722 log::error!(
11723 "No entry in selection_history found for redo. \
11724 This may correspond to a bug where undo does not update the selection. \
11725 If this is occurring, please add details to \
11726 https://github.com/zed-industries/zed/issues/22692"
11727 );
11728 }
11729 self.request_autoscroll(Autoscroll::fit(), cx);
11730 self.unmark_text(window, cx);
11731 self.refresh_inline_completion(true, false, window, cx);
11732 cx.emit(EditorEvent::Edited { transaction_id });
11733 }
11734 }
11735
11736 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11737 self.buffer
11738 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11739 }
11740
11741 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11742 self.buffer
11743 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11744 }
11745
11746 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11747 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11748 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11749 s.move_with(|map, selection| {
11750 let cursor = if selection.is_empty() {
11751 movement::left(map, selection.start)
11752 } else {
11753 selection.start
11754 };
11755 selection.collapse_to(cursor, SelectionGoal::None);
11756 });
11757 })
11758 }
11759
11760 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11761 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11762 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11763 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11764 })
11765 }
11766
11767 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11768 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11769 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11770 s.move_with(|map, selection| {
11771 let cursor = if selection.is_empty() {
11772 movement::right(map, selection.end)
11773 } else {
11774 selection.end
11775 };
11776 selection.collapse_to(cursor, SelectionGoal::None)
11777 });
11778 })
11779 }
11780
11781 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11782 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11783 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11784 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11785 })
11786 }
11787
11788 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11789 if self.take_rename(true, window, cx).is_some() {
11790 return;
11791 }
11792
11793 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11794 cx.propagate();
11795 return;
11796 }
11797
11798 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11799
11800 let text_layout_details = &self.text_layout_details(window);
11801 let selection_count = self.selections.count();
11802 let first_selection = self.selections.first_anchor();
11803
11804 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11805 s.move_with(|map, selection| {
11806 if !selection.is_empty() {
11807 selection.goal = SelectionGoal::None;
11808 }
11809 let (cursor, goal) = movement::up(
11810 map,
11811 selection.start,
11812 selection.goal,
11813 false,
11814 text_layout_details,
11815 );
11816 selection.collapse_to(cursor, goal);
11817 });
11818 });
11819
11820 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11821 {
11822 cx.propagate();
11823 }
11824 }
11825
11826 pub fn move_up_by_lines(
11827 &mut self,
11828 action: &MoveUpByLines,
11829 window: &mut Window,
11830 cx: &mut Context<Self>,
11831 ) {
11832 if self.take_rename(true, window, cx).is_some() {
11833 return;
11834 }
11835
11836 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11837 cx.propagate();
11838 return;
11839 }
11840
11841 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11842
11843 let text_layout_details = &self.text_layout_details(window);
11844
11845 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11846 s.move_with(|map, selection| {
11847 if !selection.is_empty() {
11848 selection.goal = SelectionGoal::None;
11849 }
11850 let (cursor, goal) = movement::up_by_rows(
11851 map,
11852 selection.start,
11853 action.lines,
11854 selection.goal,
11855 false,
11856 text_layout_details,
11857 );
11858 selection.collapse_to(cursor, goal);
11859 });
11860 })
11861 }
11862
11863 pub fn move_down_by_lines(
11864 &mut self,
11865 action: &MoveDownByLines,
11866 window: &mut Window,
11867 cx: &mut Context<Self>,
11868 ) {
11869 if self.take_rename(true, window, cx).is_some() {
11870 return;
11871 }
11872
11873 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11874 cx.propagate();
11875 return;
11876 }
11877
11878 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11879
11880 let text_layout_details = &self.text_layout_details(window);
11881
11882 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11883 s.move_with(|map, selection| {
11884 if !selection.is_empty() {
11885 selection.goal = SelectionGoal::None;
11886 }
11887 let (cursor, goal) = movement::down_by_rows(
11888 map,
11889 selection.start,
11890 action.lines,
11891 selection.goal,
11892 false,
11893 text_layout_details,
11894 );
11895 selection.collapse_to(cursor, goal);
11896 });
11897 })
11898 }
11899
11900 pub fn select_down_by_lines(
11901 &mut self,
11902 action: &SelectDownByLines,
11903 window: &mut Window,
11904 cx: &mut Context<Self>,
11905 ) {
11906 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11907 let text_layout_details = &self.text_layout_details(window);
11908 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11909 s.move_heads_with(|map, head, goal| {
11910 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11911 })
11912 })
11913 }
11914
11915 pub fn select_up_by_lines(
11916 &mut self,
11917 action: &SelectUpByLines,
11918 window: &mut Window,
11919 cx: &mut Context<Self>,
11920 ) {
11921 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11922 let text_layout_details = &self.text_layout_details(window);
11923 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11924 s.move_heads_with(|map, head, goal| {
11925 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11926 })
11927 })
11928 }
11929
11930 pub fn select_page_up(
11931 &mut self,
11932 _: &SelectPageUp,
11933 window: &mut Window,
11934 cx: &mut Context<Self>,
11935 ) {
11936 let Some(row_count) = self.visible_row_count() else {
11937 return;
11938 };
11939
11940 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11941
11942 let text_layout_details = &self.text_layout_details(window);
11943
11944 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11945 s.move_heads_with(|map, head, goal| {
11946 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11947 })
11948 })
11949 }
11950
11951 pub fn move_page_up(
11952 &mut self,
11953 action: &MovePageUp,
11954 window: &mut Window,
11955 cx: &mut Context<Self>,
11956 ) {
11957 if self.take_rename(true, window, cx).is_some() {
11958 return;
11959 }
11960
11961 if self
11962 .context_menu
11963 .borrow_mut()
11964 .as_mut()
11965 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
11966 .unwrap_or(false)
11967 {
11968 return;
11969 }
11970
11971 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11972 cx.propagate();
11973 return;
11974 }
11975
11976 let Some(row_count) = self.visible_row_count() else {
11977 return;
11978 };
11979
11980 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11981
11982 let autoscroll = if action.center_cursor {
11983 Autoscroll::center()
11984 } else {
11985 Autoscroll::fit()
11986 };
11987
11988 let text_layout_details = &self.text_layout_details(window);
11989
11990 self.change_selections(Some(autoscroll), window, cx, |s| {
11991 s.move_with(|map, selection| {
11992 if !selection.is_empty() {
11993 selection.goal = SelectionGoal::None;
11994 }
11995 let (cursor, goal) = movement::up_by_rows(
11996 map,
11997 selection.end,
11998 row_count,
11999 selection.goal,
12000 false,
12001 text_layout_details,
12002 );
12003 selection.collapse_to(cursor, goal);
12004 });
12005 });
12006 }
12007
12008 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12009 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12010 let text_layout_details = &self.text_layout_details(window);
12011 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12012 s.move_heads_with(|map, head, goal| {
12013 movement::up(map, head, goal, false, text_layout_details)
12014 })
12015 })
12016 }
12017
12018 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12019 self.take_rename(true, window, cx);
12020
12021 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12022 cx.propagate();
12023 return;
12024 }
12025
12026 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12027
12028 let text_layout_details = &self.text_layout_details(window);
12029 let selection_count = self.selections.count();
12030 let first_selection = self.selections.first_anchor();
12031
12032 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12033 s.move_with(|map, selection| {
12034 if !selection.is_empty() {
12035 selection.goal = SelectionGoal::None;
12036 }
12037 let (cursor, goal) = movement::down(
12038 map,
12039 selection.end,
12040 selection.goal,
12041 false,
12042 text_layout_details,
12043 );
12044 selection.collapse_to(cursor, goal);
12045 });
12046 });
12047
12048 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12049 {
12050 cx.propagate();
12051 }
12052 }
12053
12054 pub fn select_page_down(
12055 &mut self,
12056 _: &SelectPageDown,
12057 window: &mut Window,
12058 cx: &mut Context<Self>,
12059 ) {
12060 let Some(row_count) = self.visible_row_count() else {
12061 return;
12062 };
12063
12064 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12065
12066 let text_layout_details = &self.text_layout_details(window);
12067
12068 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12069 s.move_heads_with(|map, head, goal| {
12070 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12071 })
12072 })
12073 }
12074
12075 pub fn move_page_down(
12076 &mut self,
12077 action: &MovePageDown,
12078 window: &mut Window,
12079 cx: &mut Context<Self>,
12080 ) {
12081 if self.take_rename(true, window, cx).is_some() {
12082 return;
12083 }
12084
12085 if self
12086 .context_menu
12087 .borrow_mut()
12088 .as_mut()
12089 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12090 .unwrap_or(false)
12091 {
12092 return;
12093 }
12094
12095 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12096 cx.propagate();
12097 return;
12098 }
12099
12100 let Some(row_count) = self.visible_row_count() else {
12101 return;
12102 };
12103
12104 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12105
12106 let autoscroll = if action.center_cursor {
12107 Autoscroll::center()
12108 } else {
12109 Autoscroll::fit()
12110 };
12111
12112 let text_layout_details = &self.text_layout_details(window);
12113 self.change_selections(Some(autoscroll), window, cx, |s| {
12114 s.move_with(|map, selection| {
12115 if !selection.is_empty() {
12116 selection.goal = SelectionGoal::None;
12117 }
12118 let (cursor, goal) = movement::down_by_rows(
12119 map,
12120 selection.end,
12121 row_count,
12122 selection.goal,
12123 false,
12124 text_layout_details,
12125 );
12126 selection.collapse_to(cursor, goal);
12127 });
12128 });
12129 }
12130
12131 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12132 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12133 let text_layout_details = &self.text_layout_details(window);
12134 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12135 s.move_heads_with(|map, head, goal| {
12136 movement::down(map, head, goal, false, text_layout_details)
12137 })
12138 });
12139 }
12140
12141 pub fn context_menu_first(
12142 &mut self,
12143 _: &ContextMenuFirst,
12144 window: &mut Window,
12145 cx: &mut Context<Self>,
12146 ) {
12147 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12148 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12149 }
12150 }
12151
12152 pub fn context_menu_prev(
12153 &mut self,
12154 _: &ContextMenuPrevious,
12155 window: &mut Window,
12156 cx: &mut Context<Self>,
12157 ) {
12158 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12159 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12160 }
12161 }
12162
12163 pub fn context_menu_next(
12164 &mut self,
12165 _: &ContextMenuNext,
12166 window: &mut Window,
12167 cx: &mut Context<Self>,
12168 ) {
12169 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12170 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12171 }
12172 }
12173
12174 pub fn context_menu_last(
12175 &mut self,
12176 _: &ContextMenuLast,
12177 window: &mut Window,
12178 cx: &mut Context<Self>,
12179 ) {
12180 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12181 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12182 }
12183 }
12184
12185 pub fn move_to_previous_word_start(
12186 &mut self,
12187 _: &MoveToPreviousWordStart,
12188 window: &mut Window,
12189 cx: &mut Context<Self>,
12190 ) {
12191 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12192 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12193 s.move_cursors_with(|map, head, _| {
12194 (
12195 movement::previous_word_start(map, head),
12196 SelectionGoal::None,
12197 )
12198 });
12199 })
12200 }
12201
12202 pub fn move_to_previous_subword_start(
12203 &mut self,
12204 _: &MoveToPreviousSubwordStart,
12205 window: &mut Window,
12206 cx: &mut Context<Self>,
12207 ) {
12208 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12209 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12210 s.move_cursors_with(|map, head, _| {
12211 (
12212 movement::previous_subword_start(map, head),
12213 SelectionGoal::None,
12214 )
12215 });
12216 })
12217 }
12218
12219 pub fn select_to_previous_word_start(
12220 &mut self,
12221 _: &SelectToPreviousWordStart,
12222 window: &mut Window,
12223 cx: &mut Context<Self>,
12224 ) {
12225 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12226 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12227 s.move_heads_with(|map, head, _| {
12228 (
12229 movement::previous_word_start(map, head),
12230 SelectionGoal::None,
12231 )
12232 });
12233 })
12234 }
12235
12236 pub fn select_to_previous_subword_start(
12237 &mut self,
12238 _: &SelectToPreviousSubwordStart,
12239 window: &mut Window,
12240 cx: &mut Context<Self>,
12241 ) {
12242 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12243 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12244 s.move_heads_with(|map, head, _| {
12245 (
12246 movement::previous_subword_start(map, head),
12247 SelectionGoal::None,
12248 )
12249 });
12250 })
12251 }
12252
12253 pub fn delete_to_previous_word_start(
12254 &mut self,
12255 action: &DeleteToPreviousWordStart,
12256 window: &mut Window,
12257 cx: &mut Context<Self>,
12258 ) {
12259 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12260 self.transact(window, cx, |this, window, cx| {
12261 this.select_autoclose_pair(window, cx);
12262 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12263 s.move_with(|map, selection| {
12264 if selection.is_empty() {
12265 let cursor = if action.ignore_newlines {
12266 movement::previous_word_start(map, selection.head())
12267 } else {
12268 movement::previous_word_start_or_newline(map, selection.head())
12269 };
12270 selection.set_head(cursor, SelectionGoal::None);
12271 }
12272 });
12273 });
12274 this.insert("", window, cx);
12275 });
12276 }
12277
12278 pub fn delete_to_previous_subword_start(
12279 &mut self,
12280 _: &DeleteToPreviousSubwordStart,
12281 window: &mut Window,
12282 cx: &mut Context<Self>,
12283 ) {
12284 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12285 self.transact(window, cx, |this, window, cx| {
12286 this.select_autoclose_pair(window, cx);
12287 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12288 s.move_with(|map, selection| {
12289 if selection.is_empty() {
12290 let cursor = movement::previous_subword_start(map, selection.head());
12291 selection.set_head(cursor, SelectionGoal::None);
12292 }
12293 });
12294 });
12295 this.insert("", window, cx);
12296 });
12297 }
12298
12299 pub fn move_to_next_word_end(
12300 &mut self,
12301 _: &MoveToNextWordEnd,
12302 window: &mut Window,
12303 cx: &mut Context<Self>,
12304 ) {
12305 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12306 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12307 s.move_cursors_with(|map, head, _| {
12308 (movement::next_word_end(map, head), SelectionGoal::None)
12309 });
12310 })
12311 }
12312
12313 pub fn move_to_next_subword_end(
12314 &mut self,
12315 _: &MoveToNextSubwordEnd,
12316 window: &mut Window,
12317 cx: &mut Context<Self>,
12318 ) {
12319 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12320 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12321 s.move_cursors_with(|map, head, _| {
12322 (movement::next_subword_end(map, head), SelectionGoal::None)
12323 });
12324 })
12325 }
12326
12327 pub fn select_to_next_word_end(
12328 &mut self,
12329 _: &SelectToNextWordEnd,
12330 window: &mut Window,
12331 cx: &mut Context<Self>,
12332 ) {
12333 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12334 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12335 s.move_heads_with(|map, head, _| {
12336 (movement::next_word_end(map, head), SelectionGoal::None)
12337 });
12338 })
12339 }
12340
12341 pub fn select_to_next_subword_end(
12342 &mut self,
12343 _: &SelectToNextSubwordEnd,
12344 window: &mut Window,
12345 cx: &mut Context<Self>,
12346 ) {
12347 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12348 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12349 s.move_heads_with(|map, head, _| {
12350 (movement::next_subword_end(map, head), SelectionGoal::None)
12351 });
12352 })
12353 }
12354
12355 pub fn delete_to_next_word_end(
12356 &mut self,
12357 action: &DeleteToNextWordEnd,
12358 window: &mut Window,
12359 cx: &mut Context<Self>,
12360 ) {
12361 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12362 self.transact(window, cx, |this, window, cx| {
12363 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12364 s.move_with(|map, selection| {
12365 if selection.is_empty() {
12366 let cursor = if action.ignore_newlines {
12367 movement::next_word_end(map, selection.head())
12368 } else {
12369 movement::next_word_end_or_newline(map, selection.head())
12370 };
12371 selection.set_head(cursor, SelectionGoal::None);
12372 }
12373 });
12374 });
12375 this.insert("", window, cx);
12376 });
12377 }
12378
12379 pub fn delete_to_next_subword_end(
12380 &mut self,
12381 _: &DeleteToNextSubwordEnd,
12382 window: &mut Window,
12383 cx: &mut Context<Self>,
12384 ) {
12385 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12386 self.transact(window, cx, |this, window, cx| {
12387 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12388 s.move_with(|map, selection| {
12389 if selection.is_empty() {
12390 let cursor = movement::next_subword_end(map, selection.head());
12391 selection.set_head(cursor, SelectionGoal::None);
12392 }
12393 });
12394 });
12395 this.insert("", window, cx);
12396 });
12397 }
12398
12399 pub fn move_to_beginning_of_line(
12400 &mut self,
12401 action: &MoveToBeginningOfLine,
12402 window: &mut Window,
12403 cx: &mut Context<Self>,
12404 ) {
12405 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12406 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12407 s.move_cursors_with(|map, head, _| {
12408 (
12409 movement::indented_line_beginning(
12410 map,
12411 head,
12412 action.stop_at_soft_wraps,
12413 action.stop_at_indent,
12414 ),
12415 SelectionGoal::None,
12416 )
12417 });
12418 })
12419 }
12420
12421 pub fn select_to_beginning_of_line(
12422 &mut self,
12423 action: &SelectToBeginningOfLine,
12424 window: &mut Window,
12425 cx: &mut Context<Self>,
12426 ) {
12427 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12428 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12429 s.move_heads_with(|map, head, _| {
12430 (
12431 movement::indented_line_beginning(
12432 map,
12433 head,
12434 action.stop_at_soft_wraps,
12435 action.stop_at_indent,
12436 ),
12437 SelectionGoal::None,
12438 )
12439 });
12440 });
12441 }
12442
12443 pub fn delete_to_beginning_of_line(
12444 &mut self,
12445 action: &DeleteToBeginningOfLine,
12446 window: &mut Window,
12447 cx: &mut Context<Self>,
12448 ) {
12449 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12450 self.transact(window, cx, |this, window, cx| {
12451 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12452 s.move_with(|_, selection| {
12453 selection.reversed = true;
12454 });
12455 });
12456
12457 this.select_to_beginning_of_line(
12458 &SelectToBeginningOfLine {
12459 stop_at_soft_wraps: false,
12460 stop_at_indent: action.stop_at_indent,
12461 },
12462 window,
12463 cx,
12464 );
12465 this.backspace(&Backspace, window, cx);
12466 });
12467 }
12468
12469 pub fn move_to_end_of_line(
12470 &mut self,
12471 action: &MoveToEndOfLine,
12472 window: &mut Window,
12473 cx: &mut Context<Self>,
12474 ) {
12475 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12476 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12477 s.move_cursors_with(|map, head, _| {
12478 (
12479 movement::line_end(map, head, action.stop_at_soft_wraps),
12480 SelectionGoal::None,
12481 )
12482 });
12483 })
12484 }
12485
12486 pub fn select_to_end_of_line(
12487 &mut self,
12488 action: &SelectToEndOfLine,
12489 window: &mut Window,
12490 cx: &mut Context<Self>,
12491 ) {
12492 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12493 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12494 s.move_heads_with(|map, head, _| {
12495 (
12496 movement::line_end(map, head, action.stop_at_soft_wraps),
12497 SelectionGoal::None,
12498 )
12499 });
12500 })
12501 }
12502
12503 pub fn delete_to_end_of_line(
12504 &mut self,
12505 _: &DeleteToEndOfLine,
12506 window: &mut Window,
12507 cx: &mut Context<Self>,
12508 ) {
12509 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12510 self.transact(window, cx, |this, window, cx| {
12511 this.select_to_end_of_line(
12512 &SelectToEndOfLine {
12513 stop_at_soft_wraps: false,
12514 },
12515 window,
12516 cx,
12517 );
12518 this.delete(&Delete, window, cx);
12519 });
12520 }
12521
12522 pub fn cut_to_end_of_line(
12523 &mut self,
12524 _: &CutToEndOfLine,
12525 window: &mut Window,
12526 cx: &mut Context<Self>,
12527 ) {
12528 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12529 self.transact(window, cx, |this, window, cx| {
12530 this.select_to_end_of_line(
12531 &SelectToEndOfLine {
12532 stop_at_soft_wraps: false,
12533 },
12534 window,
12535 cx,
12536 );
12537 this.cut(&Cut, window, cx);
12538 });
12539 }
12540
12541 pub fn move_to_start_of_paragraph(
12542 &mut self,
12543 _: &MoveToStartOfParagraph,
12544 window: &mut Window,
12545 cx: &mut Context<Self>,
12546 ) {
12547 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12548 cx.propagate();
12549 return;
12550 }
12551 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12552 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12553 s.move_with(|map, selection| {
12554 selection.collapse_to(
12555 movement::start_of_paragraph(map, selection.head(), 1),
12556 SelectionGoal::None,
12557 )
12558 });
12559 })
12560 }
12561
12562 pub fn move_to_end_of_paragraph(
12563 &mut self,
12564 _: &MoveToEndOfParagraph,
12565 window: &mut Window,
12566 cx: &mut Context<Self>,
12567 ) {
12568 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12569 cx.propagate();
12570 return;
12571 }
12572 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12573 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12574 s.move_with(|map, selection| {
12575 selection.collapse_to(
12576 movement::end_of_paragraph(map, selection.head(), 1),
12577 SelectionGoal::None,
12578 )
12579 });
12580 })
12581 }
12582
12583 pub fn select_to_start_of_paragraph(
12584 &mut self,
12585 _: &SelectToStartOfParagraph,
12586 window: &mut Window,
12587 cx: &mut Context<Self>,
12588 ) {
12589 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12590 cx.propagate();
12591 return;
12592 }
12593 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12594 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12595 s.move_heads_with(|map, head, _| {
12596 (
12597 movement::start_of_paragraph(map, head, 1),
12598 SelectionGoal::None,
12599 )
12600 });
12601 })
12602 }
12603
12604 pub fn select_to_end_of_paragraph(
12605 &mut self,
12606 _: &SelectToEndOfParagraph,
12607 window: &mut Window,
12608 cx: &mut Context<Self>,
12609 ) {
12610 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12611 cx.propagate();
12612 return;
12613 }
12614 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12615 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12616 s.move_heads_with(|map, head, _| {
12617 (
12618 movement::end_of_paragraph(map, head, 1),
12619 SelectionGoal::None,
12620 )
12621 });
12622 })
12623 }
12624
12625 pub fn move_to_start_of_excerpt(
12626 &mut self,
12627 _: &MoveToStartOfExcerpt,
12628 window: &mut Window,
12629 cx: &mut Context<Self>,
12630 ) {
12631 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12632 cx.propagate();
12633 return;
12634 }
12635 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12636 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12637 s.move_with(|map, selection| {
12638 selection.collapse_to(
12639 movement::start_of_excerpt(
12640 map,
12641 selection.head(),
12642 workspace::searchable::Direction::Prev,
12643 ),
12644 SelectionGoal::None,
12645 )
12646 });
12647 })
12648 }
12649
12650 pub fn move_to_start_of_next_excerpt(
12651 &mut self,
12652 _: &MoveToStartOfNextExcerpt,
12653 window: &mut Window,
12654 cx: &mut Context<Self>,
12655 ) {
12656 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12657 cx.propagate();
12658 return;
12659 }
12660
12661 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12662 s.move_with(|map, selection| {
12663 selection.collapse_to(
12664 movement::start_of_excerpt(
12665 map,
12666 selection.head(),
12667 workspace::searchable::Direction::Next,
12668 ),
12669 SelectionGoal::None,
12670 )
12671 });
12672 })
12673 }
12674
12675 pub fn move_to_end_of_excerpt(
12676 &mut self,
12677 _: &MoveToEndOfExcerpt,
12678 window: &mut Window,
12679 cx: &mut Context<Self>,
12680 ) {
12681 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12682 cx.propagate();
12683 return;
12684 }
12685 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12686 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12687 s.move_with(|map, selection| {
12688 selection.collapse_to(
12689 movement::end_of_excerpt(
12690 map,
12691 selection.head(),
12692 workspace::searchable::Direction::Next,
12693 ),
12694 SelectionGoal::None,
12695 )
12696 });
12697 })
12698 }
12699
12700 pub fn move_to_end_of_previous_excerpt(
12701 &mut self,
12702 _: &MoveToEndOfPreviousExcerpt,
12703 window: &mut Window,
12704 cx: &mut Context<Self>,
12705 ) {
12706 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12707 cx.propagate();
12708 return;
12709 }
12710 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12711 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12712 s.move_with(|map, selection| {
12713 selection.collapse_to(
12714 movement::end_of_excerpt(
12715 map,
12716 selection.head(),
12717 workspace::searchable::Direction::Prev,
12718 ),
12719 SelectionGoal::None,
12720 )
12721 });
12722 })
12723 }
12724
12725 pub fn select_to_start_of_excerpt(
12726 &mut self,
12727 _: &SelectToStartOfExcerpt,
12728 window: &mut Window,
12729 cx: &mut Context<Self>,
12730 ) {
12731 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12732 cx.propagate();
12733 return;
12734 }
12735 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12736 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12737 s.move_heads_with(|map, head, _| {
12738 (
12739 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12740 SelectionGoal::None,
12741 )
12742 });
12743 })
12744 }
12745
12746 pub fn select_to_start_of_next_excerpt(
12747 &mut self,
12748 _: &SelectToStartOfNextExcerpt,
12749 window: &mut Window,
12750 cx: &mut Context<Self>,
12751 ) {
12752 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12753 cx.propagate();
12754 return;
12755 }
12756 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12757 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12758 s.move_heads_with(|map, head, _| {
12759 (
12760 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12761 SelectionGoal::None,
12762 )
12763 });
12764 })
12765 }
12766
12767 pub fn select_to_end_of_excerpt(
12768 &mut self,
12769 _: &SelectToEndOfExcerpt,
12770 window: &mut Window,
12771 cx: &mut Context<Self>,
12772 ) {
12773 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12774 cx.propagate();
12775 return;
12776 }
12777 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12778 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12779 s.move_heads_with(|map, head, _| {
12780 (
12781 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12782 SelectionGoal::None,
12783 )
12784 });
12785 })
12786 }
12787
12788 pub fn select_to_end_of_previous_excerpt(
12789 &mut self,
12790 _: &SelectToEndOfPreviousExcerpt,
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_heads_with(|map, head, _| {
12801 (
12802 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12803 SelectionGoal::None,
12804 )
12805 });
12806 })
12807 }
12808
12809 pub fn move_to_beginning(
12810 &mut self,
12811 _: &MoveToBeginning,
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.select_ranges(vec![0..0]);
12822 });
12823 }
12824
12825 pub fn select_to_beginning(
12826 &mut self,
12827 _: &SelectToBeginning,
12828 window: &mut Window,
12829 cx: &mut Context<Self>,
12830 ) {
12831 let mut selection = self.selections.last::<Point>(cx);
12832 selection.set_head(Point::zero(), SelectionGoal::None);
12833 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12834 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12835 s.select(vec![selection]);
12836 });
12837 }
12838
12839 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12840 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12841 cx.propagate();
12842 return;
12843 }
12844 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12845 let cursor = self.buffer.read(cx).read(cx).len();
12846 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12847 s.select_ranges(vec![cursor..cursor])
12848 });
12849 }
12850
12851 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12852 self.nav_history = nav_history;
12853 }
12854
12855 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12856 self.nav_history.as_ref()
12857 }
12858
12859 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12860 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12861 }
12862
12863 fn push_to_nav_history(
12864 &mut self,
12865 cursor_anchor: Anchor,
12866 new_position: Option<Point>,
12867 is_deactivate: bool,
12868 cx: &mut Context<Self>,
12869 ) {
12870 if let Some(nav_history) = self.nav_history.as_mut() {
12871 let buffer = self.buffer.read(cx).read(cx);
12872 let cursor_position = cursor_anchor.to_point(&buffer);
12873 let scroll_state = self.scroll_manager.anchor();
12874 let scroll_top_row = scroll_state.top_row(&buffer);
12875 drop(buffer);
12876
12877 if let Some(new_position) = new_position {
12878 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12879 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12880 return;
12881 }
12882 }
12883
12884 nav_history.push(
12885 Some(NavigationData {
12886 cursor_anchor,
12887 cursor_position,
12888 scroll_anchor: scroll_state,
12889 scroll_top_row,
12890 }),
12891 cx,
12892 );
12893 cx.emit(EditorEvent::PushedToNavHistory {
12894 anchor: cursor_anchor,
12895 is_deactivate,
12896 })
12897 }
12898 }
12899
12900 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12901 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12902 let buffer = self.buffer.read(cx).snapshot(cx);
12903 let mut selection = self.selections.first::<usize>(cx);
12904 selection.set_head(buffer.len(), SelectionGoal::None);
12905 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12906 s.select(vec![selection]);
12907 });
12908 }
12909
12910 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12911 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12912 let end = self.buffer.read(cx).read(cx).len();
12913 self.change_selections(None, window, cx, |s| {
12914 s.select_ranges(vec![0..end]);
12915 });
12916 }
12917
12918 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12919 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12920 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12921 let mut selections = self.selections.all::<Point>(cx);
12922 let max_point = display_map.buffer_snapshot.max_point();
12923 for selection in &mut selections {
12924 let rows = selection.spanned_rows(true, &display_map);
12925 selection.start = Point::new(rows.start.0, 0);
12926 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12927 selection.reversed = false;
12928 }
12929 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12930 s.select(selections);
12931 });
12932 }
12933
12934 pub fn split_selection_into_lines(
12935 &mut self,
12936 _: &SplitSelectionIntoLines,
12937 window: &mut Window,
12938 cx: &mut Context<Self>,
12939 ) {
12940 let selections = self
12941 .selections
12942 .all::<Point>(cx)
12943 .into_iter()
12944 .map(|selection| selection.start..selection.end)
12945 .collect::<Vec<_>>();
12946 self.unfold_ranges(&selections, true, true, cx);
12947
12948 let mut new_selection_ranges = Vec::new();
12949 {
12950 let buffer = self.buffer.read(cx).read(cx);
12951 for selection in selections {
12952 for row in selection.start.row..selection.end.row {
12953 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12954 new_selection_ranges.push(cursor..cursor);
12955 }
12956
12957 let is_multiline_selection = selection.start.row != selection.end.row;
12958 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12959 // so this action feels more ergonomic when paired with other selection operations
12960 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12961 if !should_skip_last {
12962 new_selection_ranges.push(selection.end..selection.end);
12963 }
12964 }
12965 }
12966 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12967 s.select_ranges(new_selection_ranges);
12968 });
12969 }
12970
12971 pub fn add_selection_above(
12972 &mut self,
12973 _: &AddSelectionAbove,
12974 window: &mut Window,
12975 cx: &mut Context<Self>,
12976 ) {
12977 self.add_selection(true, window, cx);
12978 }
12979
12980 pub fn add_selection_below(
12981 &mut self,
12982 _: &AddSelectionBelow,
12983 window: &mut Window,
12984 cx: &mut Context<Self>,
12985 ) {
12986 self.add_selection(false, window, cx);
12987 }
12988
12989 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12990 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12991
12992 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12993 let all_selections = self.selections.all::<Point>(cx);
12994 let text_layout_details = self.text_layout_details(window);
12995
12996 let (mut columnar_selections, new_selections_to_columnarize) = {
12997 if let Some(state) = self.add_selections_state.as_ref() {
12998 let columnar_selection_ids: HashSet<_> = state
12999 .groups
13000 .iter()
13001 .flat_map(|group| group.stack.iter())
13002 .copied()
13003 .collect();
13004
13005 all_selections
13006 .into_iter()
13007 .partition(|s| columnar_selection_ids.contains(&s.id))
13008 } else {
13009 (Vec::new(), all_selections)
13010 }
13011 };
13012
13013 let mut state = self
13014 .add_selections_state
13015 .take()
13016 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13017
13018 for selection in new_selections_to_columnarize {
13019 let range = selection.display_range(&display_map).sorted();
13020 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13021 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13022 let positions = start_x.min(end_x)..start_x.max(end_x);
13023 let mut stack = Vec::new();
13024 for row in range.start.row().0..=range.end.row().0 {
13025 if let Some(selection) = self.selections.build_columnar_selection(
13026 &display_map,
13027 DisplayRow(row),
13028 &positions,
13029 selection.reversed,
13030 &text_layout_details,
13031 ) {
13032 stack.push(selection.id);
13033 columnar_selections.push(selection);
13034 }
13035 }
13036 if !stack.is_empty() {
13037 if above {
13038 stack.reverse();
13039 }
13040 state.groups.push(AddSelectionsGroup { above, stack });
13041 }
13042 }
13043
13044 let mut final_selections = Vec::new();
13045 let end_row = if above {
13046 DisplayRow(0)
13047 } else {
13048 display_map.max_point().row()
13049 };
13050
13051 let mut last_added_item_per_group = HashMap::default();
13052 for group in state.groups.iter_mut() {
13053 if let Some(last_id) = group.stack.last() {
13054 last_added_item_per_group.insert(*last_id, group);
13055 }
13056 }
13057
13058 for selection in columnar_selections {
13059 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13060 if above == group.above {
13061 let range = selection.display_range(&display_map).sorted();
13062 debug_assert_eq!(range.start.row(), range.end.row());
13063 let mut row = range.start.row();
13064 let positions =
13065 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13066 px(start)..px(end)
13067 } else {
13068 let start_x =
13069 display_map.x_for_display_point(range.start, &text_layout_details);
13070 let end_x =
13071 display_map.x_for_display_point(range.end, &text_layout_details);
13072 start_x.min(end_x)..start_x.max(end_x)
13073 };
13074
13075 let mut maybe_new_selection = None;
13076 while row != end_row {
13077 if above {
13078 row.0 -= 1;
13079 } else {
13080 row.0 += 1;
13081 }
13082 if let Some(new_selection) = self.selections.build_columnar_selection(
13083 &display_map,
13084 row,
13085 &positions,
13086 selection.reversed,
13087 &text_layout_details,
13088 ) {
13089 maybe_new_selection = Some(new_selection);
13090 break;
13091 }
13092 }
13093
13094 if let Some(new_selection) = maybe_new_selection {
13095 group.stack.push(new_selection.id);
13096 if above {
13097 final_selections.push(new_selection);
13098 final_selections.push(selection);
13099 } else {
13100 final_selections.push(selection);
13101 final_selections.push(new_selection);
13102 }
13103 } else {
13104 final_selections.push(selection);
13105 }
13106 } else {
13107 group.stack.pop();
13108 }
13109 } else {
13110 final_selections.push(selection);
13111 }
13112 }
13113
13114 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13115 s.select(final_selections);
13116 });
13117
13118 let final_selection_ids: HashSet<_> = self
13119 .selections
13120 .all::<Point>(cx)
13121 .iter()
13122 .map(|s| s.id)
13123 .collect();
13124 state.groups.retain_mut(|group| {
13125 // selections might get merged above so we remove invalid items from stacks
13126 group.stack.retain(|id| final_selection_ids.contains(id));
13127
13128 // single selection in stack can be treated as initial state
13129 group.stack.len() > 1
13130 });
13131
13132 if !state.groups.is_empty() {
13133 self.add_selections_state = Some(state);
13134 }
13135 }
13136
13137 fn select_match_ranges(
13138 &mut self,
13139 range: Range<usize>,
13140 reversed: bool,
13141 replace_newest: bool,
13142 auto_scroll: Option<Autoscroll>,
13143 window: &mut Window,
13144 cx: &mut Context<Editor>,
13145 ) {
13146 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
13147 self.change_selections(auto_scroll, window, cx, |s| {
13148 if replace_newest {
13149 s.delete(s.newest_anchor().id);
13150 }
13151 if reversed {
13152 s.insert_range(range.end..range.start);
13153 } else {
13154 s.insert_range(range);
13155 }
13156 });
13157 }
13158
13159 pub fn select_next_match_internal(
13160 &mut self,
13161 display_map: &DisplaySnapshot,
13162 replace_newest: bool,
13163 autoscroll: Option<Autoscroll>,
13164 window: &mut Window,
13165 cx: &mut Context<Self>,
13166 ) -> Result<()> {
13167 let buffer = &display_map.buffer_snapshot;
13168 let mut selections = self.selections.all::<usize>(cx);
13169 if let Some(mut select_next_state) = self.select_next_state.take() {
13170 let query = &select_next_state.query;
13171 if !select_next_state.done {
13172 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13173 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13174 let mut next_selected_range = None;
13175
13176 let bytes_after_last_selection =
13177 buffer.bytes_in_range(last_selection.end..buffer.len());
13178 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13179 let query_matches = query
13180 .stream_find_iter(bytes_after_last_selection)
13181 .map(|result| (last_selection.end, result))
13182 .chain(
13183 query
13184 .stream_find_iter(bytes_before_first_selection)
13185 .map(|result| (0, result)),
13186 );
13187
13188 for (start_offset, query_match) in query_matches {
13189 let query_match = query_match.unwrap(); // can only fail due to I/O
13190 let offset_range =
13191 start_offset + query_match.start()..start_offset + query_match.end();
13192 let display_range = offset_range.start.to_display_point(display_map)
13193 ..offset_range.end.to_display_point(display_map);
13194
13195 if !select_next_state.wordwise
13196 || (!movement::is_inside_word(display_map, display_range.start)
13197 && !movement::is_inside_word(display_map, display_range.end))
13198 {
13199 // TODO: This is n^2, because we might check all the selections
13200 if !selections
13201 .iter()
13202 .any(|selection| selection.range().overlaps(&offset_range))
13203 {
13204 next_selected_range = Some(offset_range);
13205 break;
13206 }
13207 }
13208 }
13209
13210 if let Some(next_selected_range) = next_selected_range {
13211 self.select_match_ranges(
13212 next_selected_range,
13213 last_selection.reversed,
13214 replace_newest,
13215 autoscroll,
13216 window,
13217 cx,
13218 );
13219 } else {
13220 select_next_state.done = true;
13221 }
13222 }
13223
13224 self.select_next_state = Some(select_next_state);
13225 } else {
13226 let mut only_carets = true;
13227 let mut same_text_selected = true;
13228 let mut selected_text = None;
13229
13230 let mut selections_iter = selections.iter().peekable();
13231 while let Some(selection) = selections_iter.next() {
13232 if selection.start != selection.end {
13233 only_carets = false;
13234 }
13235
13236 if same_text_selected {
13237 if selected_text.is_none() {
13238 selected_text =
13239 Some(buffer.text_for_range(selection.range()).collect::<String>());
13240 }
13241
13242 if let Some(next_selection) = selections_iter.peek() {
13243 if next_selection.range().len() == selection.range().len() {
13244 let next_selected_text = buffer
13245 .text_for_range(next_selection.range())
13246 .collect::<String>();
13247 if Some(next_selected_text) != selected_text {
13248 same_text_selected = false;
13249 selected_text = None;
13250 }
13251 } else {
13252 same_text_selected = false;
13253 selected_text = None;
13254 }
13255 }
13256 }
13257 }
13258
13259 if only_carets {
13260 for selection in &mut selections {
13261 let word_range = movement::surrounding_word(
13262 display_map,
13263 selection.start.to_display_point(display_map),
13264 );
13265 selection.start = word_range.start.to_offset(display_map, Bias::Left);
13266 selection.end = word_range.end.to_offset(display_map, Bias::Left);
13267 selection.goal = SelectionGoal::None;
13268 selection.reversed = false;
13269 self.select_match_ranges(
13270 selection.start..selection.end,
13271 selection.reversed,
13272 replace_newest,
13273 autoscroll,
13274 window,
13275 cx,
13276 );
13277 }
13278
13279 if selections.len() == 1 {
13280 let selection = selections
13281 .last()
13282 .expect("ensured that there's only one selection");
13283 let query = buffer
13284 .text_for_range(selection.start..selection.end)
13285 .collect::<String>();
13286 let is_empty = query.is_empty();
13287 let select_state = SelectNextState {
13288 query: AhoCorasick::new(&[query])?,
13289 wordwise: true,
13290 done: is_empty,
13291 };
13292 self.select_next_state = Some(select_state);
13293 } else {
13294 self.select_next_state = None;
13295 }
13296 } else if let Some(selected_text) = selected_text {
13297 self.select_next_state = Some(SelectNextState {
13298 query: AhoCorasick::new(&[selected_text])?,
13299 wordwise: false,
13300 done: false,
13301 });
13302 self.select_next_match_internal(
13303 display_map,
13304 replace_newest,
13305 autoscroll,
13306 window,
13307 cx,
13308 )?;
13309 }
13310 }
13311 Ok(())
13312 }
13313
13314 pub fn select_all_matches(
13315 &mut self,
13316 _action: &SelectAllMatches,
13317 window: &mut Window,
13318 cx: &mut Context<Self>,
13319 ) -> Result<()> {
13320 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13321
13322 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13323
13324 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13325 let Some(select_next_state) = self.select_next_state.as_mut() else {
13326 return Ok(());
13327 };
13328 if select_next_state.done {
13329 return Ok(());
13330 }
13331
13332 let mut new_selections = Vec::new();
13333
13334 let reversed = self.selections.oldest::<usize>(cx).reversed;
13335 let buffer = &display_map.buffer_snapshot;
13336 let query_matches = select_next_state
13337 .query
13338 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13339
13340 for query_match in query_matches.into_iter() {
13341 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13342 let offset_range = if reversed {
13343 query_match.end()..query_match.start()
13344 } else {
13345 query_match.start()..query_match.end()
13346 };
13347 let display_range = offset_range.start.to_display_point(&display_map)
13348 ..offset_range.end.to_display_point(&display_map);
13349
13350 if !select_next_state.wordwise
13351 || (!movement::is_inside_word(&display_map, display_range.start)
13352 && !movement::is_inside_word(&display_map, display_range.end))
13353 {
13354 new_selections.push(offset_range.start..offset_range.end);
13355 }
13356 }
13357
13358 select_next_state.done = true;
13359 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13360 self.change_selections(None, window, cx, |selections| {
13361 selections.select_ranges(new_selections)
13362 });
13363
13364 Ok(())
13365 }
13366
13367 pub fn select_next(
13368 &mut self,
13369 action: &SelectNext,
13370 window: &mut Window,
13371 cx: &mut Context<Self>,
13372 ) -> Result<()> {
13373 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13374 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13375 self.select_next_match_internal(
13376 &display_map,
13377 action.replace_newest,
13378 Some(Autoscroll::newest()),
13379 window,
13380 cx,
13381 )?;
13382 Ok(())
13383 }
13384
13385 pub fn select_previous(
13386 &mut self,
13387 action: &SelectPrevious,
13388 window: &mut Window,
13389 cx: &mut Context<Self>,
13390 ) -> Result<()> {
13391 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13392 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13393 let buffer = &display_map.buffer_snapshot;
13394 let mut selections = self.selections.all::<usize>(cx);
13395 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13396 let query = &select_prev_state.query;
13397 if !select_prev_state.done {
13398 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13399 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13400 let mut next_selected_range = None;
13401 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13402 let bytes_before_last_selection =
13403 buffer.reversed_bytes_in_range(0..last_selection.start);
13404 let bytes_after_first_selection =
13405 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13406 let query_matches = query
13407 .stream_find_iter(bytes_before_last_selection)
13408 .map(|result| (last_selection.start, result))
13409 .chain(
13410 query
13411 .stream_find_iter(bytes_after_first_selection)
13412 .map(|result| (buffer.len(), result)),
13413 );
13414 for (end_offset, query_match) in query_matches {
13415 let query_match = query_match.unwrap(); // can only fail due to I/O
13416 let offset_range =
13417 end_offset - query_match.end()..end_offset - query_match.start();
13418 let display_range = offset_range.start.to_display_point(&display_map)
13419 ..offset_range.end.to_display_point(&display_map);
13420
13421 if !select_prev_state.wordwise
13422 || (!movement::is_inside_word(&display_map, display_range.start)
13423 && !movement::is_inside_word(&display_map, display_range.end))
13424 {
13425 next_selected_range = Some(offset_range);
13426 break;
13427 }
13428 }
13429
13430 if let Some(next_selected_range) = next_selected_range {
13431 self.select_match_ranges(
13432 next_selected_range,
13433 last_selection.reversed,
13434 action.replace_newest,
13435 Some(Autoscroll::newest()),
13436 window,
13437 cx,
13438 );
13439 } else {
13440 select_prev_state.done = true;
13441 }
13442 }
13443
13444 self.select_prev_state = Some(select_prev_state);
13445 } else {
13446 let mut only_carets = true;
13447 let mut same_text_selected = true;
13448 let mut selected_text = None;
13449
13450 let mut selections_iter = selections.iter().peekable();
13451 while let Some(selection) = selections_iter.next() {
13452 if selection.start != selection.end {
13453 only_carets = false;
13454 }
13455
13456 if same_text_selected {
13457 if selected_text.is_none() {
13458 selected_text =
13459 Some(buffer.text_for_range(selection.range()).collect::<String>());
13460 }
13461
13462 if let Some(next_selection) = selections_iter.peek() {
13463 if next_selection.range().len() == selection.range().len() {
13464 let next_selected_text = buffer
13465 .text_for_range(next_selection.range())
13466 .collect::<String>();
13467 if Some(next_selected_text) != selected_text {
13468 same_text_selected = false;
13469 selected_text = None;
13470 }
13471 } else {
13472 same_text_selected = false;
13473 selected_text = None;
13474 }
13475 }
13476 }
13477 }
13478
13479 if only_carets {
13480 for selection in &mut selections {
13481 let word_range = movement::surrounding_word(
13482 &display_map,
13483 selection.start.to_display_point(&display_map),
13484 );
13485 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
13486 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
13487 selection.goal = SelectionGoal::None;
13488 selection.reversed = false;
13489 self.select_match_ranges(
13490 selection.start..selection.end,
13491 selection.reversed,
13492 action.replace_newest,
13493 Some(Autoscroll::newest()),
13494 window,
13495 cx,
13496 );
13497 }
13498 if selections.len() == 1 {
13499 let selection = selections
13500 .last()
13501 .expect("ensured that there's only one selection");
13502 let query = buffer
13503 .text_for_range(selection.start..selection.end)
13504 .collect::<String>();
13505 let is_empty = query.is_empty();
13506 let select_state = SelectNextState {
13507 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13508 wordwise: true,
13509 done: is_empty,
13510 };
13511 self.select_prev_state = Some(select_state);
13512 } else {
13513 self.select_prev_state = None;
13514 }
13515 } else if let Some(selected_text) = selected_text {
13516 self.select_prev_state = Some(SelectNextState {
13517 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13518 wordwise: false,
13519 done: false,
13520 });
13521 self.select_previous(action, window, cx)?;
13522 }
13523 }
13524 Ok(())
13525 }
13526
13527 pub fn find_next_match(
13528 &mut self,
13529 _: &FindNextMatch,
13530 window: &mut Window,
13531 cx: &mut Context<Self>,
13532 ) -> Result<()> {
13533 let selections = self.selections.disjoint_anchors();
13534 match selections.first() {
13535 Some(first) if selections.len() >= 2 => {
13536 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13537 s.select_ranges([first.range()]);
13538 });
13539 }
13540 _ => self.select_next(
13541 &SelectNext {
13542 replace_newest: true,
13543 },
13544 window,
13545 cx,
13546 )?,
13547 }
13548 Ok(())
13549 }
13550
13551 pub fn find_previous_match(
13552 &mut self,
13553 _: &FindPreviousMatch,
13554 window: &mut Window,
13555 cx: &mut Context<Self>,
13556 ) -> Result<()> {
13557 let selections = self.selections.disjoint_anchors();
13558 match selections.last() {
13559 Some(last) if selections.len() >= 2 => {
13560 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13561 s.select_ranges([last.range()]);
13562 });
13563 }
13564 _ => self.select_previous(
13565 &SelectPrevious {
13566 replace_newest: true,
13567 },
13568 window,
13569 cx,
13570 )?,
13571 }
13572 Ok(())
13573 }
13574
13575 pub fn toggle_comments(
13576 &mut self,
13577 action: &ToggleComments,
13578 window: &mut Window,
13579 cx: &mut Context<Self>,
13580 ) {
13581 if self.read_only(cx) {
13582 return;
13583 }
13584 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13585 let text_layout_details = &self.text_layout_details(window);
13586 self.transact(window, cx, |this, window, cx| {
13587 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13588 let mut edits = Vec::new();
13589 let mut selection_edit_ranges = Vec::new();
13590 let mut last_toggled_row = None;
13591 let snapshot = this.buffer.read(cx).read(cx);
13592 let empty_str: Arc<str> = Arc::default();
13593 let mut suffixes_inserted = Vec::new();
13594 let ignore_indent = action.ignore_indent;
13595
13596 fn comment_prefix_range(
13597 snapshot: &MultiBufferSnapshot,
13598 row: MultiBufferRow,
13599 comment_prefix: &str,
13600 comment_prefix_whitespace: &str,
13601 ignore_indent: bool,
13602 ) -> Range<Point> {
13603 let indent_size = if ignore_indent {
13604 0
13605 } else {
13606 snapshot.indent_size_for_line(row).len
13607 };
13608
13609 let start = Point::new(row.0, indent_size);
13610
13611 let mut line_bytes = snapshot
13612 .bytes_in_range(start..snapshot.max_point())
13613 .flatten()
13614 .copied();
13615
13616 // If this line currently begins with the line comment prefix, then record
13617 // the range containing the prefix.
13618 if line_bytes
13619 .by_ref()
13620 .take(comment_prefix.len())
13621 .eq(comment_prefix.bytes())
13622 {
13623 // Include any whitespace that matches the comment prefix.
13624 let matching_whitespace_len = line_bytes
13625 .zip(comment_prefix_whitespace.bytes())
13626 .take_while(|(a, b)| a == b)
13627 .count() as u32;
13628 let end = Point::new(
13629 start.row,
13630 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13631 );
13632 start..end
13633 } else {
13634 start..start
13635 }
13636 }
13637
13638 fn comment_suffix_range(
13639 snapshot: &MultiBufferSnapshot,
13640 row: MultiBufferRow,
13641 comment_suffix: &str,
13642 comment_suffix_has_leading_space: bool,
13643 ) -> Range<Point> {
13644 let end = Point::new(row.0, snapshot.line_len(row));
13645 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13646
13647 let mut line_end_bytes = snapshot
13648 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13649 .flatten()
13650 .copied();
13651
13652 let leading_space_len = if suffix_start_column > 0
13653 && line_end_bytes.next() == Some(b' ')
13654 && comment_suffix_has_leading_space
13655 {
13656 1
13657 } else {
13658 0
13659 };
13660
13661 // If this line currently begins with the line comment prefix, then record
13662 // the range containing the prefix.
13663 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13664 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13665 start..end
13666 } else {
13667 end..end
13668 }
13669 }
13670
13671 // TODO: Handle selections that cross excerpts
13672 for selection in &mut selections {
13673 let start_column = snapshot
13674 .indent_size_for_line(MultiBufferRow(selection.start.row))
13675 .len;
13676 let language = if let Some(language) =
13677 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13678 {
13679 language
13680 } else {
13681 continue;
13682 };
13683
13684 selection_edit_ranges.clear();
13685
13686 // If multiple selections contain a given row, avoid processing that
13687 // row more than once.
13688 let mut start_row = MultiBufferRow(selection.start.row);
13689 if last_toggled_row == Some(start_row) {
13690 start_row = start_row.next_row();
13691 }
13692 let end_row =
13693 if selection.end.row > selection.start.row && selection.end.column == 0 {
13694 MultiBufferRow(selection.end.row - 1)
13695 } else {
13696 MultiBufferRow(selection.end.row)
13697 };
13698 last_toggled_row = Some(end_row);
13699
13700 if start_row > end_row {
13701 continue;
13702 }
13703
13704 // If the language has line comments, toggle those.
13705 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13706
13707 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13708 if ignore_indent {
13709 full_comment_prefixes = full_comment_prefixes
13710 .into_iter()
13711 .map(|s| Arc::from(s.trim_end()))
13712 .collect();
13713 }
13714
13715 if !full_comment_prefixes.is_empty() {
13716 let first_prefix = full_comment_prefixes
13717 .first()
13718 .expect("prefixes is non-empty");
13719 let prefix_trimmed_lengths = full_comment_prefixes
13720 .iter()
13721 .map(|p| p.trim_end_matches(' ').len())
13722 .collect::<SmallVec<[usize; 4]>>();
13723
13724 let mut all_selection_lines_are_comments = true;
13725
13726 for row in start_row.0..=end_row.0 {
13727 let row = MultiBufferRow(row);
13728 if start_row < end_row && snapshot.is_line_blank(row) {
13729 continue;
13730 }
13731
13732 let prefix_range = full_comment_prefixes
13733 .iter()
13734 .zip(prefix_trimmed_lengths.iter().copied())
13735 .map(|(prefix, trimmed_prefix_len)| {
13736 comment_prefix_range(
13737 snapshot.deref(),
13738 row,
13739 &prefix[..trimmed_prefix_len],
13740 &prefix[trimmed_prefix_len..],
13741 ignore_indent,
13742 )
13743 })
13744 .max_by_key(|range| range.end.column - range.start.column)
13745 .expect("prefixes is non-empty");
13746
13747 if prefix_range.is_empty() {
13748 all_selection_lines_are_comments = false;
13749 }
13750
13751 selection_edit_ranges.push(prefix_range);
13752 }
13753
13754 if all_selection_lines_are_comments {
13755 edits.extend(
13756 selection_edit_ranges
13757 .iter()
13758 .cloned()
13759 .map(|range| (range, empty_str.clone())),
13760 );
13761 } else {
13762 let min_column = selection_edit_ranges
13763 .iter()
13764 .map(|range| range.start.column)
13765 .min()
13766 .unwrap_or(0);
13767 edits.extend(selection_edit_ranges.iter().map(|range| {
13768 let position = Point::new(range.start.row, min_column);
13769 (position..position, first_prefix.clone())
13770 }));
13771 }
13772 } else if let Some((full_comment_prefix, comment_suffix)) =
13773 language.block_comment_delimiters()
13774 {
13775 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13776 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13777 let prefix_range = comment_prefix_range(
13778 snapshot.deref(),
13779 start_row,
13780 comment_prefix,
13781 comment_prefix_whitespace,
13782 ignore_indent,
13783 );
13784 let suffix_range = comment_suffix_range(
13785 snapshot.deref(),
13786 end_row,
13787 comment_suffix.trim_start_matches(' '),
13788 comment_suffix.starts_with(' '),
13789 );
13790
13791 if prefix_range.is_empty() || suffix_range.is_empty() {
13792 edits.push((
13793 prefix_range.start..prefix_range.start,
13794 full_comment_prefix.clone(),
13795 ));
13796 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13797 suffixes_inserted.push((end_row, comment_suffix.len()));
13798 } else {
13799 edits.push((prefix_range, empty_str.clone()));
13800 edits.push((suffix_range, empty_str.clone()));
13801 }
13802 } else {
13803 continue;
13804 }
13805 }
13806
13807 drop(snapshot);
13808 this.buffer.update(cx, |buffer, cx| {
13809 buffer.edit(edits, None, cx);
13810 });
13811
13812 // Adjust selections so that they end before any comment suffixes that
13813 // were inserted.
13814 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13815 let mut selections = this.selections.all::<Point>(cx);
13816 let snapshot = this.buffer.read(cx).read(cx);
13817 for selection in &mut selections {
13818 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13819 match row.cmp(&MultiBufferRow(selection.end.row)) {
13820 Ordering::Less => {
13821 suffixes_inserted.next();
13822 continue;
13823 }
13824 Ordering::Greater => break,
13825 Ordering::Equal => {
13826 if selection.end.column == snapshot.line_len(row) {
13827 if selection.is_empty() {
13828 selection.start.column -= suffix_len as u32;
13829 }
13830 selection.end.column -= suffix_len as u32;
13831 }
13832 break;
13833 }
13834 }
13835 }
13836 }
13837
13838 drop(snapshot);
13839 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13840 s.select(selections)
13841 });
13842
13843 let selections = this.selections.all::<Point>(cx);
13844 let selections_on_single_row = selections.windows(2).all(|selections| {
13845 selections[0].start.row == selections[1].start.row
13846 && selections[0].end.row == selections[1].end.row
13847 && selections[0].start.row == selections[0].end.row
13848 });
13849 let selections_selecting = selections
13850 .iter()
13851 .any(|selection| selection.start != selection.end);
13852 let advance_downwards = action.advance_downwards
13853 && selections_on_single_row
13854 && !selections_selecting
13855 && !matches!(this.mode, EditorMode::SingleLine { .. });
13856
13857 if advance_downwards {
13858 let snapshot = this.buffer.read(cx).snapshot(cx);
13859
13860 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13861 s.move_cursors_with(|display_snapshot, display_point, _| {
13862 let mut point = display_point.to_point(display_snapshot);
13863 point.row += 1;
13864 point = snapshot.clip_point(point, Bias::Left);
13865 let display_point = point.to_display_point(display_snapshot);
13866 let goal = SelectionGoal::HorizontalPosition(
13867 display_snapshot
13868 .x_for_display_point(display_point, text_layout_details)
13869 .into(),
13870 );
13871 (display_point, goal)
13872 })
13873 });
13874 }
13875 });
13876 }
13877
13878 pub fn select_enclosing_symbol(
13879 &mut self,
13880 _: &SelectEnclosingSymbol,
13881 window: &mut Window,
13882 cx: &mut Context<Self>,
13883 ) {
13884 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13885
13886 let buffer = self.buffer.read(cx).snapshot(cx);
13887 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13888
13889 fn update_selection(
13890 selection: &Selection<usize>,
13891 buffer_snap: &MultiBufferSnapshot,
13892 ) -> Option<Selection<usize>> {
13893 let cursor = selection.head();
13894 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13895 for symbol in symbols.iter().rev() {
13896 let start = symbol.range.start.to_offset(buffer_snap);
13897 let end = symbol.range.end.to_offset(buffer_snap);
13898 let new_range = start..end;
13899 if start < selection.start || end > selection.end {
13900 return Some(Selection {
13901 id: selection.id,
13902 start: new_range.start,
13903 end: new_range.end,
13904 goal: SelectionGoal::None,
13905 reversed: selection.reversed,
13906 });
13907 }
13908 }
13909 None
13910 }
13911
13912 let mut selected_larger_symbol = false;
13913 let new_selections = old_selections
13914 .iter()
13915 .map(|selection| match update_selection(selection, &buffer) {
13916 Some(new_selection) => {
13917 if new_selection.range() != selection.range() {
13918 selected_larger_symbol = true;
13919 }
13920 new_selection
13921 }
13922 None => selection.clone(),
13923 })
13924 .collect::<Vec<_>>();
13925
13926 if selected_larger_symbol {
13927 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13928 s.select(new_selections);
13929 });
13930 }
13931 }
13932
13933 pub fn select_larger_syntax_node(
13934 &mut self,
13935 _: &SelectLargerSyntaxNode,
13936 window: &mut Window,
13937 cx: &mut Context<Self>,
13938 ) {
13939 let Some(visible_row_count) = self.visible_row_count() else {
13940 return;
13941 };
13942 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13943 if old_selections.is_empty() {
13944 return;
13945 }
13946
13947 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13948
13949 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13950 let buffer = self.buffer.read(cx).snapshot(cx);
13951
13952 let mut selected_larger_node = false;
13953 let mut new_selections = old_selections
13954 .iter()
13955 .map(|selection| {
13956 let old_range = selection.start..selection.end;
13957
13958 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13959 // manually select word at selection
13960 if ["string_content", "inline"].contains(&node.kind()) {
13961 let word_range = {
13962 let display_point = buffer
13963 .offset_to_point(old_range.start)
13964 .to_display_point(&display_map);
13965 let Range { start, end } =
13966 movement::surrounding_word(&display_map, display_point);
13967 start.to_point(&display_map).to_offset(&buffer)
13968 ..end.to_point(&display_map).to_offset(&buffer)
13969 };
13970 // ignore if word is already selected
13971 if !word_range.is_empty() && old_range != word_range {
13972 let last_word_range = {
13973 let display_point = buffer
13974 .offset_to_point(old_range.end)
13975 .to_display_point(&display_map);
13976 let Range { start, end } =
13977 movement::surrounding_word(&display_map, display_point);
13978 start.to_point(&display_map).to_offset(&buffer)
13979 ..end.to_point(&display_map).to_offset(&buffer)
13980 };
13981 // only select word if start and end point belongs to same word
13982 if word_range == last_word_range {
13983 selected_larger_node = true;
13984 return Selection {
13985 id: selection.id,
13986 start: word_range.start,
13987 end: word_range.end,
13988 goal: SelectionGoal::None,
13989 reversed: selection.reversed,
13990 };
13991 }
13992 }
13993 }
13994 }
13995
13996 let mut new_range = old_range.clone();
13997 while let Some((_node, containing_range)) =
13998 buffer.syntax_ancestor(new_range.clone())
13999 {
14000 new_range = match containing_range {
14001 MultiOrSingleBufferOffsetRange::Single(_) => break,
14002 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14003 };
14004 if !display_map.intersects_fold(new_range.start)
14005 && !display_map.intersects_fold(new_range.end)
14006 {
14007 break;
14008 }
14009 }
14010
14011 selected_larger_node |= new_range != old_range;
14012 Selection {
14013 id: selection.id,
14014 start: new_range.start,
14015 end: new_range.end,
14016 goal: SelectionGoal::None,
14017 reversed: selection.reversed,
14018 }
14019 })
14020 .collect::<Vec<_>>();
14021
14022 if !selected_larger_node {
14023 return; // don't put this call in the history
14024 }
14025
14026 // scroll based on transformation done to the last selection created by the user
14027 let (last_old, last_new) = old_selections
14028 .last()
14029 .zip(new_selections.last().cloned())
14030 .expect("old_selections isn't empty");
14031
14032 // revert selection
14033 let is_selection_reversed = {
14034 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14035 new_selections.last_mut().expect("checked above").reversed =
14036 should_newest_selection_be_reversed;
14037 should_newest_selection_be_reversed
14038 };
14039
14040 if selected_larger_node {
14041 self.select_syntax_node_history.disable_clearing = true;
14042 self.change_selections(None, window, cx, |s| {
14043 s.select(new_selections.clone());
14044 });
14045 self.select_syntax_node_history.disable_clearing = false;
14046 }
14047
14048 let start_row = last_new.start.to_display_point(&display_map).row().0;
14049 let end_row = last_new.end.to_display_point(&display_map).row().0;
14050 let selection_height = end_row - start_row + 1;
14051 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14052
14053 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14054 let scroll_behavior = if fits_on_the_screen {
14055 self.request_autoscroll(Autoscroll::fit(), cx);
14056 SelectSyntaxNodeScrollBehavior::FitSelection
14057 } else if is_selection_reversed {
14058 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14059 SelectSyntaxNodeScrollBehavior::CursorTop
14060 } else {
14061 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14062 SelectSyntaxNodeScrollBehavior::CursorBottom
14063 };
14064
14065 self.select_syntax_node_history.push((
14066 old_selections,
14067 scroll_behavior,
14068 is_selection_reversed,
14069 ));
14070 }
14071
14072 pub fn select_smaller_syntax_node(
14073 &mut self,
14074 _: &SelectSmallerSyntaxNode,
14075 window: &mut Window,
14076 cx: &mut Context<Self>,
14077 ) {
14078 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14079
14080 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14081 self.select_syntax_node_history.pop()
14082 {
14083 if let Some(selection) = selections.last_mut() {
14084 selection.reversed = is_selection_reversed;
14085 }
14086
14087 self.select_syntax_node_history.disable_clearing = true;
14088 self.change_selections(None, window, cx, |s| {
14089 s.select(selections.to_vec());
14090 });
14091 self.select_syntax_node_history.disable_clearing = false;
14092
14093 match scroll_behavior {
14094 SelectSyntaxNodeScrollBehavior::CursorTop => {
14095 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14096 }
14097 SelectSyntaxNodeScrollBehavior::FitSelection => {
14098 self.request_autoscroll(Autoscroll::fit(), cx);
14099 }
14100 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14101 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14102 }
14103 }
14104 }
14105 }
14106
14107 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14108 if !EditorSettings::get_global(cx).gutter.runnables {
14109 self.clear_tasks();
14110 return Task::ready(());
14111 }
14112 let project = self.project.as_ref().map(Entity::downgrade);
14113 let task_sources = self.lsp_task_sources(cx);
14114 let multi_buffer = self.buffer.downgrade();
14115 cx.spawn_in(window, async move |editor, cx| {
14116 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14117 let Some(project) = project.and_then(|p| p.upgrade()) else {
14118 return;
14119 };
14120 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14121 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14122 }) else {
14123 return;
14124 };
14125
14126 let hide_runnables = project
14127 .update(cx, |project, cx| {
14128 // Do not display any test indicators in non-dev server remote projects.
14129 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14130 })
14131 .unwrap_or(true);
14132 if hide_runnables {
14133 return;
14134 }
14135 let new_rows =
14136 cx.background_spawn({
14137 let snapshot = display_snapshot.clone();
14138 async move {
14139 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14140 }
14141 })
14142 .await;
14143 let Ok(lsp_tasks) =
14144 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14145 else {
14146 return;
14147 };
14148 let lsp_tasks = lsp_tasks.await;
14149
14150 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14151 lsp_tasks
14152 .into_iter()
14153 .flat_map(|(kind, tasks)| {
14154 tasks.into_iter().filter_map(move |(location, task)| {
14155 Some((kind.clone(), location?, task))
14156 })
14157 })
14158 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14159 let buffer = location.target.buffer;
14160 let buffer_snapshot = buffer.read(cx).snapshot();
14161 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14162 |(excerpt_id, snapshot, _)| {
14163 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14164 display_snapshot
14165 .buffer_snapshot
14166 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14167 } else {
14168 None
14169 }
14170 },
14171 );
14172 if let Some(offset) = offset {
14173 let task_buffer_range =
14174 location.target.range.to_point(&buffer_snapshot);
14175 let context_buffer_range =
14176 task_buffer_range.to_offset(&buffer_snapshot);
14177 let context_range = BufferOffset(context_buffer_range.start)
14178 ..BufferOffset(context_buffer_range.end);
14179
14180 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14181 .or_insert_with(|| RunnableTasks {
14182 templates: Vec::new(),
14183 offset,
14184 column: task_buffer_range.start.column,
14185 extra_variables: HashMap::default(),
14186 context_range,
14187 })
14188 .templates
14189 .push((kind, task.original_task().clone()));
14190 }
14191
14192 acc
14193 })
14194 }) else {
14195 return;
14196 };
14197
14198 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14199 buffer.language_settings(cx).tasks.prefer_lsp
14200 }) else {
14201 return;
14202 };
14203
14204 let rows = Self::runnable_rows(
14205 project,
14206 display_snapshot,
14207 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14208 new_rows,
14209 cx.clone(),
14210 )
14211 .await;
14212 editor
14213 .update(cx, |editor, _| {
14214 editor.clear_tasks();
14215 for (key, mut value) in rows {
14216 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14217 value.templates.extend(lsp_tasks.templates);
14218 }
14219
14220 editor.insert_tasks(key, value);
14221 }
14222 for (key, value) in lsp_tasks_by_rows {
14223 editor.insert_tasks(key, value);
14224 }
14225 })
14226 .ok();
14227 })
14228 }
14229 fn fetch_runnable_ranges(
14230 snapshot: &DisplaySnapshot,
14231 range: Range<Anchor>,
14232 ) -> Vec<language::RunnableRange> {
14233 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14234 }
14235
14236 fn runnable_rows(
14237 project: Entity<Project>,
14238 snapshot: DisplaySnapshot,
14239 prefer_lsp: bool,
14240 runnable_ranges: Vec<RunnableRange>,
14241 cx: AsyncWindowContext,
14242 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14243 cx.spawn(async move |cx| {
14244 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14245 for mut runnable in runnable_ranges {
14246 let Some(tasks) = cx
14247 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14248 .ok()
14249 else {
14250 continue;
14251 };
14252 let mut tasks = tasks.await;
14253
14254 if prefer_lsp {
14255 tasks.retain(|(task_kind, _)| {
14256 !matches!(task_kind, TaskSourceKind::Language { .. })
14257 });
14258 }
14259 if tasks.is_empty() {
14260 continue;
14261 }
14262
14263 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14264 let Some(row) = snapshot
14265 .buffer_snapshot
14266 .buffer_line_for_row(MultiBufferRow(point.row))
14267 .map(|(_, range)| range.start.row)
14268 else {
14269 continue;
14270 };
14271
14272 let context_range =
14273 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14274 runnable_rows.push((
14275 (runnable.buffer_id, row),
14276 RunnableTasks {
14277 templates: tasks,
14278 offset: snapshot
14279 .buffer_snapshot
14280 .anchor_before(runnable.run_range.start),
14281 context_range,
14282 column: point.column,
14283 extra_variables: runnable.extra_captures,
14284 },
14285 ));
14286 }
14287 runnable_rows
14288 })
14289 }
14290
14291 fn templates_with_tags(
14292 project: &Entity<Project>,
14293 runnable: &mut Runnable,
14294 cx: &mut App,
14295 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14296 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14297 let (worktree_id, file) = project
14298 .buffer_for_id(runnable.buffer, cx)
14299 .and_then(|buffer| buffer.read(cx).file())
14300 .map(|file| (file.worktree_id(cx), file.clone()))
14301 .unzip();
14302
14303 (
14304 project.task_store().read(cx).task_inventory().cloned(),
14305 worktree_id,
14306 file,
14307 )
14308 });
14309
14310 let tags = mem::take(&mut runnable.tags);
14311 let language = runnable.language.clone();
14312 cx.spawn(async move |cx| {
14313 let mut templates_with_tags = Vec::new();
14314 if let Some(inventory) = inventory {
14315 for RunnableTag(tag) in tags {
14316 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14317 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14318 }) else {
14319 return templates_with_tags;
14320 };
14321 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14322 move |(_, template)| {
14323 template.tags.iter().any(|source_tag| source_tag == &tag)
14324 },
14325 ));
14326 }
14327 }
14328 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14329
14330 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14331 // Strongest source wins; if we have worktree tag binding, prefer that to
14332 // global and language bindings;
14333 // if we have a global binding, prefer that to language binding.
14334 let first_mismatch = templates_with_tags
14335 .iter()
14336 .position(|(tag_source, _)| tag_source != leading_tag_source);
14337 if let Some(index) = first_mismatch {
14338 templates_with_tags.truncate(index);
14339 }
14340 }
14341
14342 templates_with_tags
14343 })
14344 }
14345
14346 pub fn move_to_enclosing_bracket(
14347 &mut self,
14348 _: &MoveToEnclosingBracket,
14349 window: &mut Window,
14350 cx: &mut Context<Self>,
14351 ) {
14352 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14353 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14354 s.move_offsets_with(|snapshot, selection| {
14355 let Some(enclosing_bracket_ranges) =
14356 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14357 else {
14358 return;
14359 };
14360
14361 let mut best_length = usize::MAX;
14362 let mut best_inside = false;
14363 let mut best_in_bracket_range = false;
14364 let mut best_destination = None;
14365 for (open, close) in enclosing_bracket_ranges {
14366 let close = close.to_inclusive();
14367 let length = close.end() - open.start;
14368 let inside = selection.start >= open.end && selection.end <= *close.start();
14369 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14370 || close.contains(&selection.head());
14371
14372 // If best is next to a bracket and current isn't, skip
14373 if !in_bracket_range && best_in_bracket_range {
14374 continue;
14375 }
14376
14377 // Prefer smaller lengths unless best is inside and current isn't
14378 if length > best_length && (best_inside || !inside) {
14379 continue;
14380 }
14381
14382 best_length = length;
14383 best_inside = inside;
14384 best_in_bracket_range = in_bracket_range;
14385 best_destination = Some(
14386 if close.contains(&selection.start) && close.contains(&selection.end) {
14387 if inside { open.end } else { open.start }
14388 } else if inside {
14389 *close.start()
14390 } else {
14391 *close.end()
14392 },
14393 );
14394 }
14395
14396 if let Some(destination) = best_destination {
14397 selection.collapse_to(destination, SelectionGoal::None);
14398 }
14399 })
14400 });
14401 }
14402
14403 pub fn undo_selection(
14404 &mut self,
14405 _: &UndoSelection,
14406 window: &mut Window,
14407 cx: &mut Context<Self>,
14408 ) {
14409 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14410 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14411 self.selection_history.mode = SelectionHistoryMode::Undoing;
14412 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14413 this.end_selection(window, cx);
14414 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14415 s.select_anchors(entry.selections.to_vec())
14416 });
14417 });
14418 self.selection_history.mode = SelectionHistoryMode::Normal;
14419
14420 self.select_next_state = entry.select_next_state;
14421 self.select_prev_state = entry.select_prev_state;
14422 self.add_selections_state = entry.add_selections_state;
14423 }
14424 }
14425
14426 pub fn redo_selection(
14427 &mut self,
14428 _: &RedoSelection,
14429 window: &mut Window,
14430 cx: &mut Context<Self>,
14431 ) {
14432 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14433 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14434 self.selection_history.mode = SelectionHistoryMode::Redoing;
14435 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14436 this.end_selection(window, cx);
14437 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14438 s.select_anchors(entry.selections.to_vec())
14439 });
14440 });
14441 self.selection_history.mode = SelectionHistoryMode::Normal;
14442
14443 self.select_next_state = entry.select_next_state;
14444 self.select_prev_state = entry.select_prev_state;
14445 self.add_selections_state = entry.add_selections_state;
14446 }
14447 }
14448
14449 pub fn expand_excerpts(
14450 &mut self,
14451 action: &ExpandExcerpts,
14452 _: &mut Window,
14453 cx: &mut Context<Self>,
14454 ) {
14455 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14456 }
14457
14458 pub fn expand_excerpts_down(
14459 &mut self,
14460 action: &ExpandExcerptsDown,
14461 _: &mut Window,
14462 cx: &mut Context<Self>,
14463 ) {
14464 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14465 }
14466
14467 pub fn expand_excerpts_up(
14468 &mut self,
14469 action: &ExpandExcerptsUp,
14470 _: &mut Window,
14471 cx: &mut Context<Self>,
14472 ) {
14473 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14474 }
14475
14476 pub fn expand_excerpts_for_direction(
14477 &mut self,
14478 lines: u32,
14479 direction: ExpandExcerptDirection,
14480
14481 cx: &mut Context<Self>,
14482 ) {
14483 let selections = self.selections.disjoint_anchors();
14484
14485 let lines = if lines == 0 {
14486 EditorSettings::get_global(cx).expand_excerpt_lines
14487 } else {
14488 lines
14489 };
14490
14491 self.buffer.update(cx, |buffer, cx| {
14492 let snapshot = buffer.snapshot(cx);
14493 let mut excerpt_ids = selections
14494 .iter()
14495 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14496 .collect::<Vec<_>>();
14497 excerpt_ids.sort();
14498 excerpt_ids.dedup();
14499 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14500 })
14501 }
14502
14503 pub fn expand_excerpt(
14504 &mut self,
14505 excerpt: ExcerptId,
14506 direction: ExpandExcerptDirection,
14507 window: &mut Window,
14508 cx: &mut Context<Self>,
14509 ) {
14510 let current_scroll_position = self.scroll_position(cx);
14511 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14512 let mut should_scroll_up = false;
14513
14514 if direction == ExpandExcerptDirection::Down {
14515 let multi_buffer = self.buffer.read(cx);
14516 let snapshot = multi_buffer.snapshot(cx);
14517 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14518 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14519 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14520 let buffer_snapshot = buffer.read(cx).snapshot();
14521 let excerpt_end_row =
14522 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14523 let last_row = buffer_snapshot.max_point().row;
14524 let lines_below = last_row.saturating_sub(excerpt_end_row);
14525 should_scroll_up = lines_below >= lines_to_expand;
14526 }
14527 }
14528 }
14529 }
14530
14531 self.buffer.update(cx, |buffer, cx| {
14532 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14533 });
14534
14535 if should_scroll_up {
14536 let new_scroll_position =
14537 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14538 self.set_scroll_position(new_scroll_position, window, cx);
14539 }
14540 }
14541
14542 pub fn go_to_singleton_buffer_point(
14543 &mut self,
14544 point: Point,
14545 window: &mut Window,
14546 cx: &mut Context<Self>,
14547 ) {
14548 self.go_to_singleton_buffer_range(point..point, window, cx);
14549 }
14550
14551 pub fn go_to_singleton_buffer_range(
14552 &mut self,
14553 range: Range<Point>,
14554 window: &mut Window,
14555 cx: &mut Context<Self>,
14556 ) {
14557 let multibuffer = self.buffer().read(cx);
14558 let Some(buffer) = multibuffer.as_singleton() else {
14559 return;
14560 };
14561 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14562 return;
14563 };
14564 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14565 return;
14566 };
14567 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
14568 s.select_anchor_ranges([start..end])
14569 });
14570 }
14571
14572 pub fn go_to_diagnostic(
14573 &mut self,
14574 _: &GoToDiagnostic,
14575 window: &mut Window,
14576 cx: &mut Context<Self>,
14577 ) {
14578 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14579 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14580 }
14581
14582 pub fn go_to_prev_diagnostic(
14583 &mut self,
14584 _: &GoToPreviousDiagnostic,
14585 window: &mut Window,
14586 cx: &mut Context<Self>,
14587 ) {
14588 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14589 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14590 }
14591
14592 pub fn go_to_diagnostic_impl(
14593 &mut self,
14594 direction: Direction,
14595 window: &mut Window,
14596 cx: &mut Context<Self>,
14597 ) {
14598 let buffer = self.buffer.read(cx).snapshot(cx);
14599 let selection = self.selections.newest::<usize>(cx);
14600
14601 let mut active_group_id = None;
14602 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14603 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14604 active_group_id = Some(active_group.group_id);
14605 }
14606 }
14607
14608 fn filtered(
14609 snapshot: EditorSnapshot,
14610 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14611 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14612 diagnostics
14613 .filter(|entry| entry.range.start != entry.range.end)
14614 .filter(|entry| !entry.diagnostic.is_unnecessary)
14615 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14616 }
14617
14618 let snapshot = self.snapshot(window, cx);
14619 let before = filtered(
14620 snapshot.clone(),
14621 buffer
14622 .diagnostics_in_range(0..selection.start)
14623 .filter(|entry| entry.range.start <= selection.start),
14624 );
14625 let after = filtered(
14626 snapshot,
14627 buffer
14628 .diagnostics_in_range(selection.start..buffer.len())
14629 .filter(|entry| entry.range.start >= selection.start),
14630 );
14631
14632 let mut found: Option<DiagnosticEntry<usize>> = None;
14633 if direction == Direction::Prev {
14634 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14635 {
14636 for diagnostic in prev_diagnostics.into_iter().rev() {
14637 if diagnostic.range.start != selection.start
14638 || active_group_id
14639 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14640 {
14641 found = Some(diagnostic);
14642 break 'outer;
14643 }
14644 }
14645 }
14646 } else {
14647 for diagnostic in after.chain(before) {
14648 if diagnostic.range.start != selection.start
14649 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14650 {
14651 found = Some(diagnostic);
14652 break;
14653 }
14654 }
14655 }
14656 let Some(next_diagnostic) = found else {
14657 return;
14658 };
14659
14660 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14661 return;
14662 };
14663 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14664 s.select_ranges(vec![
14665 next_diagnostic.range.start..next_diagnostic.range.start,
14666 ])
14667 });
14668 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14669 self.refresh_inline_completion(false, true, window, cx);
14670 }
14671
14672 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14673 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14674 let snapshot = self.snapshot(window, cx);
14675 let selection = self.selections.newest::<Point>(cx);
14676 self.go_to_hunk_before_or_after_position(
14677 &snapshot,
14678 selection.head(),
14679 Direction::Next,
14680 window,
14681 cx,
14682 );
14683 }
14684
14685 pub fn go_to_hunk_before_or_after_position(
14686 &mut self,
14687 snapshot: &EditorSnapshot,
14688 position: Point,
14689 direction: Direction,
14690 window: &mut Window,
14691 cx: &mut Context<Editor>,
14692 ) {
14693 let row = if direction == Direction::Next {
14694 self.hunk_after_position(snapshot, position)
14695 .map(|hunk| hunk.row_range.start)
14696 } else {
14697 self.hunk_before_position(snapshot, position)
14698 };
14699
14700 if let Some(row) = row {
14701 let destination = Point::new(row.0, 0);
14702 let autoscroll = Autoscroll::center();
14703
14704 self.unfold_ranges(&[destination..destination], false, false, cx);
14705 self.change_selections(Some(autoscroll), window, cx, |s| {
14706 s.select_ranges([destination..destination]);
14707 });
14708 }
14709 }
14710
14711 fn hunk_after_position(
14712 &mut self,
14713 snapshot: &EditorSnapshot,
14714 position: Point,
14715 ) -> Option<MultiBufferDiffHunk> {
14716 snapshot
14717 .buffer_snapshot
14718 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14719 .find(|hunk| hunk.row_range.start.0 > position.row)
14720 .or_else(|| {
14721 snapshot
14722 .buffer_snapshot
14723 .diff_hunks_in_range(Point::zero()..position)
14724 .find(|hunk| hunk.row_range.end.0 < position.row)
14725 })
14726 }
14727
14728 fn go_to_prev_hunk(
14729 &mut self,
14730 _: &GoToPreviousHunk,
14731 window: &mut Window,
14732 cx: &mut Context<Self>,
14733 ) {
14734 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14735 let snapshot = self.snapshot(window, cx);
14736 let selection = self.selections.newest::<Point>(cx);
14737 self.go_to_hunk_before_or_after_position(
14738 &snapshot,
14739 selection.head(),
14740 Direction::Prev,
14741 window,
14742 cx,
14743 );
14744 }
14745
14746 fn hunk_before_position(
14747 &mut self,
14748 snapshot: &EditorSnapshot,
14749 position: Point,
14750 ) -> Option<MultiBufferRow> {
14751 snapshot
14752 .buffer_snapshot
14753 .diff_hunk_before(position)
14754 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14755 }
14756
14757 fn go_to_next_change(
14758 &mut self,
14759 _: &GoToNextChange,
14760 window: &mut Window,
14761 cx: &mut Context<Self>,
14762 ) {
14763 if let Some(selections) = self
14764 .change_list
14765 .next_change(1, Direction::Next)
14766 .map(|s| s.to_vec())
14767 {
14768 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14769 let map = s.display_map();
14770 s.select_display_ranges(selections.iter().map(|a| {
14771 let point = a.to_display_point(&map);
14772 point..point
14773 }))
14774 })
14775 }
14776 }
14777
14778 fn go_to_previous_change(
14779 &mut self,
14780 _: &GoToPreviousChange,
14781 window: &mut Window,
14782 cx: &mut Context<Self>,
14783 ) {
14784 if let Some(selections) = self
14785 .change_list
14786 .next_change(1, Direction::Prev)
14787 .map(|s| s.to_vec())
14788 {
14789 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14790 let map = s.display_map();
14791 s.select_display_ranges(selections.iter().map(|a| {
14792 let point = a.to_display_point(&map);
14793 point..point
14794 }))
14795 })
14796 }
14797 }
14798
14799 fn go_to_line<T: 'static>(
14800 &mut self,
14801 position: Anchor,
14802 highlight_color: Option<Hsla>,
14803 window: &mut Window,
14804 cx: &mut Context<Self>,
14805 ) {
14806 let snapshot = self.snapshot(window, cx).display_snapshot;
14807 let position = position.to_point(&snapshot.buffer_snapshot);
14808 let start = snapshot
14809 .buffer_snapshot
14810 .clip_point(Point::new(position.row, 0), Bias::Left);
14811 let end = start + Point::new(1, 0);
14812 let start = snapshot.buffer_snapshot.anchor_before(start);
14813 let end = snapshot.buffer_snapshot.anchor_before(end);
14814
14815 self.highlight_rows::<T>(
14816 start..end,
14817 highlight_color
14818 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14819 Default::default(),
14820 cx,
14821 );
14822
14823 if self.buffer.read(cx).is_singleton() {
14824 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14825 }
14826 }
14827
14828 pub fn go_to_definition(
14829 &mut self,
14830 _: &GoToDefinition,
14831 window: &mut Window,
14832 cx: &mut Context<Self>,
14833 ) -> Task<Result<Navigated>> {
14834 let definition =
14835 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14836 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14837 cx.spawn_in(window, async move |editor, cx| {
14838 if definition.await? == Navigated::Yes {
14839 return Ok(Navigated::Yes);
14840 }
14841 match fallback_strategy {
14842 GoToDefinitionFallback::None => Ok(Navigated::No),
14843 GoToDefinitionFallback::FindAllReferences => {
14844 match editor.update_in(cx, |editor, window, cx| {
14845 editor.find_all_references(&FindAllReferences, window, cx)
14846 })? {
14847 Some(references) => references.await,
14848 None => Ok(Navigated::No),
14849 }
14850 }
14851 }
14852 })
14853 }
14854
14855 pub fn go_to_declaration(
14856 &mut self,
14857 _: &GoToDeclaration,
14858 window: &mut Window,
14859 cx: &mut Context<Self>,
14860 ) -> Task<Result<Navigated>> {
14861 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14862 }
14863
14864 pub fn go_to_declaration_split(
14865 &mut self,
14866 _: &GoToDeclaration,
14867 window: &mut Window,
14868 cx: &mut Context<Self>,
14869 ) -> Task<Result<Navigated>> {
14870 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14871 }
14872
14873 pub fn go_to_implementation(
14874 &mut self,
14875 _: &GoToImplementation,
14876 window: &mut Window,
14877 cx: &mut Context<Self>,
14878 ) -> Task<Result<Navigated>> {
14879 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14880 }
14881
14882 pub fn go_to_implementation_split(
14883 &mut self,
14884 _: &GoToImplementationSplit,
14885 window: &mut Window,
14886 cx: &mut Context<Self>,
14887 ) -> Task<Result<Navigated>> {
14888 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14889 }
14890
14891 pub fn go_to_type_definition(
14892 &mut self,
14893 _: &GoToTypeDefinition,
14894 window: &mut Window,
14895 cx: &mut Context<Self>,
14896 ) -> Task<Result<Navigated>> {
14897 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14898 }
14899
14900 pub fn go_to_definition_split(
14901 &mut self,
14902 _: &GoToDefinitionSplit,
14903 window: &mut Window,
14904 cx: &mut Context<Self>,
14905 ) -> Task<Result<Navigated>> {
14906 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14907 }
14908
14909 pub fn go_to_type_definition_split(
14910 &mut self,
14911 _: &GoToTypeDefinitionSplit,
14912 window: &mut Window,
14913 cx: &mut Context<Self>,
14914 ) -> Task<Result<Navigated>> {
14915 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14916 }
14917
14918 fn go_to_definition_of_kind(
14919 &mut self,
14920 kind: GotoDefinitionKind,
14921 split: bool,
14922 window: &mut Window,
14923 cx: &mut Context<Self>,
14924 ) -> Task<Result<Navigated>> {
14925 let Some(provider) = self.semantics_provider.clone() else {
14926 return Task::ready(Ok(Navigated::No));
14927 };
14928 let head = self.selections.newest::<usize>(cx).head();
14929 let buffer = self.buffer.read(cx);
14930 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14931 text_anchor
14932 } else {
14933 return Task::ready(Ok(Navigated::No));
14934 };
14935
14936 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14937 return Task::ready(Ok(Navigated::No));
14938 };
14939
14940 cx.spawn_in(window, async move |editor, cx| {
14941 let definitions = definitions.await?;
14942 let navigated = editor
14943 .update_in(cx, |editor, window, cx| {
14944 editor.navigate_to_hover_links(
14945 Some(kind),
14946 definitions
14947 .into_iter()
14948 .filter(|location| {
14949 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14950 })
14951 .map(HoverLink::Text)
14952 .collect::<Vec<_>>(),
14953 split,
14954 window,
14955 cx,
14956 )
14957 })?
14958 .await?;
14959 anyhow::Ok(navigated)
14960 })
14961 }
14962
14963 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14964 let selection = self.selections.newest_anchor();
14965 let head = selection.head();
14966 let tail = selection.tail();
14967
14968 let Some((buffer, start_position)) =
14969 self.buffer.read(cx).text_anchor_for_position(head, cx)
14970 else {
14971 return;
14972 };
14973
14974 let end_position = if head != tail {
14975 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14976 return;
14977 };
14978 Some(pos)
14979 } else {
14980 None
14981 };
14982
14983 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14984 let url = if let Some(end_pos) = end_position {
14985 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14986 } else {
14987 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14988 };
14989
14990 if let Some(url) = url {
14991 editor.update(cx, |_, cx| {
14992 cx.open_url(&url);
14993 })
14994 } else {
14995 Ok(())
14996 }
14997 });
14998
14999 url_finder.detach();
15000 }
15001
15002 pub fn open_selected_filename(
15003 &mut self,
15004 _: &OpenSelectedFilename,
15005 window: &mut Window,
15006 cx: &mut Context<Self>,
15007 ) {
15008 let Some(workspace) = self.workspace() else {
15009 return;
15010 };
15011
15012 let position = self.selections.newest_anchor().head();
15013
15014 let Some((buffer, buffer_position)) =
15015 self.buffer.read(cx).text_anchor_for_position(position, cx)
15016 else {
15017 return;
15018 };
15019
15020 let project = self.project.clone();
15021
15022 cx.spawn_in(window, async move |_, cx| {
15023 let result = find_file(&buffer, project, buffer_position, cx).await;
15024
15025 if let Some((_, path)) = result {
15026 workspace
15027 .update_in(cx, |workspace, window, cx| {
15028 workspace.open_resolved_path(path, window, cx)
15029 })?
15030 .await?;
15031 }
15032 anyhow::Ok(())
15033 })
15034 .detach();
15035 }
15036
15037 pub(crate) fn navigate_to_hover_links(
15038 &mut self,
15039 kind: Option<GotoDefinitionKind>,
15040 mut definitions: Vec<HoverLink>,
15041 split: bool,
15042 window: &mut Window,
15043 cx: &mut Context<Editor>,
15044 ) -> Task<Result<Navigated>> {
15045 // If there is one definition, just open it directly
15046 if definitions.len() == 1 {
15047 let definition = definitions.pop().unwrap();
15048
15049 enum TargetTaskResult {
15050 Location(Option<Location>),
15051 AlreadyNavigated,
15052 }
15053
15054 let target_task = match definition {
15055 HoverLink::Text(link) => {
15056 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
15057 }
15058 HoverLink::InlayHint(lsp_location, server_id) => {
15059 let computation =
15060 self.compute_target_location(lsp_location, server_id, window, cx);
15061 cx.background_spawn(async move {
15062 let location = computation.await?;
15063 Ok(TargetTaskResult::Location(location))
15064 })
15065 }
15066 HoverLink::Url(url) => {
15067 cx.open_url(&url);
15068 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
15069 }
15070 HoverLink::File(path) => {
15071 if let Some(workspace) = self.workspace() {
15072 cx.spawn_in(window, async move |_, cx| {
15073 workspace
15074 .update_in(cx, |workspace, window, cx| {
15075 workspace.open_resolved_path(path, window, cx)
15076 })?
15077 .await
15078 .map(|_| TargetTaskResult::AlreadyNavigated)
15079 })
15080 } else {
15081 Task::ready(Ok(TargetTaskResult::Location(None)))
15082 }
15083 }
15084 };
15085 cx.spawn_in(window, async move |editor, cx| {
15086 let target = match target_task.await.context("target resolution task")? {
15087 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
15088 TargetTaskResult::Location(None) => return Ok(Navigated::No),
15089 TargetTaskResult::Location(Some(target)) => target,
15090 };
15091
15092 editor.update_in(cx, |editor, window, cx| {
15093 let Some(workspace) = editor.workspace() else {
15094 return Navigated::No;
15095 };
15096 let pane = workspace.read(cx).active_pane().clone();
15097
15098 let range = target.range.to_point(target.buffer.read(cx));
15099 let range = editor.range_for_match(&range);
15100 let range = collapse_multiline_range(range);
15101
15102 if !split
15103 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15104 {
15105 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15106 } else {
15107 window.defer(cx, move |window, cx| {
15108 let target_editor: Entity<Self> =
15109 workspace.update(cx, |workspace, cx| {
15110 let pane = if split {
15111 workspace.adjacent_pane(window, cx)
15112 } else {
15113 workspace.active_pane().clone()
15114 };
15115
15116 workspace.open_project_item(
15117 pane,
15118 target.buffer.clone(),
15119 true,
15120 true,
15121 window,
15122 cx,
15123 )
15124 });
15125 target_editor.update(cx, |target_editor, cx| {
15126 // When selecting a definition in a different buffer, disable the nav history
15127 // to avoid creating a history entry at the previous cursor location.
15128 pane.update(cx, |pane, _| pane.disable_history());
15129 target_editor.go_to_singleton_buffer_range(range, window, cx);
15130 pane.update(cx, |pane, _| pane.enable_history());
15131 });
15132 });
15133 }
15134 Navigated::Yes
15135 })
15136 })
15137 } else if !definitions.is_empty() {
15138 cx.spawn_in(window, async move |editor, cx| {
15139 let (title, location_tasks, workspace) = editor
15140 .update_in(cx, |editor, window, cx| {
15141 let tab_kind = match kind {
15142 Some(GotoDefinitionKind::Implementation) => "Implementations",
15143 _ => "Definitions",
15144 };
15145 let title = definitions
15146 .iter()
15147 .find_map(|definition| match definition {
15148 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15149 let buffer = origin.buffer.read(cx);
15150 format!(
15151 "{} for {}",
15152 tab_kind,
15153 buffer
15154 .text_for_range(origin.range.clone())
15155 .collect::<String>()
15156 )
15157 }),
15158 HoverLink::InlayHint(_, _) => None,
15159 HoverLink::Url(_) => None,
15160 HoverLink::File(_) => None,
15161 })
15162 .unwrap_or(tab_kind.to_string());
15163 let location_tasks = definitions
15164 .into_iter()
15165 .map(|definition| match definition {
15166 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15167 HoverLink::InlayHint(lsp_location, server_id) => editor
15168 .compute_target_location(lsp_location, server_id, window, cx),
15169 HoverLink::Url(_) => Task::ready(Ok(None)),
15170 HoverLink::File(_) => Task::ready(Ok(None)),
15171 })
15172 .collect::<Vec<_>>();
15173 (title, location_tasks, editor.workspace().clone())
15174 })
15175 .context("location tasks preparation")?;
15176
15177 let locations: Vec<Location> = future::join_all(location_tasks)
15178 .await
15179 .into_iter()
15180 .filter_map(|location| location.transpose())
15181 .collect::<Result<_>>()
15182 .context("location tasks")?;
15183
15184 if locations.is_empty() {
15185 return Ok(Navigated::No);
15186 }
15187
15188 let Some(workspace) = workspace else {
15189 return Ok(Navigated::No);
15190 };
15191
15192 let opened = workspace
15193 .update_in(cx, |workspace, window, cx| {
15194 Self::open_locations_in_multibuffer(
15195 workspace,
15196 locations,
15197 title,
15198 split,
15199 MultibufferSelectionMode::First,
15200 window,
15201 cx,
15202 )
15203 })
15204 .ok();
15205
15206 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15207 })
15208 } else {
15209 Task::ready(Ok(Navigated::No))
15210 }
15211 }
15212
15213 fn compute_target_location(
15214 &self,
15215 lsp_location: lsp::Location,
15216 server_id: LanguageServerId,
15217 window: &mut Window,
15218 cx: &mut Context<Self>,
15219 ) -> Task<anyhow::Result<Option<Location>>> {
15220 let Some(project) = self.project.clone() else {
15221 return Task::ready(Ok(None));
15222 };
15223
15224 cx.spawn_in(window, async move |editor, cx| {
15225 let location_task = editor.update(cx, |_, cx| {
15226 project.update(cx, |project, cx| {
15227 let language_server_name = project
15228 .language_server_statuses(cx)
15229 .find(|(id, _)| server_id == *id)
15230 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15231 language_server_name.map(|language_server_name| {
15232 project.open_local_buffer_via_lsp(
15233 lsp_location.uri.clone(),
15234 server_id,
15235 language_server_name,
15236 cx,
15237 )
15238 })
15239 })
15240 })?;
15241 let location = match location_task {
15242 Some(task) => Some({
15243 let target_buffer_handle = task.await.context("open local buffer")?;
15244 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15245 let target_start = target_buffer
15246 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15247 let target_end = target_buffer
15248 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15249 target_buffer.anchor_after(target_start)
15250 ..target_buffer.anchor_before(target_end)
15251 })?;
15252 Location {
15253 buffer: target_buffer_handle,
15254 range,
15255 }
15256 }),
15257 None => None,
15258 };
15259 Ok(location)
15260 })
15261 }
15262
15263 pub fn find_all_references(
15264 &mut self,
15265 _: &FindAllReferences,
15266 window: &mut Window,
15267 cx: &mut Context<Self>,
15268 ) -> Option<Task<Result<Navigated>>> {
15269 let selection = self.selections.newest::<usize>(cx);
15270 let multi_buffer = self.buffer.read(cx);
15271 let head = selection.head();
15272
15273 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15274 let head_anchor = multi_buffer_snapshot.anchor_at(
15275 head,
15276 if head < selection.tail() {
15277 Bias::Right
15278 } else {
15279 Bias::Left
15280 },
15281 );
15282
15283 match self
15284 .find_all_references_task_sources
15285 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15286 {
15287 Ok(_) => {
15288 log::info!(
15289 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15290 );
15291 return None;
15292 }
15293 Err(i) => {
15294 self.find_all_references_task_sources.insert(i, head_anchor);
15295 }
15296 }
15297
15298 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15299 let workspace = self.workspace()?;
15300 let project = workspace.read(cx).project().clone();
15301 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15302 Some(cx.spawn_in(window, async move |editor, cx| {
15303 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15304 if let Ok(i) = editor
15305 .find_all_references_task_sources
15306 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15307 {
15308 editor.find_all_references_task_sources.remove(i);
15309 }
15310 });
15311
15312 let locations = references.await?;
15313 if locations.is_empty() {
15314 return anyhow::Ok(Navigated::No);
15315 }
15316
15317 workspace.update_in(cx, |workspace, window, cx| {
15318 let title = locations
15319 .first()
15320 .as_ref()
15321 .map(|location| {
15322 let buffer = location.buffer.read(cx);
15323 format!(
15324 "References to `{}`",
15325 buffer
15326 .text_for_range(location.range.clone())
15327 .collect::<String>()
15328 )
15329 })
15330 .unwrap();
15331 Self::open_locations_in_multibuffer(
15332 workspace,
15333 locations,
15334 title,
15335 false,
15336 MultibufferSelectionMode::First,
15337 window,
15338 cx,
15339 );
15340 Navigated::Yes
15341 })
15342 }))
15343 }
15344
15345 /// Opens a multibuffer with the given project locations in it
15346 pub fn open_locations_in_multibuffer(
15347 workspace: &mut Workspace,
15348 mut locations: Vec<Location>,
15349 title: String,
15350 split: bool,
15351 multibuffer_selection_mode: MultibufferSelectionMode,
15352 window: &mut Window,
15353 cx: &mut Context<Workspace>,
15354 ) {
15355 if locations.is_empty() {
15356 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
15357 return;
15358 }
15359
15360 // If there are multiple definitions, open them in a multibuffer
15361 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15362 let mut locations = locations.into_iter().peekable();
15363 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15364 let capability = workspace.project().read(cx).capability();
15365
15366 let excerpt_buffer = cx.new(|cx| {
15367 let mut multibuffer = MultiBuffer::new(capability);
15368 while let Some(location) = locations.next() {
15369 let buffer = location.buffer.read(cx);
15370 let mut ranges_for_buffer = Vec::new();
15371 let range = location.range.to_point(buffer);
15372 ranges_for_buffer.push(range.clone());
15373
15374 while let Some(next_location) = locations.peek() {
15375 if next_location.buffer == location.buffer {
15376 ranges_for_buffer.push(next_location.range.to_point(buffer));
15377 locations.next();
15378 } else {
15379 break;
15380 }
15381 }
15382
15383 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15384 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15385 PathKey::for_buffer(&location.buffer, cx),
15386 location.buffer.clone(),
15387 ranges_for_buffer,
15388 DEFAULT_MULTIBUFFER_CONTEXT,
15389 cx,
15390 );
15391 ranges.extend(new_ranges)
15392 }
15393
15394 multibuffer.with_title(title)
15395 });
15396
15397 let editor = cx.new(|cx| {
15398 Editor::for_multibuffer(
15399 excerpt_buffer,
15400 Some(workspace.project().clone()),
15401 window,
15402 cx,
15403 )
15404 });
15405 editor.update(cx, |editor, cx| {
15406 match multibuffer_selection_mode {
15407 MultibufferSelectionMode::First => {
15408 if let Some(first_range) = ranges.first() {
15409 editor.change_selections(None, window, cx, |selections| {
15410 selections.clear_disjoint();
15411 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
15412 });
15413 }
15414 editor.highlight_background::<Self>(
15415 &ranges,
15416 |theme| theme.colors().editor_highlighted_line_background,
15417 cx,
15418 );
15419 }
15420 MultibufferSelectionMode::All => {
15421 editor.change_selections(None, window, cx, |selections| {
15422 selections.clear_disjoint();
15423 selections.select_anchor_ranges(ranges);
15424 });
15425 }
15426 }
15427 editor.register_buffers_with_language_servers(cx);
15428 });
15429
15430 let item = Box::new(editor);
15431 let item_id = item.item_id();
15432
15433 if split {
15434 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15435 } else {
15436 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15437 let (preview_item_id, preview_item_idx) =
15438 workspace.active_pane().read_with(cx, |pane, _| {
15439 (pane.preview_item_id(), pane.preview_item_idx())
15440 });
15441
15442 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15443
15444 if let Some(preview_item_id) = preview_item_id {
15445 workspace.active_pane().update(cx, |pane, cx| {
15446 pane.remove_item(preview_item_id, false, false, window, cx);
15447 });
15448 }
15449 } else {
15450 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15451 }
15452 }
15453 workspace.active_pane().update(cx, |pane, cx| {
15454 pane.set_preview_item_id(Some(item_id), cx);
15455 });
15456 }
15457
15458 pub fn rename(
15459 &mut self,
15460 _: &Rename,
15461 window: &mut Window,
15462 cx: &mut Context<Self>,
15463 ) -> Option<Task<Result<()>>> {
15464 use language::ToOffset as _;
15465
15466 let provider = self.semantics_provider.clone()?;
15467 let selection = self.selections.newest_anchor().clone();
15468 let (cursor_buffer, cursor_buffer_position) = self
15469 .buffer
15470 .read(cx)
15471 .text_anchor_for_position(selection.head(), cx)?;
15472 let (tail_buffer, cursor_buffer_position_end) = self
15473 .buffer
15474 .read(cx)
15475 .text_anchor_for_position(selection.tail(), cx)?;
15476 if tail_buffer != cursor_buffer {
15477 return None;
15478 }
15479
15480 let snapshot = cursor_buffer.read(cx).snapshot();
15481 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15482 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15483 let prepare_rename = provider
15484 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15485 .unwrap_or_else(|| Task::ready(Ok(None)));
15486 drop(snapshot);
15487
15488 Some(cx.spawn_in(window, async move |this, cx| {
15489 let rename_range = if let Some(range) = prepare_rename.await? {
15490 Some(range)
15491 } else {
15492 this.update(cx, |this, cx| {
15493 let buffer = this.buffer.read(cx).snapshot(cx);
15494 let mut buffer_highlights = this
15495 .document_highlights_for_position(selection.head(), &buffer)
15496 .filter(|highlight| {
15497 highlight.start.excerpt_id == selection.head().excerpt_id
15498 && highlight.end.excerpt_id == selection.head().excerpt_id
15499 });
15500 buffer_highlights
15501 .next()
15502 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15503 })?
15504 };
15505 if let Some(rename_range) = rename_range {
15506 this.update_in(cx, |this, window, cx| {
15507 let snapshot = cursor_buffer.read(cx).snapshot();
15508 let rename_buffer_range = rename_range.to_offset(&snapshot);
15509 let cursor_offset_in_rename_range =
15510 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15511 let cursor_offset_in_rename_range_end =
15512 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15513
15514 this.take_rename(false, window, cx);
15515 let buffer = this.buffer.read(cx).read(cx);
15516 let cursor_offset = selection.head().to_offset(&buffer);
15517 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15518 let rename_end = rename_start + rename_buffer_range.len();
15519 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15520 let mut old_highlight_id = None;
15521 let old_name: Arc<str> = buffer
15522 .chunks(rename_start..rename_end, true)
15523 .map(|chunk| {
15524 if old_highlight_id.is_none() {
15525 old_highlight_id = chunk.syntax_highlight_id;
15526 }
15527 chunk.text
15528 })
15529 .collect::<String>()
15530 .into();
15531
15532 drop(buffer);
15533
15534 // Position the selection in the rename editor so that it matches the current selection.
15535 this.show_local_selections = false;
15536 let rename_editor = cx.new(|cx| {
15537 let mut editor = Editor::single_line(window, cx);
15538 editor.buffer.update(cx, |buffer, cx| {
15539 buffer.edit([(0..0, old_name.clone())], None, cx)
15540 });
15541 let rename_selection_range = match cursor_offset_in_rename_range
15542 .cmp(&cursor_offset_in_rename_range_end)
15543 {
15544 Ordering::Equal => {
15545 editor.select_all(&SelectAll, window, cx);
15546 return editor;
15547 }
15548 Ordering::Less => {
15549 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15550 }
15551 Ordering::Greater => {
15552 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15553 }
15554 };
15555 if rename_selection_range.end > old_name.len() {
15556 editor.select_all(&SelectAll, window, cx);
15557 } else {
15558 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15559 s.select_ranges([rename_selection_range]);
15560 });
15561 }
15562 editor
15563 });
15564 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15565 if e == &EditorEvent::Focused {
15566 cx.emit(EditorEvent::FocusedIn)
15567 }
15568 })
15569 .detach();
15570
15571 let write_highlights = this
15572 .clear_background_highlights::<DocumentHighlightWrite>(cx)
15573 .unwrap_or_default();
15574 let read_highlights = this
15575 .clear_background_highlights::<DocumentHighlightRead>(cx)
15576 .unwrap_or_default();
15577 let ranges = write_highlights
15578 .iter()
15579 .chain(read_highlights.iter())
15580 .cloned()
15581 .map(|highlight| {
15582 (
15583 highlight.range,
15584 HighlightStyle {
15585 fade_out: Some(0.6),
15586 ..Default::default()
15587 },
15588 )
15589 })
15590 .collect();
15591
15592 this.highlight_text::<Rename>(ranges, cx);
15593 let rename_focus_handle = rename_editor.focus_handle(cx);
15594 window.focus(&rename_focus_handle);
15595 let block_id = this.insert_blocks(
15596 [BlockProperties {
15597 style: BlockStyle::Flex,
15598 placement: BlockPlacement::Below(range.start),
15599 height: Some(1),
15600 render: Arc::new({
15601 let rename_editor = rename_editor.clone();
15602 move |cx: &mut BlockContext| {
15603 let mut text_style = cx.editor_style.text.clone();
15604 if let Some(highlight_style) = old_highlight_id
15605 .and_then(|h| h.style(&cx.editor_style.syntax))
15606 {
15607 text_style = text_style.highlight(highlight_style);
15608 }
15609 div()
15610 .block_mouse_except_scroll()
15611 .pl(cx.anchor_x)
15612 .child(EditorElement::new(
15613 &rename_editor,
15614 EditorStyle {
15615 background: cx.theme().system().transparent,
15616 local_player: cx.editor_style.local_player,
15617 text: text_style,
15618 scrollbar_width: cx.editor_style.scrollbar_width,
15619 syntax: cx.editor_style.syntax.clone(),
15620 status: cx.editor_style.status.clone(),
15621 inlay_hints_style: HighlightStyle {
15622 font_weight: Some(FontWeight::BOLD),
15623 ..make_inlay_hints_style(cx.app)
15624 },
15625 inline_completion_styles: make_suggestion_styles(
15626 cx.app,
15627 ),
15628 ..EditorStyle::default()
15629 },
15630 ))
15631 .into_any_element()
15632 }
15633 }),
15634 priority: 0,
15635 render_in_minimap: true,
15636 }],
15637 Some(Autoscroll::fit()),
15638 cx,
15639 )[0];
15640 this.pending_rename = Some(RenameState {
15641 range,
15642 old_name,
15643 editor: rename_editor,
15644 block_id,
15645 });
15646 })?;
15647 }
15648
15649 Ok(())
15650 }))
15651 }
15652
15653 pub fn confirm_rename(
15654 &mut self,
15655 _: &ConfirmRename,
15656 window: &mut Window,
15657 cx: &mut Context<Self>,
15658 ) -> Option<Task<Result<()>>> {
15659 let rename = self.take_rename(false, window, cx)?;
15660 let workspace = self.workspace()?.downgrade();
15661 let (buffer, start) = self
15662 .buffer
15663 .read(cx)
15664 .text_anchor_for_position(rename.range.start, cx)?;
15665 let (end_buffer, _) = self
15666 .buffer
15667 .read(cx)
15668 .text_anchor_for_position(rename.range.end, cx)?;
15669 if buffer != end_buffer {
15670 return None;
15671 }
15672
15673 let old_name = rename.old_name;
15674 let new_name = rename.editor.read(cx).text(cx);
15675
15676 let rename = self.semantics_provider.as_ref()?.perform_rename(
15677 &buffer,
15678 start,
15679 new_name.clone(),
15680 cx,
15681 )?;
15682
15683 Some(cx.spawn_in(window, async move |editor, cx| {
15684 let project_transaction = rename.await?;
15685 Self::open_project_transaction(
15686 &editor,
15687 workspace,
15688 project_transaction,
15689 format!("Rename: {} → {}", old_name, new_name),
15690 cx,
15691 )
15692 .await?;
15693
15694 editor.update(cx, |editor, cx| {
15695 editor.refresh_document_highlights(cx);
15696 })?;
15697 Ok(())
15698 }))
15699 }
15700
15701 fn take_rename(
15702 &mut self,
15703 moving_cursor: bool,
15704 window: &mut Window,
15705 cx: &mut Context<Self>,
15706 ) -> Option<RenameState> {
15707 let rename = self.pending_rename.take()?;
15708 if rename.editor.focus_handle(cx).is_focused(window) {
15709 window.focus(&self.focus_handle);
15710 }
15711
15712 self.remove_blocks(
15713 [rename.block_id].into_iter().collect(),
15714 Some(Autoscroll::fit()),
15715 cx,
15716 );
15717 self.clear_highlights::<Rename>(cx);
15718 self.show_local_selections = true;
15719
15720 if moving_cursor {
15721 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15722 editor.selections.newest::<usize>(cx).head()
15723 });
15724
15725 // Update the selection to match the position of the selection inside
15726 // the rename editor.
15727 let snapshot = self.buffer.read(cx).read(cx);
15728 let rename_range = rename.range.to_offset(&snapshot);
15729 let cursor_in_editor = snapshot
15730 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15731 .min(rename_range.end);
15732 drop(snapshot);
15733
15734 self.change_selections(None, window, cx, |s| {
15735 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15736 });
15737 } else {
15738 self.refresh_document_highlights(cx);
15739 }
15740
15741 Some(rename)
15742 }
15743
15744 pub fn pending_rename(&self) -> Option<&RenameState> {
15745 self.pending_rename.as_ref()
15746 }
15747
15748 fn format(
15749 &mut self,
15750 _: &Format,
15751 window: &mut Window,
15752 cx: &mut Context<Self>,
15753 ) -> Option<Task<Result<()>>> {
15754 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15755
15756 let project = match &self.project {
15757 Some(project) => project.clone(),
15758 None => return None,
15759 };
15760
15761 Some(self.perform_format(
15762 project,
15763 FormatTrigger::Manual,
15764 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
15765 window,
15766 cx,
15767 ))
15768 }
15769
15770 fn format_selections(
15771 &mut self,
15772 _: &FormatSelections,
15773 window: &mut Window,
15774 cx: &mut Context<Self>,
15775 ) -> Option<Task<Result<()>>> {
15776 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15777
15778 let project = match &self.project {
15779 Some(project) => project.clone(),
15780 None => return None,
15781 };
15782
15783 let ranges = self
15784 .selections
15785 .all_adjusted(cx)
15786 .into_iter()
15787 .map(|selection| selection.range())
15788 .collect_vec();
15789
15790 Some(self.perform_format(
15791 project,
15792 FormatTrigger::Manual,
15793 FormatTarget::Ranges(ranges),
15794 window,
15795 cx,
15796 ))
15797 }
15798
15799 fn perform_format(
15800 &mut self,
15801 project: Entity<Project>,
15802 trigger: FormatTrigger,
15803 target: FormatTarget,
15804 window: &mut Window,
15805 cx: &mut Context<Self>,
15806 ) -> Task<Result<()>> {
15807 let buffer = self.buffer.clone();
15808 let (buffers, target) = match target {
15809 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
15810 FormatTarget::Ranges(selection_ranges) => {
15811 let multi_buffer = buffer.read(cx);
15812 let snapshot = multi_buffer.read(cx);
15813 let mut buffers = HashSet::default();
15814 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15815 BTreeMap::new();
15816 for selection_range in selection_ranges {
15817 for (buffer, buffer_range, _) in
15818 snapshot.range_to_buffer_ranges(selection_range)
15819 {
15820 let buffer_id = buffer.remote_id();
15821 let start = buffer.anchor_before(buffer_range.start);
15822 let end = buffer.anchor_after(buffer_range.end);
15823 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15824 buffer_id_to_ranges
15825 .entry(buffer_id)
15826 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15827 .or_insert_with(|| vec![start..end]);
15828 }
15829 }
15830 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15831 }
15832 };
15833
15834 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15835 let selections_prev = transaction_id_prev
15836 .and_then(|transaction_id_prev| {
15837 // default to selections as they were after the last edit, if we have them,
15838 // instead of how they are now.
15839 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15840 // will take you back to where you made the last edit, instead of staying where you scrolled
15841 self.selection_history
15842 .transaction(transaction_id_prev)
15843 .map(|t| t.0.clone())
15844 })
15845 .unwrap_or_else(|| {
15846 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15847 self.selections.disjoint_anchors()
15848 });
15849
15850 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15851 let format = project.update(cx, |project, cx| {
15852 project.format(buffers, target, true, trigger, cx)
15853 });
15854
15855 cx.spawn_in(window, async move |editor, cx| {
15856 let transaction = futures::select_biased! {
15857 transaction = format.log_err().fuse() => transaction,
15858 () = timeout => {
15859 log::warn!("timed out waiting for formatting");
15860 None
15861 }
15862 };
15863
15864 buffer
15865 .update(cx, |buffer, cx| {
15866 if let Some(transaction) = transaction {
15867 if !buffer.is_singleton() {
15868 buffer.push_transaction(&transaction.0, cx);
15869 }
15870 }
15871 cx.notify();
15872 })
15873 .ok();
15874
15875 if let Some(transaction_id_now) =
15876 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15877 {
15878 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15879 if has_new_transaction {
15880 _ = editor.update(cx, |editor, _| {
15881 editor
15882 .selection_history
15883 .insert_transaction(transaction_id_now, selections_prev);
15884 });
15885 }
15886 }
15887
15888 Ok(())
15889 })
15890 }
15891
15892 fn organize_imports(
15893 &mut self,
15894 _: &OrganizeImports,
15895 window: &mut Window,
15896 cx: &mut Context<Self>,
15897 ) -> Option<Task<Result<()>>> {
15898 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15899 let project = match &self.project {
15900 Some(project) => project.clone(),
15901 None => return None,
15902 };
15903 Some(self.perform_code_action_kind(
15904 project,
15905 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15906 window,
15907 cx,
15908 ))
15909 }
15910
15911 fn perform_code_action_kind(
15912 &mut self,
15913 project: Entity<Project>,
15914 kind: CodeActionKind,
15915 window: &mut Window,
15916 cx: &mut Context<Self>,
15917 ) -> Task<Result<()>> {
15918 let buffer = self.buffer.clone();
15919 let buffers = buffer.read(cx).all_buffers();
15920 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15921 let apply_action = project.update(cx, |project, cx| {
15922 project.apply_code_action_kind(buffers, kind, true, cx)
15923 });
15924 cx.spawn_in(window, async move |_, cx| {
15925 let transaction = futures::select_biased! {
15926 () = timeout => {
15927 log::warn!("timed out waiting for executing code action");
15928 None
15929 }
15930 transaction = apply_action.log_err().fuse() => transaction,
15931 };
15932 buffer
15933 .update(cx, |buffer, cx| {
15934 // check if we need this
15935 if let Some(transaction) = transaction {
15936 if !buffer.is_singleton() {
15937 buffer.push_transaction(&transaction.0, cx);
15938 }
15939 }
15940 cx.notify();
15941 })
15942 .ok();
15943 Ok(())
15944 })
15945 }
15946
15947 fn restart_language_server(
15948 &mut self,
15949 _: &RestartLanguageServer,
15950 _: &mut Window,
15951 cx: &mut Context<Self>,
15952 ) {
15953 if let Some(project) = self.project.clone() {
15954 self.buffer.update(cx, |multi_buffer, cx| {
15955 project.update(cx, |project, cx| {
15956 project.restart_language_servers_for_buffers(
15957 multi_buffer.all_buffers().into_iter().collect(),
15958 cx,
15959 );
15960 });
15961 })
15962 }
15963 }
15964
15965 fn stop_language_server(
15966 &mut self,
15967 _: &StopLanguageServer,
15968 _: &mut Window,
15969 cx: &mut Context<Self>,
15970 ) {
15971 if let Some(project) = self.project.clone() {
15972 self.buffer.update(cx, |multi_buffer, cx| {
15973 project.update(cx, |project, cx| {
15974 project.stop_language_servers_for_buffers(
15975 multi_buffer.all_buffers().into_iter().collect(),
15976 cx,
15977 );
15978 cx.emit(project::Event::RefreshInlayHints);
15979 });
15980 });
15981 }
15982 }
15983
15984 fn cancel_language_server_work(
15985 workspace: &mut Workspace,
15986 _: &actions::CancelLanguageServerWork,
15987 _: &mut Window,
15988 cx: &mut Context<Workspace>,
15989 ) {
15990 let project = workspace.project();
15991 let buffers = workspace
15992 .active_item(cx)
15993 .and_then(|item| item.act_as::<Editor>(cx))
15994 .map_or(HashSet::default(), |editor| {
15995 editor.read(cx).buffer.read(cx).all_buffers()
15996 });
15997 project.update(cx, |project, cx| {
15998 project.cancel_language_server_work_for_buffers(buffers, cx);
15999 });
16000 }
16001
16002 fn show_character_palette(
16003 &mut self,
16004 _: &ShowCharacterPalette,
16005 window: &mut Window,
16006 _: &mut Context<Self>,
16007 ) {
16008 window.show_character_palette();
16009 }
16010
16011 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16012 if self.mode.is_minimap() {
16013 return;
16014 }
16015
16016 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16017 let buffer = self.buffer.read(cx).snapshot(cx);
16018 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16019 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16020 let is_valid = buffer
16021 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16022 .any(|entry| {
16023 entry.diagnostic.is_primary
16024 && !entry.range.is_empty()
16025 && entry.range.start == primary_range_start
16026 && entry.diagnostic.message == active_diagnostics.active_message
16027 });
16028
16029 if !is_valid {
16030 self.dismiss_diagnostics(cx);
16031 }
16032 }
16033 }
16034
16035 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16036 match &self.active_diagnostics {
16037 ActiveDiagnostic::Group(group) => Some(group),
16038 _ => None,
16039 }
16040 }
16041
16042 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16043 self.dismiss_diagnostics(cx);
16044 self.active_diagnostics = ActiveDiagnostic::All;
16045 }
16046
16047 fn activate_diagnostics(
16048 &mut self,
16049 buffer_id: BufferId,
16050 diagnostic: DiagnosticEntry<usize>,
16051 window: &mut Window,
16052 cx: &mut Context<Self>,
16053 ) {
16054 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16055 return;
16056 }
16057 self.dismiss_diagnostics(cx);
16058 let snapshot = self.snapshot(window, cx);
16059 let buffer = self.buffer.read(cx).snapshot(cx);
16060 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16061 return;
16062 };
16063
16064 let diagnostic_group = buffer
16065 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16066 .collect::<Vec<_>>();
16067
16068 let blocks =
16069 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16070
16071 let blocks = self.display_map.update(cx, |display_map, cx| {
16072 display_map.insert_blocks(blocks, cx).into_iter().collect()
16073 });
16074 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16075 active_range: buffer.anchor_before(diagnostic.range.start)
16076 ..buffer.anchor_after(diagnostic.range.end),
16077 active_message: diagnostic.diagnostic.message.clone(),
16078 group_id: diagnostic.diagnostic.group_id,
16079 blocks,
16080 });
16081 cx.notify();
16082 }
16083
16084 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16085 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16086 return;
16087 };
16088
16089 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16090 if let ActiveDiagnostic::Group(group) = prev {
16091 self.display_map.update(cx, |display_map, cx| {
16092 display_map.remove_blocks(group.blocks, cx);
16093 });
16094 cx.notify();
16095 }
16096 }
16097
16098 /// Disable inline diagnostics rendering for this editor.
16099 pub fn disable_inline_diagnostics(&mut self) {
16100 self.inline_diagnostics_enabled = false;
16101 self.inline_diagnostics_update = Task::ready(());
16102 self.inline_diagnostics.clear();
16103 }
16104
16105 pub fn diagnostics_enabled(&self) -> bool {
16106 self.mode.is_full()
16107 }
16108
16109 pub fn inline_diagnostics_enabled(&self) -> bool {
16110 self.diagnostics_enabled() && self.inline_diagnostics_enabled
16111 }
16112
16113 pub fn show_inline_diagnostics(&self) -> bool {
16114 self.show_inline_diagnostics
16115 }
16116
16117 pub fn toggle_inline_diagnostics(
16118 &mut self,
16119 _: &ToggleInlineDiagnostics,
16120 window: &mut Window,
16121 cx: &mut Context<Editor>,
16122 ) {
16123 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16124 self.refresh_inline_diagnostics(false, window, cx);
16125 }
16126
16127 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16128 self.diagnostics_max_severity = severity;
16129 self.display_map.update(cx, |display_map, _| {
16130 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16131 });
16132 }
16133
16134 pub fn toggle_diagnostics(
16135 &mut self,
16136 _: &ToggleDiagnostics,
16137 window: &mut Window,
16138 cx: &mut Context<Editor>,
16139 ) {
16140 if !self.diagnostics_enabled() {
16141 return;
16142 }
16143
16144 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16145 EditorSettings::get_global(cx)
16146 .diagnostics_max_severity
16147 .filter(|severity| severity != &DiagnosticSeverity::Off)
16148 .unwrap_or(DiagnosticSeverity::Hint)
16149 } else {
16150 DiagnosticSeverity::Off
16151 };
16152 self.set_max_diagnostics_severity(new_severity, cx);
16153 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16154 self.active_diagnostics = ActiveDiagnostic::None;
16155 self.inline_diagnostics_update = Task::ready(());
16156 self.inline_diagnostics.clear();
16157 } else {
16158 self.refresh_inline_diagnostics(false, window, cx);
16159 }
16160
16161 cx.notify();
16162 }
16163
16164 pub fn toggle_minimap(
16165 &mut self,
16166 _: &ToggleMinimap,
16167 window: &mut Window,
16168 cx: &mut Context<Editor>,
16169 ) {
16170 if self.supports_minimap(cx) {
16171 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16172 }
16173 }
16174
16175 fn refresh_inline_diagnostics(
16176 &mut self,
16177 debounce: bool,
16178 window: &mut Window,
16179 cx: &mut Context<Self>,
16180 ) {
16181 let max_severity = ProjectSettings::get_global(cx)
16182 .diagnostics
16183 .inline
16184 .max_severity
16185 .unwrap_or(self.diagnostics_max_severity);
16186
16187 if !self.inline_diagnostics_enabled()
16188 || !self.show_inline_diagnostics
16189 || max_severity == DiagnosticSeverity::Off
16190 {
16191 self.inline_diagnostics_update = Task::ready(());
16192 self.inline_diagnostics.clear();
16193 return;
16194 }
16195
16196 let debounce_ms = ProjectSettings::get_global(cx)
16197 .diagnostics
16198 .inline
16199 .update_debounce_ms;
16200 let debounce = if debounce && debounce_ms > 0 {
16201 Some(Duration::from_millis(debounce_ms))
16202 } else {
16203 None
16204 };
16205 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16206 if let Some(debounce) = debounce {
16207 cx.background_executor().timer(debounce).await;
16208 }
16209 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16210 editor
16211 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16212 .ok()
16213 }) else {
16214 return;
16215 };
16216
16217 let new_inline_diagnostics = cx
16218 .background_spawn(async move {
16219 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16220 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16221 let message = diagnostic_entry
16222 .diagnostic
16223 .message
16224 .split_once('\n')
16225 .map(|(line, _)| line)
16226 .map(SharedString::new)
16227 .unwrap_or_else(|| {
16228 SharedString::from(diagnostic_entry.diagnostic.message)
16229 });
16230 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16231 let (Ok(i) | Err(i)) = inline_diagnostics
16232 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16233 inline_diagnostics.insert(
16234 i,
16235 (
16236 start_anchor,
16237 InlineDiagnostic {
16238 message,
16239 group_id: diagnostic_entry.diagnostic.group_id,
16240 start: diagnostic_entry.range.start.to_point(&snapshot),
16241 is_primary: diagnostic_entry.diagnostic.is_primary,
16242 severity: diagnostic_entry.diagnostic.severity,
16243 },
16244 ),
16245 );
16246 }
16247 inline_diagnostics
16248 })
16249 .await;
16250
16251 editor
16252 .update(cx, |editor, cx| {
16253 editor.inline_diagnostics = new_inline_diagnostics;
16254 cx.notify();
16255 })
16256 .ok();
16257 });
16258 }
16259
16260 fn pull_diagnostics(
16261 &mut self,
16262 buffer_id: Option<BufferId>,
16263 window: &Window,
16264 cx: &mut Context<Self>,
16265 ) -> Option<()> {
16266 if !self.mode().is_full() {
16267 return None;
16268 }
16269 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16270 .diagnostics
16271 .lsp_pull_diagnostics;
16272 if !pull_diagnostics_settings.enabled {
16273 return None;
16274 }
16275 let project = self.project.as_ref()?.downgrade();
16276 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16277 let mut buffers = self.buffer.read(cx).all_buffers();
16278 if let Some(buffer_id) = buffer_id {
16279 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16280 }
16281
16282 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16283 cx.background_executor().timer(debounce).await;
16284
16285 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16286 buffers
16287 .into_iter()
16288 .filter_map(|buffer| {
16289 project
16290 .update(cx, |project, cx| {
16291 project.lsp_store().update(cx, |lsp_store, cx| {
16292 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
16293 })
16294 })
16295 .ok()
16296 })
16297 .collect::<FuturesUnordered<_>>()
16298 }) else {
16299 return;
16300 };
16301
16302 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16303 match pull_task {
16304 Ok(()) => {
16305 if editor
16306 .update_in(cx, |editor, window, cx| {
16307 editor.update_diagnostics_state(window, cx);
16308 })
16309 .is_err()
16310 {
16311 return;
16312 }
16313 }
16314 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16315 }
16316 }
16317 });
16318
16319 Some(())
16320 }
16321
16322 pub fn set_selections_from_remote(
16323 &mut self,
16324 selections: Vec<Selection<Anchor>>,
16325 pending_selection: Option<Selection<Anchor>>,
16326 window: &mut Window,
16327 cx: &mut Context<Self>,
16328 ) {
16329 let old_cursor_position = self.selections.newest_anchor().head();
16330 self.selections.change_with(cx, |s| {
16331 s.select_anchors(selections);
16332 if let Some(pending_selection) = pending_selection {
16333 s.set_pending(pending_selection, SelectMode::Character);
16334 } else {
16335 s.clear_pending();
16336 }
16337 });
16338 self.selections_did_change(
16339 false,
16340 &old_cursor_position,
16341 SelectionEffects::default(),
16342 window,
16343 cx,
16344 );
16345 }
16346
16347 pub fn transact(
16348 &mut self,
16349 window: &mut Window,
16350 cx: &mut Context<Self>,
16351 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16352 ) -> Option<TransactionId> {
16353 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16354 this.start_transaction_at(Instant::now(), window, cx);
16355 update(this, window, cx);
16356 this.end_transaction_at(Instant::now(), cx)
16357 })
16358 }
16359
16360 pub fn start_transaction_at(
16361 &mut self,
16362 now: Instant,
16363 window: &mut Window,
16364 cx: &mut Context<Self>,
16365 ) {
16366 self.end_selection(window, cx);
16367 if let Some(tx_id) = self
16368 .buffer
16369 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16370 {
16371 self.selection_history
16372 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16373 cx.emit(EditorEvent::TransactionBegun {
16374 transaction_id: tx_id,
16375 })
16376 }
16377 }
16378
16379 pub fn end_transaction_at(
16380 &mut self,
16381 now: Instant,
16382 cx: &mut Context<Self>,
16383 ) -> Option<TransactionId> {
16384 if let Some(transaction_id) = self
16385 .buffer
16386 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16387 {
16388 if let Some((_, end_selections)) =
16389 self.selection_history.transaction_mut(transaction_id)
16390 {
16391 *end_selections = Some(self.selections.disjoint_anchors());
16392 } else {
16393 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16394 }
16395
16396 cx.emit(EditorEvent::Edited { transaction_id });
16397 Some(transaction_id)
16398 } else {
16399 None
16400 }
16401 }
16402
16403 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16404 if self.selection_mark_mode {
16405 self.change_selections(None, window, cx, |s| {
16406 s.move_with(|_, sel| {
16407 sel.collapse_to(sel.head(), SelectionGoal::None);
16408 });
16409 })
16410 }
16411 self.selection_mark_mode = true;
16412 cx.notify();
16413 }
16414
16415 pub fn swap_selection_ends(
16416 &mut self,
16417 _: &actions::SwapSelectionEnds,
16418 window: &mut Window,
16419 cx: &mut Context<Self>,
16420 ) {
16421 self.change_selections(None, window, cx, |s| {
16422 s.move_with(|_, sel| {
16423 if sel.start != sel.end {
16424 sel.reversed = !sel.reversed
16425 }
16426 });
16427 });
16428 self.request_autoscroll(Autoscroll::newest(), cx);
16429 cx.notify();
16430 }
16431
16432 pub fn toggle_fold(
16433 &mut self,
16434 _: &actions::ToggleFold,
16435 window: &mut Window,
16436 cx: &mut Context<Self>,
16437 ) {
16438 if self.is_singleton(cx) {
16439 let selection = self.selections.newest::<Point>(cx);
16440
16441 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16442 let range = if selection.is_empty() {
16443 let point = selection.head().to_display_point(&display_map);
16444 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16445 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16446 .to_point(&display_map);
16447 start..end
16448 } else {
16449 selection.range()
16450 };
16451 if display_map.folds_in_range(range).next().is_some() {
16452 self.unfold_lines(&Default::default(), window, cx)
16453 } else {
16454 self.fold(&Default::default(), window, cx)
16455 }
16456 } else {
16457 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16458 let buffer_ids: HashSet<_> = self
16459 .selections
16460 .disjoint_anchor_ranges()
16461 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16462 .collect();
16463
16464 let should_unfold = buffer_ids
16465 .iter()
16466 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16467
16468 for buffer_id in buffer_ids {
16469 if should_unfold {
16470 self.unfold_buffer(buffer_id, cx);
16471 } else {
16472 self.fold_buffer(buffer_id, cx);
16473 }
16474 }
16475 }
16476 }
16477
16478 pub fn toggle_fold_recursive(
16479 &mut self,
16480 _: &actions::ToggleFoldRecursive,
16481 window: &mut Window,
16482 cx: &mut Context<Self>,
16483 ) {
16484 let selection = self.selections.newest::<Point>(cx);
16485
16486 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16487 let range = if selection.is_empty() {
16488 let point = selection.head().to_display_point(&display_map);
16489 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16490 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16491 .to_point(&display_map);
16492 start..end
16493 } else {
16494 selection.range()
16495 };
16496 if display_map.folds_in_range(range).next().is_some() {
16497 self.unfold_recursive(&Default::default(), window, cx)
16498 } else {
16499 self.fold_recursive(&Default::default(), window, cx)
16500 }
16501 }
16502
16503 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16504 if self.is_singleton(cx) {
16505 let mut to_fold = Vec::new();
16506 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16507 let selections = self.selections.all_adjusted(cx);
16508
16509 for selection in selections {
16510 let range = selection.range().sorted();
16511 let buffer_start_row = range.start.row;
16512
16513 if range.start.row != range.end.row {
16514 let mut found = false;
16515 let mut row = range.start.row;
16516 while row <= range.end.row {
16517 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16518 {
16519 found = true;
16520 row = crease.range().end.row + 1;
16521 to_fold.push(crease);
16522 } else {
16523 row += 1
16524 }
16525 }
16526 if found {
16527 continue;
16528 }
16529 }
16530
16531 for row in (0..=range.start.row).rev() {
16532 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16533 if crease.range().end.row >= buffer_start_row {
16534 to_fold.push(crease);
16535 if row <= range.start.row {
16536 break;
16537 }
16538 }
16539 }
16540 }
16541 }
16542
16543 self.fold_creases(to_fold, true, window, cx);
16544 } else {
16545 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16546 let buffer_ids = self
16547 .selections
16548 .disjoint_anchor_ranges()
16549 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16550 .collect::<HashSet<_>>();
16551 for buffer_id in buffer_ids {
16552 self.fold_buffer(buffer_id, cx);
16553 }
16554 }
16555 }
16556
16557 fn fold_at_level(
16558 &mut self,
16559 fold_at: &FoldAtLevel,
16560 window: &mut Window,
16561 cx: &mut Context<Self>,
16562 ) {
16563 if !self.buffer.read(cx).is_singleton() {
16564 return;
16565 }
16566
16567 let fold_at_level = fold_at.0;
16568 let snapshot = self.buffer.read(cx).snapshot(cx);
16569 let mut to_fold = Vec::new();
16570 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16571
16572 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16573 while start_row < end_row {
16574 match self
16575 .snapshot(window, cx)
16576 .crease_for_buffer_row(MultiBufferRow(start_row))
16577 {
16578 Some(crease) => {
16579 let nested_start_row = crease.range().start.row + 1;
16580 let nested_end_row = crease.range().end.row;
16581
16582 if current_level < fold_at_level {
16583 stack.push((nested_start_row, nested_end_row, current_level + 1));
16584 } else if current_level == fold_at_level {
16585 to_fold.push(crease);
16586 }
16587
16588 start_row = nested_end_row + 1;
16589 }
16590 None => start_row += 1,
16591 }
16592 }
16593 }
16594
16595 self.fold_creases(to_fold, true, window, cx);
16596 }
16597
16598 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16599 if self.buffer.read(cx).is_singleton() {
16600 let mut fold_ranges = Vec::new();
16601 let snapshot = self.buffer.read(cx).snapshot(cx);
16602
16603 for row in 0..snapshot.max_row().0 {
16604 if let Some(foldable_range) = self
16605 .snapshot(window, cx)
16606 .crease_for_buffer_row(MultiBufferRow(row))
16607 {
16608 fold_ranges.push(foldable_range);
16609 }
16610 }
16611
16612 self.fold_creases(fold_ranges, true, window, cx);
16613 } else {
16614 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16615 editor
16616 .update_in(cx, |editor, _, cx| {
16617 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16618 editor.fold_buffer(buffer_id, cx);
16619 }
16620 })
16621 .ok();
16622 });
16623 }
16624 }
16625
16626 pub fn fold_function_bodies(
16627 &mut self,
16628 _: &actions::FoldFunctionBodies,
16629 window: &mut Window,
16630 cx: &mut Context<Self>,
16631 ) {
16632 let snapshot = self.buffer.read(cx).snapshot(cx);
16633
16634 let ranges = snapshot
16635 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16636 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16637 .collect::<Vec<_>>();
16638
16639 let creases = ranges
16640 .into_iter()
16641 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16642 .collect();
16643
16644 self.fold_creases(creases, true, window, cx);
16645 }
16646
16647 pub fn fold_recursive(
16648 &mut self,
16649 _: &actions::FoldRecursive,
16650 window: &mut Window,
16651 cx: &mut Context<Self>,
16652 ) {
16653 let mut to_fold = Vec::new();
16654 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16655 let selections = self.selections.all_adjusted(cx);
16656
16657 for selection in selections {
16658 let range = selection.range().sorted();
16659 let buffer_start_row = range.start.row;
16660
16661 if range.start.row != range.end.row {
16662 let mut found = false;
16663 for row in range.start.row..=range.end.row {
16664 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16665 found = true;
16666 to_fold.push(crease);
16667 }
16668 }
16669 if found {
16670 continue;
16671 }
16672 }
16673
16674 for row in (0..=range.start.row).rev() {
16675 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16676 if crease.range().end.row >= buffer_start_row {
16677 to_fold.push(crease);
16678 } else {
16679 break;
16680 }
16681 }
16682 }
16683 }
16684
16685 self.fold_creases(to_fold, true, window, cx);
16686 }
16687
16688 pub fn fold_at(
16689 &mut self,
16690 buffer_row: MultiBufferRow,
16691 window: &mut Window,
16692 cx: &mut Context<Self>,
16693 ) {
16694 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16695
16696 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16697 let autoscroll = self
16698 .selections
16699 .all::<Point>(cx)
16700 .iter()
16701 .any(|selection| crease.range().overlaps(&selection.range()));
16702
16703 self.fold_creases(vec![crease], autoscroll, window, cx);
16704 }
16705 }
16706
16707 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16708 if self.is_singleton(cx) {
16709 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16710 let buffer = &display_map.buffer_snapshot;
16711 let selections = self.selections.all::<Point>(cx);
16712 let ranges = selections
16713 .iter()
16714 .map(|s| {
16715 let range = s.display_range(&display_map).sorted();
16716 let mut start = range.start.to_point(&display_map);
16717 let mut end = range.end.to_point(&display_map);
16718 start.column = 0;
16719 end.column = buffer.line_len(MultiBufferRow(end.row));
16720 start..end
16721 })
16722 .collect::<Vec<_>>();
16723
16724 self.unfold_ranges(&ranges, true, true, cx);
16725 } else {
16726 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16727 let buffer_ids = self
16728 .selections
16729 .disjoint_anchor_ranges()
16730 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16731 .collect::<HashSet<_>>();
16732 for buffer_id in buffer_ids {
16733 self.unfold_buffer(buffer_id, cx);
16734 }
16735 }
16736 }
16737
16738 pub fn unfold_recursive(
16739 &mut self,
16740 _: &UnfoldRecursive,
16741 _window: &mut Window,
16742 cx: &mut Context<Self>,
16743 ) {
16744 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16745 let selections = self.selections.all::<Point>(cx);
16746 let ranges = selections
16747 .iter()
16748 .map(|s| {
16749 let mut range = s.display_range(&display_map).sorted();
16750 *range.start.column_mut() = 0;
16751 *range.end.column_mut() = display_map.line_len(range.end.row());
16752 let start = range.start.to_point(&display_map);
16753 let end = range.end.to_point(&display_map);
16754 start..end
16755 })
16756 .collect::<Vec<_>>();
16757
16758 self.unfold_ranges(&ranges, true, true, cx);
16759 }
16760
16761 pub fn unfold_at(
16762 &mut self,
16763 buffer_row: MultiBufferRow,
16764 _window: &mut Window,
16765 cx: &mut Context<Self>,
16766 ) {
16767 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16768
16769 let intersection_range = Point::new(buffer_row.0, 0)
16770 ..Point::new(
16771 buffer_row.0,
16772 display_map.buffer_snapshot.line_len(buffer_row),
16773 );
16774
16775 let autoscroll = self
16776 .selections
16777 .all::<Point>(cx)
16778 .iter()
16779 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16780
16781 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16782 }
16783
16784 pub fn unfold_all(
16785 &mut self,
16786 _: &actions::UnfoldAll,
16787 _window: &mut Window,
16788 cx: &mut Context<Self>,
16789 ) {
16790 if self.buffer.read(cx).is_singleton() {
16791 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16792 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16793 } else {
16794 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16795 editor
16796 .update(cx, |editor, cx| {
16797 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16798 editor.unfold_buffer(buffer_id, cx);
16799 }
16800 })
16801 .ok();
16802 });
16803 }
16804 }
16805
16806 pub fn fold_selected_ranges(
16807 &mut self,
16808 _: &FoldSelectedRanges,
16809 window: &mut Window,
16810 cx: &mut Context<Self>,
16811 ) {
16812 let selections = self.selections.all_adjusted(cx);
16813 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16814 let ranges = selections
16815 .into_iter()
16816 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16817 .collect::<Vec<_>>();
16818 self.fold_creases(ranges, true, window, cx);
16819 }
16820
16821 pub fn fold_ranges<T: ToOffset + Clone>(
16822 &mut self,
16823 ranges: Vec<Range<T>>,
16824 auto_scroll: bool,
16825 window: &mut Window,
16826 cx: &mut Context<Self>,
16827 ) {
16828 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16829 let ranges = ranges
16830 .into_iter()
16831 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16832 .collect::<Vec<_>>();
16833 self.fold_creases(ranges, auto_scroll, window, cx);
16834 }
16835
16836 pub fn fold_creases<T: ToOffset + Clone>(
16837 &mut self,
16838 creases: Vec<Crease<T>>,
16839 auto_scroll: bool,
16840 _window: &mut Window,
16841 cx: &mut Context<Self>,
16842 ) {
16843 if creases.is_empty() {
16844 return;
16845 }
16846
16847 let mut buffers_affected = HashSet::default();
16848 let multi_buffer = self.buffer().read(cx);
16849 for crease in &creases {
16850 if let Some((_, buffer, _)) =
16851 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16852 {
16853 buffers_affected.insert(buffer.read(cx).remote_id());
16854 };
16855 }
16856
16857 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16858
16859 if auto_scroll {
16860 self.request_autoscroll(Autoscroll::fit(), cx);
16861 }
16862
16863 cx.notify();
16864
16865 self.scrollbar_marker_state.dirty = true;
16866 self.folds_did_change(cx);
16867 }
16868
16869 /// Removes any folds whose ranges intersect any of the given ranges.
16870 pub fn unfold_ranges<T: ToOffset + Clone>(
16871 &mut self,
16872 ranges: &[Range<T>],
16873 inclusive: bool,
16874 auto_scroll: bool,
16875 cx: &mut Context<Self>,
16876 ) {
16877 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16878 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16879 });
16880 self.folds_did_change(cx);
16881 }
16882
16883 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16884 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16885 return;
16886 }
16887 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16888 self.display_map.update(cx, |display_map, cx| {
16889 display_map.fold_buffers([buffer_id], cx)
16890 });
16891 cx.emit(EditorEvent::BufferFoldToggled {
16892 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16893 folded: true,
16894 });
16895 cx.notify();
16896 }
16897
16898 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16899 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16900 return;
16901 }
16902 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16903 self.display_map.update(cx, |display_map, cx| {
16904 display_map.unfold_buffers([buffer_id], cx);
16905 });
16906 cx.emit(EditorEvent::BufferFoldToggled {
16907 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16908 folded: false,
16909 });
16910 cx.notify();
16911 }
16912
16913 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16914 self.display_map.read(cx).is_buffer_folded(buffer)
16915 }
16916
16917 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16918 self.display_map.read(cx).folded_buffers()
16919 }
16920
16921 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16922 self.display_map.update(cx, |display_map, cx| {
16923 display_map.disable_header_for_buffer(buffer_id, cx);
16924 });
16925 cx.notify();
16926 }
16927
16928 /// Removes any folds with the given ranges.
16929 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16930 &mut self,
16931 ranges: &[Range<T>],
16932 type_id: TypeId,
16933 auto_scroll: bool,
16934 cx: &mut Context<Self>,
16935 ) {
16936 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16937 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16938 });
16939 self.folds_did_change(cx);
16940 }
16941
16942 fn remove_folds_with<T: ToOffset + Clone>(
16943 &mut self,
16944 ranges: &[Range<T>],
16945 auto_scroll: bool,
16946 cx: &mut Context<Self>,
16947 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16948 ) {
16949 if ranges.is_empty() {
16950 return;
16951 }
16952
16953 let mut buffers_affected = HashSet::default();
16954 let multi_buffer = self.buffer().read(cx);
16955 for range in ranges {
16956 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16957 buffers_affected.insert(buffer.read(cx).remote_id());
16958 };
16959 }
16960
16961 self.display_map.update(cx, update);
16962
16963 if auto_scroll {
16964 self.request_autoscroll(Autoscroll::fit(), cx);
16965 }
16966
16967 cx.notify();
16968 self.scrollbar_marker_state.dirty = true;
16969 self.active_indent_guides_state.dirty = true;
16970 }
16971
16972 pub fn update_fold_widths(
16973 &mut self,
16974 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16975 cx: &mut Context<Self>,
16976 ) -> bool {
16977 self.display_map
16978 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16979 }
16980
16981 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16982 self.display_map.read(cx).fold_placeholder.clone()
16983 }
16984
16985 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16986 self.buffer.update(cx, |buffer, cx| {
16987 buffer.set_all_diff_hunks_expanded(cx);
16988 });
16989 }
16990
16991 pub fn expand_all_diff_hunks(
16992 &mut self,
16993 _: &ExpandAllDiffHunks,
16994 _window: &mut Window,
16995 cx: &mut Context<Self>,
16996 ) {
16997 self.buffer.update(cx, |buffer, cx| {
16998 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16999 });
17000 }
17001
17002 pub fn toggle_selected_diff_hunks(
17003 &mut self,
17004 _: &ToggleSelectedDiffHunks,
17005 _window: &mut Window,
17006 cx: &mut Context<Self>,
17007 ) {
17008 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17009 self.toggle_diff_hunks_in_ranges(ranges, cx);
17010 }
17011
17012 pub fn diff_hunks_in_ranges<'a>(
17013 &'a self,
17014 ranges: &'a [Range<Anchor>],
17015 buffer: &'a MultiBufferSnapshot,
17016 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17017 ranges.iter().flat_map(move |range| {
17018 let end_excerpt_id = range.end.excerpt_id;
17019 let range = range.to_point(buffer);
17020 let mut peek_end = range.end;
17021 if range.end.row < buffer.max_row().0 {
17022 peek_end = Point::new(range.end.row + 1, 0);
17023 }
17024 buffer
17025 .diff_hunks_in_range(range.start..peek_end)
17026 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17027 })
17028 }
17029
17030 pub fn has_stageable_diff_hunks_in_ranges(
17031 &self,
17032 ranges: &[Range<Anchor>],
17033 snapshot: &MultiBufferSnapshot,
17034 ) -> bool {
17035 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17036 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17037 }
17038
17039 pub fn toggle_staged_selected_diff_hunks(
17040 &mut self,
17041 _: &::git::ToggleStaged,
17042 _: &mut Window,
17043 cx: &mut Context<Self>,
17044 ) {
17045 let snapshot = self.buffer.read(cx).snapshot(cx);
17046 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17047 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17048 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17049 }
17050
17051 pub fn set_render_diff_hunk_controls(
17052 &mut self,
17053 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17054 cx: &mut Context<Self>,
17055 ) {
17056 self.render_diff_hunk_controls = render_diff_hunk_controls;
17057 cx.notify();
17058 }
17059
17060 pub fn stage_and_next(
17061 &mut self,
17062 _: &::git::StageAndNext,
17063 window: &mut Window,
17064 cx: &mut Context<Self>,
17065 ) {
17066 self.do_stage_or_unstage_and_next(true, window, cx);
17067 }
17068
17069 pub fn unstage_and_next(
17070 &mut self,
17071 _: &::git::UnstageAndNext,
17072 window: &mut Window,
17073 cx: &mut Context<Self>,
17074 ) {
17075 self.do_stage_or_unstage_and_next(false, window, cx);
17076 }
17077
17078 pub fn stage_or_unstage_diff_hunks(
17079 &mut self,
17080 stage: bool,
17081 ranges: Vec<Range<Anchor>>,
17082 cx: &mut Context<Self>,
17083 ) {
17084 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17085 cx.spawn(async move |this, cx| {
17086 task.await?;
17087 this.update(cx, |this, cx| {
17088 let snapshot = this.buffer.read(cx).snapshot(cx);
17089 let chunk_by = this
17090 .diff_hunks_in_ranges(&ranges, &snapshot)
17091 .chunk_by(|hunk| hunk.buffer_id);
17092 for (buffer_id, hunks) in &chunk_by {
17093 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17094 }
17095 })
17096 })
17097 .detach_and_log_err(cx);
17098 }
17099
17100 fn save_buffers_for_ranges_if_needed(
17101 &mut self,
17102 ranges: &[Range<Anchor>],
17103 cx: &mut Context<Editor>,
17104 ) -> Task<Result<()>> {
17105 let multibuffer = self.buffer.read(cx);
17106 let snapshot = multibuffer.read(cx);
17107 let buffer_ids: HashSet<_> = ranges
17108 .iter()
17109 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17110 .collect();
17111 drop(snapshot);
17112
17113 let mut buffers = HashSet::default();
17114 for buffer_id in buffer_ids {
17115 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17116 let buffer = buffer_entity.read(cx);
17117 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17118 {
17119 buffers.insert(buffer_entity);
17120 }
17121 }
17122 }
17123
17124 if let Some(project) = &self.project {
17125 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17126 } else {
17127 Task::ready(Ok(()))
17128 }
17129 }
17130
17131 fn do_stage_or_unstage_and_next(
17132 &mut self,
17133 stage: bool,
17134 window: &mut Window,
17135 cx: &mut Context<Self>,
17136 ) {
17137 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17138
17139 if ranges.iter().any(|range| range.start != range.end) {
17140 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17141 return;
17142 }
17143
17144 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17145 let snapshot = self.snapshot(window, cx);
17146 let position = self.selections.newest::<Point>(cx).head();
17147 let mut row = snapshot
17148 .buffer_snapshot
17149 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17150 .find(|hunk| hunk.row_range.start.0 > position.row)
17151 .map(|hunk| hunk.row_range.start);
17152
17153 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17154 // Outside of the project diff editor, wrap around to the beginning.
17155 if !all_diff_hunks_expanded {
17156 row = row.or_else(|| {
17157 snapshot
17158 .buffer_snapshot
17159 .diff_hunks_in_range(Point::zero()..position)
17160 .find(|hunk| hunk.row_range.end.0 < position.row)
17161 .map(|hunk| hunk.row_range.start)
17162 });
17163 }
17164
17165 if let Some(row) = row {
17166 let destination = Point::new(row.0, 0);
17167 let autoscroll = Autoscroll::center();
17168
17169 self.unfold_ranges(&[destination..destination], false, false, cx);
17170 self.change_selections(Some(autoscroll), window, cx, |s| {
17171 s.select_ranges([destination..destination]);
17172 });
17173 }
17174 }
17175
17176 fn do_stage_or_unstage(
17177 &self,
17178 stage: bool,
17179 buffer_id: BufferId,
17180 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17181 cx: &mut App,
17182 ) -> Option<()> {
17183 let project = self.project.as_ref()?;
17184 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17185 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17186 let buffer_snapshot = buffer.read(cx).snapshot();
17187 let file_exists = buffer_snapshot
17188 .file()
17189 .is_some_and(|file| file.disk_state().exists());
17190 diff.update(cx, |diff, cx| {
17191 diff.stage_or_unstage_hunks(
17192 stage,
17193 &hunks
17194 .map(|hunk| buffer_diff::DiffHunk {
17195 buffer_range: hunk.buffer_range,
17196 diff_base_byte_range: hunk.diff_base_byte_range,
17197 secondary_status: hunk.secondary_status,
17198 range: Point::zero()..Point::zero(), // unused
17199 })
17200 .collect::<Vec<_>>(),
17201 &buffer_snapshot,
17202 file_exists,
17203 cx,
17204 )
17205 });
17206 None
17207 }
17208
17209 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17210 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17211 self.buffer
17212 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17213 }
17214
17215 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17216 self.buffer.update(cx, |buffer, cx| {
17217 let ranges = vec![Anchor::min()..Anchor::max()];
17218 if !buffer.all_diff_hunks_expanded()
17219 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17220 {
17221 buffer.collapse_diff_hunks(ranges, cx);
17222 true
17223 } else {
17224 false
17225 }
17226 })
17227 }
17228
17229 fn toggle_diff_hunks_in_ranges(
17230 &mut self,
17231 ranges: Vec<Range<Anchor>>,
17232 cx: &mut Context<Editor>,
17233 ) {
17234 self.buffer.update(cx, |buffer, cx| {
17235 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17236 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17237 })
17238 }
17239
17240 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17241 self.buffer.update(cx, |buffer, cx| {
17242 let snapshot = buffer.snapshot(cx);
17243 let excerpt_id = range.end.excerpt_id;
17244 let point_range = range.to_point(&snapshot);
17245 let expand = !buffer.single_hunk_is_expanded(range, cx);
17246 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17247 })
17248 }
17249
17250 pub(crate) fn apply_all_diff_hunks(
17251 &mut self,
17252 _: &ApplyAllDiffHunks,
17253 window: &mut Window,
17254 cx: &mut Context<Self>,
17255 ) {
17256 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17257
17258 let buffers = self.buffer.read(cx).all_buffers();
17259 for branch_buffer in buffers {
17260 branch_buffer.update(cx, |branch_buffer, cx| {
17261 branch_buffer.merge_into_base(Vec::new(), cx);
17262 });
17263 }
17264
17265 if let Some(project) = self.project.clone() {
17266 self.save(
17267 SaveOptions {
17268 format: true,
17269 autosave: false,
17270 },
17271 project,
17272 window,
17273 cx,
17274 )
17275 .detach_and_log_err(cx);
17276 }
17277 }
17278
17279 pub(crate) fn apply_selected_diff_hunks(
17280 &mut self,
17281 _: &ApplyDiffHunk,
17282 window: &mut Window,
17283 cx: &mut Context<Self>,
17284 ) {
17285 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17286 let snapshot = self.snapshot(window, cx);
17287 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17288 let mut ranges_by_buffer = HashMap::default();
17289 self.transact(window, cx, |editor, _window, cx| {
17290 for hunk in hunks {
17291 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17292 ranges_by_buffer
17293 .entry(buffer.clone())
17294 .or_insert_with(Vec::new)
17295 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17296 }
17297 }
17298
17299 for (buffer, ranges) in ranges_by_buffer {
17300 buffer.update(cx, |buffer, cx| {
17301 buffer.merge_into_base(ranges, cx);
17302 });
17303 }
17304 });
17305
17306 if let Some(project) = self.project.clone() {
17307 self.save(
17308 SaveOptions {
17309 format: true,
17310 autosave: false,
17311 },
17312 project,
17313 window,
17314 cx,
17315 )
17316 .detach_and_log_err(cx);
17317 }
17318 }
17319
17320 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17321 if hovered != self.gutter_hovered {
17322 self.gutter_hovered = hovered;
17323 cx.notify();
17324 }
17325 }
17326
17327 pub fn insert_blocks(
17328 &mut self,
17329 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17330 autoscroll: Option<Autoscroll>,
17331 cx: &mut Context<Self>,
17332 ) -> Vec<CustomBlockId> {
17333 let blocks = self
17334 .display_map
17335 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17336 if let Some(autoscroll) = autoscroll {
17337 self.request_autoscroll(autoscroll, cx);
17338 }
17339 cx.notify();
17340 blocks
17341 }
17342
17343 pub fn resize_blocks(
17344 &mut self,
17345 heights: HashMap<CustomBlockId, u32>,
17346 autoscroll: Option<Autoscroll>,
17347 cx: &mut Context<Self>,
17348 ) {
17349 self.display_map
17350 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17351 if let Some(autoscroll) = autoscroll {
17352 self.request_autoscroll(autoscroll, cx);
17353 }
17354 cx.notify();
17355 }
17356
17357 pub fn replace_blocks(
17358 &mut self,
17359 renderers: HashMap<CustomBlockId, RenderBlock>,
17360 autoscroll: Option<Autoscroll>,
17361 cx: &mut Context<Self>,
17362 ) {
17363 self.display_map
17364 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17365 if let Some(autoscroll) = autoscroll {
17366 self.request_autoscroll(autoscroll, cx);
17367 }
17368 cx.notify();
17369 }
17370
17371 pub fn remove_blocks(
17372 &mut self,
17373 block_ids: HashSet<CustomBlockId>,
17374 autoscroll: Option<Autoscroll>,
17375 cx: &mut Context<Self>,
17376 ) {
17377 self.display_map.update(cx, |display_map, cx| {
17378 display_map.remove_blocks(block_ids, cx)
17379 });
17380 if let Some(autoscroll) = autoscroll {
17381 self.request_autoscroll(autoscroll, cx);
17382 }
17383 cx.notify();
17384 }
17385
17386 pub fn row_for_block(
17387 &self,
17388 block_id: CustomBlockId,
17389 cx: &mut Context<Self>,
17390 ) -> Option<DisplayRow> {
17391 self.display_map
17392 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17393 }
17394
17395 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17396 self.focused_block = Some(focused_block);
17397 }
17398
17399 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17400 self.focused_block.take()
17401 }
17402
17403 pub fn insert_creases(
17404 &mut self,
17405 creases: impl IntoIterator<Item = Crease<Anchor>>,
17406 cx: &mut Context<Self>,
17407 ) -> Vec<CreaseId> {
17408 self.display_map
17409 .update(cx, |map, cx| map.insert_creases(creases, cx))
17410 }
17411
17412 pub fn remove_creases(
17413 &mut self,
17414 ids: impl IntoIterator<Item = CreaseId>,
17415 cx: &mut Context<Self>,
17416 ) -> Vec<(CreaseId, Range<Anchor>)> {
17417 self.display_map
17418 .update(cx, |map, cx| map.remove_creases(ids, cx))
17419 }
17420
17421 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17422 self.display_map
17423 .update(cx, |map, cx| map.snapshot(cx))
17424 .longest_row()
17425 }
17426
17427 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17428 self.display_map
17429 .update(cx, |map, cx| map.snapshot(cx))
17430 .max_point()
17431 }
17432
17433 pub fn text(&self, cx: &App) -> String {
17434 self.buffer.read(cx).read(cx).text()
17435 }
17436
17437 pub fn is_empty(&self, cx: &App) -> bool {
17438 self.buffer.read(cx).read(cx).is_empty()
17439 }
17440
17441 pub fn text_option(&self, cx: &App) -> Option<String> {
17442 let text = self.text(cx);
17443 let text = text.trim();
17444
17445 if text.is_empty() {
17446 return None;
17447 }
17448
17449 Some(text.to_string())
17450 }
17451
17452 pub fn set_text(
17453 &mut self,
17454 text: impl Into<Arc<str>>,
17455 window: &mut Window,
17456 cx: &mut Context<Self>,
17457 ) {
17458 self.transact(window, cx, |this, _, cx| {
17459 this.buffer
17460 .read(cx)
17461 .as_singleton()
17462 .expect("you can only call set_text on editors for singleton buffers")
17463 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17464 });
17465 }
17466
17467 pub fn display_text(&self, cx: &mut App) -> String {
17468 self.display_map
17469 .update(cx, |map, cx| map.snapshot(cx))
17470 .text()
17471 }
17472
17473 fn create_minimap(
17474 &self,
17475 minimap_settings: MinimapSettings,
17476 window: &mut Window,
17477 cx: &mut Context<Self>,
17478 ) -> Option<Entity<Self>> {
17479 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17480 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17481 }
17482
17483 fn initialize_new_minimap(
17484 &self,
17485 minimap_settings: MinimapSettings,
17486 window: &mut Window,
17487 cx: &mut Context<Self>,
17488 ) -> Entity<Self> {
17489 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17490
17491 let mut minimap = Editor::new_internal(
17492 EditorMode::Minimap {
17493 parent: cx.weak_entity(),
17494 },
17495 self.buffer.clone(),
17496 self.project.clone(),
17497 Some(self.display_map.clone()),
17498 window,
17499 cx,
17500 );
17501 minimap.scroll_manager.clone_state(&self.scroll_manager);
17502 minimap.set_text_style_refinement(TextStyleRefinement {
17503 font_size: Some(MINIMAP_FONT_SIZE),
17504 font_weight: Some(MINIMAP_FONT_WEIGHT),
17505 ..Default::default()
17506 });
17507 minimap.update_minimap_configuration(minimap_settings, cx);
17508 cx.new(|_| minimap)
17509 }
17510
17511 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17512 let current_line_highlight = minimap_settings
17513 .current_line_highlight
17514 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17515 self.set_current_line_highlight(Some(current_line_highlight));
17516 }
17517
17518 pub fn minimap(&self) -> Option<&Entity<Self>> {
17519 self.minimap
17520 .as_ref()
17521 .filter(|_| self.minimap_visibility.visible())
17522 }
17523
17524 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17525 let mut wrap_guides = smallvec![];
17526
17527 if self.show_wrap_guides == Some(false) {
17528 return wrap_guides;
17529 }
17530
17531 let settings = self.buffer.read(cx).language_settings(cx);
17532 if settings.show_wrap_guides {
17533 match self.soft_wrap_mode(cx) {
17534 SoftWrap::Column(soft_wrap) => {
17535 wrap_guides.push((soft_wrap as usize, true));
17536 }
17537 SoftWrap::Bounded(soft_wrap) => {
17538 wrap_guides.push((soft_wrap as usize, true));
17539 }
17540 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17541 }
17542 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17543 }
17544
17545 wrap_guides
17546 }
17547
17548 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17549 let settings = self.buffer.read(cx).language_settings(cx);
17550 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17551 match mode {
17552 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17553 SoftWrap::None
17554 }
17555 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17556 language_settings::SoftWrap::PreferredLineLength => {
17557 SoftWrap::Column(settings.preferred_line_length)
17558 }
17559 language_settings::SoftWrap::Bounded => {
17560 SoftWrap::Bounded(settings.preferred_line_length)
17561 }
17562 }
17563 }
17564
17565 pub fn set_soft_wrap_mode(
17566 &mut self,
17567 mode: language_settings::SoftWrap,
17568
17569 cx: &mut Context<Self>,
17570 ) {
17571 self.soft_wrap_mode_override = Some(mode);
17572 cx.notify();
17573 }
17574
17575 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17576 self.hard_wrap = hard_wrap;
17577 cx.notify();
17578 }
17579
17580 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17581 self.text_style_refinement = Some(style);
17582 }
17583
17584 /// called by the Element so we know what style we were most recently rendered with.
17585 pub(crate) fn set_style(
17586 &mut self,
17587 style: EditorStyle,
17588 window: &mut Window,
17589 cx: &mut Context<Self>,
17590 ) {
17591 // We intentionally do not inform the display map about the minimap style
17592 // so that wrapping is not recalculated and stays consistent for the editor
17593 // and its linked minimap.
17594 if !self.mode.is_minimap() {
17595 let rem_size = window.rem_size();
17596 self.display_map.update(cx, |map, cx| {
17597 map.set_font(
17598 style.text.font(),
17599 style.text.font_size.to_pixels(rem_size),
17600 cx,
17601 )
17602 });
17603 }
17604 self.style = Some(style);
17605 }
17606
17607 pub fn style(&self) -> Option<&EditorStyle> {
17608 self.style.as_ref()
17609 }
17610
17611 // Called by the element. This method is not designed to be called outside of the editor
17612 // element's layout code because it does not notify when rewrapping is computed synchronously.
17613 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17614 self.display_map
17615 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17616 }
17617
17618 pub fn set_soft_wrap(&mut self) {
17619 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17620 }
17621
17622 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17623 if self.soft_wrap_mode_override.is_some() {
17624 self.soft_wrap_mode_override.take();
17625 } else {
17626 let soft_wrap = match self.soft_wrap_mode(cx) {
17627 SoftWrap::GitDiff => return,
17628 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17629 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17630 language_settings::SoftWrap::None
17631 }
17632 };
17633 self.soft_wrap_mode_override = Some(soft_wrap);
17634 }
17635 cx.notify();
17636 }
17637
17638 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17639 let Some(workspace) = self.workspace() else {
17640 return;
17641 };
17642 let fs = workspace.read(cx).app_state().fs.clone();
17643 let current_show = TabBarSettings::get_global(cx).show;
17644 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17645 setting.show = Some(!current_show);
17646 });
17647 }
17648
17649 pub fn toggle_indent_guides(
17650 &mut self,
17651 _: &ToggleIndentGuides,
17652 _: &mut Window,
17653 cx: &mut Context<Self>,
17654 ) {
17655 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17656 self.buffer
17657 .read(cx)
17658 .language_settings(cx)
17659 .indent_guides
17660 .enabled
17661 });
17662 self.show_indent_guides = Some(!currently_enabled);
17663 cx.notify();
17664 }
17665
17666 fn should_show_indent_guides(&self) -> Option<bool> {
17667 self.show_indent_guides
17668 }
17669
17670 pub fn toggle_line_numbers(
17671 &mut self,
17672 _: &ToggleLineNumbers,
17673 _: &mut Window,
17674 cx: &mut Context<Self>,
17675 ) {
17676 let mut editor_settings = EditorSettings::get_global(cx).clone();
17677 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17678 EditorSettings::override_global(editor_settings, cx);
17679 }
17680
17681 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17682 if let Some(show_line_numbers) = self.show_line_numbers {
17683 return show_line_numbers;
17684 }
17685 EditorSettings::get_global(cx).gutter.line_numbers
17686 }
17687
17688 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17689 self.use_relative_line_numbers
17690 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17691 }
17692
17693 pub fn toggle_relative_line_numbers(
17694 &mut self,
17695 _: &ToggleRelativeLineNumbers,
17696 _: &mut Window,
17697 cx: &mut Context<Self>,
17698 ) {
17699 let is_relative = self.should_use_relative_line_numbers(cx);
17700 self.set_relative_line_number(Some(!is_relative), cx)
17701 }
17702
17703 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17704 self.use_relative_line_numbers = is_relative;
17705 cx.notify();
17706 }
17707
17708 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17709 self.show_gutter = show_gutter;
17710 cx.notify();
17711 }
17712
17713 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17714 self.show_scrollbars = ScrollbarAxes {
17715 horizontal: show,
17716 vertical: show,
17717 };
17718 cx.notify();
17719 }
17720
17721 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17722 self.show_scrollbars.vertical = show;
17723 cx.notify();
17724 }
17725
17726 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17727 self.show_scrollbars.horizontal = show;
17728 cx.notify();
17729 }
17730
17731 pub fn set_minimap_visibility(
17732 &mut self,
17733 minimap_visibility: MinimapVisibility,
17734 window: &mut Window,
17735 cx: &mut Context<Self>,
17736 ) {
17737 if self.minimap_visibility != minimap_visibility {
17738 if minimap_visibility.visible() && self.minimap.is_none() {
17739 let minimap_settings = EditorSettings::get_global(cx).minimap;
17740 self.minimap =
17741 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17742 }
17743 self.minimap_visibility = minimap_visibility;
17744 cx.notify();
17745 }
17746 }
17747
17748 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17749 self.set_show_scrollbars(false, cx);
17750 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17751 }
17752
17753 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17754 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17755 }
17756
17757 /// Normally the text in full mode and auto height editors is padded on the
17758 /// left side by roughly half a character width for improved hit testing.
17759 ///
17760 /// Use this method to disable this for cases where this is not wanted (e.g.
17761 /// if you want to align the editor text with some other text above or below)
17762 /// or if you want to add this padding to single-line editors.
17763 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17764 self.offset_content = offset_content;
17765 cx.notify();
17766 }
17767
17768 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17769 self.show_line_numbers = Some(show_line_numbers);
17770 cx.notify();
17771 }
17772
17773 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17774 self.disable_expand_excerpt_buttons = true;
17775 cx.notify();
17776 }
17777
17778 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17779 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17780 cx.notify();
17781 }
17782
17783 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17784 self.show_code_actions = Some(show_code_actions);
17785 cx.notify();
17786 }
17787
17788 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17789 self.show_runnables = Some(show_runnables);
17790 cx.notify();
17791 }
17792
17793 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17794 self.show_breakpoints = Some(show_breakpoints);
17795 cx.notify();
17796 }
17797
17798 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17799 if self.display_map.read(cx).masked != masked {
17800 self.display_map.update(cx, |map, _| map.masked = masked);
17801 }
17802 cx.notify()
17803 }
17804
17805 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17806 self.show_wrap_guides = Some(show_wrap_guides);
17807 cx.notify();
17808 }
17809
17810 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17811 self.show_indent_guides = Some(show_indent_guides);
17812 cx.notify();
17813 }
17814
17815 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17816 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17817 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17818 if let Some(dir) = file.abs_path(cx).parent() {
17819 return Some(dir.to_owned());
17820 }
17821 }
17822
17823 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17824 return Some(project_path.path.to_path_buf());
17825 }
17826 }
17827
17828 None
17829 }
17830
17831 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17832 self.active_excerpt(cx)?
17833 .1
17834 .read(cx)
17835 .file()
17836 .and_then(|f| f.as_local())
17837 }
17838
17839 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17840 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17841 let buffer = buffer.read(cx);
17842 if let Some(project_path) = buffer.project_path(cx) {
17843 let project = self.project.as_ref()?.read(cx);
17844 project.absolute_path(&project_path, cx)
17845 } else {
17846 buffer
17847 .file()
17848 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17849 }
17850 })
17851 }
17852
17853 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17854 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17855 let project_path = buffer.read(cx).project_path(cx)?;
17856 let project = self.project.as_ref()?.read(cx);
17857 let entry = project.entry_for_path(&project_path, cx)?;
17858 let path = entry.path.to_path_buf();
17859 Some(path)
17860 })
17861 }
17862
17863 pub fn reveal_in_finder(
17864 &mut self,
17865 _: &RevealInFileManager,
17866 _window: &mut Window,
17867 cx: &mut Context<Self>,
17868 ) {
17869 if let Some(target) = self.target_file(cx) {
17870 cx.reveal_path(&target.abs_path(cx));
17871 }
17872 }
17873
17874 pub fn copy_path(
17875 &mut self,
17876 _: &zed_actions::workspace::CopyPath,
17877 _window: &mut Window,
17878 cx: &mut Context<Self>,
17879 ) {
17880 if let Some(path) = self.target_file_abs_path(cx) {
17881 if let Some(path) = path.to_str() {
17882 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17883 }
17884 }
17885 }
17886
17887 pub fn copy_relative_path(
17888 &mut self,
17889 _: &zed_actions::workspace::CopyRelativePath,
17890 _window: &mut Window,
17891 cx: &mut Context<Self>,
17892 ) {
17893 if let Some(path) = self.target_file_path(cx) {
17894 if let Some(path) = path.to_str() {
17895 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17896 }
17897 }
17898 }
17899
17900 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17901 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17902 buffer.read(cx).project_path(cx)
17903 } else {
17904 None
17905 }
17906 }
17907
17908 // Returns true if the editor handled a go-to-line request
17909 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17910 maybe!({
17911 let breakpoint_store = self.breakpoint_store.as_ref()?;
17912
17913 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17914 else {
17915 self.clear_row_highlights::<ActiveDebugLine>();
17916 return None;
17917 };
17918
17919 let position = active_stack_frame.position;
17920 let buffer_id = position.buffer_id?;
17921 let snapshot = self
17922 .project
17923 .as_ref()?
17924 .read(cx)
17925 .buffer_for_id(buffer_id, cx)?
17926 .read(cx)
17927 .snapshot();
17928
17929 let mut handled = false;
17930 for (id, ExcerptRange { context, .. }) in
17931 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17932 {
17933 if context.start.cmp(&position, &snapshot).is_ge()
17934 || context.end.cmp(&position, &snapshot).is_lt()
17935 {
17936 continue;
17937 }
17938 let snapshot = self.buffer.read(cx).snapshot(cx);
17939 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17940
17941 handled = true;
17942 self.clear_row_highlights::<ActiveDebugLine>();
17943
17944 self.go_to_line::<ActiveDebugLine>(
17945 multibuffer_anchor,
17946 Some(cx.theme().colors().editor_debugger_active_line_background),
17947 window,
17948 cx,
17949 );
17950
17951 cx.notify();
17952 }
17953
17954 handled.then_some(())
17955 })
17956 .is_some()
17957 }
17958
17959 pub fn copy_file_name_without_extension(
17960 &mut self,
17961 _: &CopyFileNameWithoutExtension,
17962 _: &mut Window,
17963 cx: &mut Context<Self>,
17964 ) {
17965 if let Some(file) = self.target_file(cx) {
17966 if let Some(file_stem) = file.path().file_stem() {
17967 if let Some(name) = file_stem.to_str() {
17968 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17969 }
17970 }
17971 }
17972 }
17973
17974 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17975 if let Some(file) = self.target_file(cx) {
17976 if let Some(file_name) = file.path().file_name() {
17977 if let Some(name) = file_name.to_str() {
17978 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17979 }
17980 }
17981 }
17982 }
17983
17984 pub fn toggle_git_blame(
17985 &mut self,
17986 _: &::git::Blame,
17987 window: &mut Window,
17988 cx: &mut Context<Self>,
17989 ) {
17990 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17991
17992 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17993 self.start_git_blame(true, window, cx);
17994 }
17995
17996 cx.notify();
17997 }
17998
17999 pub fn toggle_git_blame_inline(
18000 &mut self,
18001 _: &ToggleGitBlameInline,
18002 window: &mut Window,
18003 cx: &mut Context<Self>,
18004 ) {
18005 self.toggle_git_blame_inline_internal(true, window, cx);
18006 cx.notify();
18007 }
18008
18009 pub fn open_git_blame_commit(
18010 &mut self,
18011 _: &OpenGitBlameCommit,
18012 window: &mut Window,
18013 cx: &mut Context<Self>,
18014 ) {
18015 self.open_git_blame_commit_internal(window, cx);
18016 }
18017
18018 fn open_git_blame_commit_internal(
18019 &mut self,
18020 window: &mut Window,
18021 cx: &mut Context<Self>,
18022 ) -> Option<()> {
18023 let blame = self.blame.as_ref()?;
18024 let snapshot = self.snapshot(window, cx);
18025 let cursor = self.selections.newest::<Point>(cx).head();
18026 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18027 let blame_entry = blame
18028 .update(cx, |blame, cx| {
18029 blame
18030 .blame_for_rows(
18031 &[RowInfo {
18032 buffer_id: Some(buffer.remote_id()),
18033 buffer_row: Some(point.row),
18034 ..Default::default()
18035 }],
18036 cx,
18037 )
18038 .next()
18039 })
18040 .flatten()?;
18041 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18042 let repo = blame.read(cx).repository(cx)?;
18043 let workspace = self.workspace()?.downgrade();
18044 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18045 None
18046 }
18047
18048 pub fn git_blame_inline_enabled(&self) -> bool {
18049 self.git_blame_inline_enabled
18050 }
18051
18052 pub fn toggle_selection_menu(
18053 &mut self,
18054 _: &ToggleSelectionMenu,
18055 _: &mut Window,
18056 cx: &mut Context<Self>,
18057 ) {
18058 self.show_selection_menu = self
18059 .show_selection_menu
18060 .map(|show_selections_menu| !show_selections_menu)
18061 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18062
18063 cx.notify();
18064 }
18065
18066 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18067 self.show_selection_menu
18068 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18069 }
18070
18071 fn start_git_blame(
18072 &mut self,
18073 user_triggered: bool,
18074 window: &mut Window,
18075 cx: &mut Context<Self>,
18076 ) {
18077 if let Some(project) = self.project.as_ref() {
18078 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18079 return;
18080 };
18081
18082 if buffer.read(cx).file().is_none() {
18083 return;
18084 }
18085
18086 let focused = self.focus_handle(cx).contains_focused(window, cx);
18087
18088 let project = project.clone();
18089 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18090 self.blame_subscription =
18091 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18092 self.blame = Some(blame);
18093 }
18094 }
18095
18096 fn toggle_git_blame_inline_internal(
18097 &mut self,
18098 user_triggered: bool,
18099 window: &mut Window,
18100 cx: &mut Context<Self>,
18101 ) {
18102 if self.git_blame_inline_enabled {
18103 self.git_blame_inline_enabled = false;
18104 self.show_git_blame_inline = false;
18105 self.show_git_blame_inline_delay_task.take();
18106 } else {
18107 self.git_blame_inline_enabled = true;
18108 self.start_git_blame_inline(user_triggered, window, cx);
18109 }
18110
18111 cx.notify();
18112 }
18113
18114 fn start_git_blame_inline(
18115 &mut self,
18116 user_triggered: bool,
18117 window: &mut Window,
18118 cx: &mut Context<Self>,
18119 ) {
18120 self.start_git_blame(user_triggered, window, cx);
18121
18122 if ProjectSettings::get_global(cx)
18123 .git
18124 .inline_blame_delay()
18125 .is_some()
18126 {
18127 self.start_inline_blame_timer(window, cx);
18128 } else {
18129 self.show_git_blame_inline = true
18130 }
18131 }
18132
18133 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18134 self.blame.as_ref()
18135 }
18136
18137 pub fn show_git_blame_gutter(&self) -> bool {
18138 self.show_git_blame_gutter
18139 }
18140
18141 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18142 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18143 }
18144
18145 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18146 self.show_git_blame_inline
18147 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18148 && !self.newest_selection_head_on_empty_line(cx)
18149 && self.has_blame_entries(cx)
18150 }
18151
18152 fn has_blame_entries(&self, cx: &App) -> bool {
18153 self.blame()
18154 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18155 }
18156
18157 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18158 let cursor_anchor = self.selections.newest_anchor().head();
18159
18160 let snapshot = self.buffer.read(cx).snapshot(cx);
18161 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18162
18163 snapshot.line_len(buffer_row) == 0
18164 }
18165
18166 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18167 let buffer_and_selection = maybe!({
18168 let selection = self.selections.newest::<Point>(cx);
18169 let selection_range = selection.range();
18170
18171 let multi_buffer = self.buffer().read(cx);
18172 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18173 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18174
18175 let (buffer, range, _) = if selection.reversed {
18176 buffer_ranges.first()
18177 } else {
18178 buffer_ranges.last()
18179 }?;
18180
18181 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18182 ..text::ToPoint::to_point(&range.end, &buffer).row;
18183 Some((
18184 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18185 selection,
18186 ))
18187 });
18188
18189 let Some((buffer, selection)) = buffer_and_selection else {
18190 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18191 };
18192
18193 let Some(project) = self.project.as_ref() else {
18194 return Task::ready(Err(anyhow!("editor does not have project")));
18195 };
18196
18197 project.update(cx, |project, cx| {
18198 project.get_permalink_to_line(&buffer, selection, cx)
18199 })
18200 }
18201
18202 pub fn copy_permalink_to_line(
18203 &mut self,
18204 _: &CopyPermalinkToLine,
18205 window: &mut Window,
18206 cx: &mut Context<Self>,
18207 ) {
18208 let permalink_task = self.get_permalink_to_line(cx);
18209 let workspace = self.workspace();
18210
18211 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18212 Ok(permalink) => {
18213 cx.update(|_, cx| {
18214 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18215 })
18216 .ok();
18217 }
18218 Err(err) => {
18219 let message = format!("Failed to copy permalink: {err}");
18220
18221 anyhow::Result::<()>::Err(err).log_err();
18222
18223 if let Some(workspace) = workspace {
18224 workspace
18225 .update_in(cx, |workspace, _, cx| {
18226 struct CopyPermalinkToLine;
18227
18228 workspace.show_toast(
18229 Toast::new(
18230 NotificationId::unique::<CopyPermalinkToLine>(),
18231 message,
18232 ),
18233 cx,
18234 )
18235 })
18236 .ok();
18237 }
18238 }
18239 })
18240 .detach();
18241 }
18242
18243 pub fn copy_file_location(
18244 &mut self,
18245 _: &CopyFileLocation,
18246 _: &mut Window,
18247 cx: &mut Context<Self>,
18248 ) {
18249 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18250 if let Some(file) = self.target_file(cx) {
18251 if let Some(path) = file.path().to_str() {
18252 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18253 }
18254 }
18255 }
18256
18257 pub fn open_permalink_to_line(
18258 &mut self,
18259 _: &OpenPermalinkToLine,
18260 window: &mut Window,
18261 cx: &mut Context<Self>,
18262 ) {
18263 let permalink_task = self.get_permalink_to_line(cx);
18264 let workspace = self.workspace();
18265
18266 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18267 Ok(permalink) => {
18268 cx.update(|_, cx| {
18269 cx.open_url(permalink.as_ref());
18270 })
18271 .ok();
18272 }
18273 Err(err) => {
18274 let message = format!("Failed to open permalink: {err}");
18275
18276 anyhow::Result::<()>::Err(err).log_err();
18277
18278 if let Some(workspace) = workspace {
18279 workspace
18280 .update(cx, |workspace, cx| {
18281 struct OpenPermalinkToLine;
18282
18283 workspace.show_toast(
18284 Toast::new(
18285 NotificationId::unique::<OpenPermalinkToLine>(),
18286 message,
18287 ),
18288 cx,
18289 )
18290 })
18291 .ok();
18292 }
18293 }
18294 })
18295 .detach();
18296 }
18297
18298 pub fn insert_uuid_v4(
18299 &mut self,
18300 _: &InsertUuidV4,
18301 window: &mut Window,
18302 cx: &mut Context<Self>,
18303 ) {
18304 self.insert_uuid(UuidVersion::V4, window, cx);
18305 }
18306
18307 pub fn insert_uuid_v7(
18308 &mut self,
18309 _: &InsertUuidV7,
18310 window: &mut Window,
18311 cx: &mut Context<Self>,
18312 ) {
18313 self.insert_uuid(UuidVersion::V7, window, cx);
18314 }
18315
18316 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18317 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18318 self.transact(window, cx, |this, window, cx| {
18319 let edits = this
18320 .selections
18321 .all::<Point>(cx)
18322 .into_iter()
18323 .map(|selection| {
18324 let uuid = match version {
18325 UuidVersion::V4 => uuid::Uuid::new_v4(),
18326 UuidVersion::V7 => uuid::Uuid::now_v7(),
18327 };
18328
18329 (selection.range(), uuid.to_string())
18330 });
18331 this.edit(edits, cx);
18332 this.refresh_inline_completion(true, false, window, cx);
18333 });
18334 }
18335
18336 pub fn open_selections_in_multibuffer(
18337 &mut self,
18338 _: &OpenSelectionsInMultibuffer,
18339 window: &mut Window,
18340 cx: &mut Context<Self>,
18341 ) {
18342 let multibuffer = self.buffer.read(cx);
18343
18344 let Some(buffer) = multibuffer.as_singleton() else {
18345 return;
18346 };
18347
18348 let Some(workspace) = self.workspace() else {
18349 return;
18350 };
18351
18352 let title = multibuffer.title(cx).to_string();
18353
18354 let locations = self
18355 .selections
18356 .all_anchors(cx)
18357 .into_iter()
18358 .map(|selection| Location {
18359 buffer: buffer.clone(),
18360 range: selection.start.text_anchor..selection.end.text_anchor,
18361 })
18362 .collect::<Vec<_>>();
18363
18364 cx.spawn_in(window, async move |_, cx| {
18365 workspace.update_in(cx, |workspace, window, cx| {
18366 Self::open_locations_in_multibuffer(
18367 workspace,
18368 locations,
18369 format!("Selections for '{title}'"),
18370 false,
18371 MultibufferSelectionMode::All,
18372 window,
18373 cx,
18374 );
18375 })
18376 })
18377 .detach();
18378 }
18379
18380 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18381 /// last highlight added will be used.
18382 ///
18383 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18384 pub fn highlight_rows<T: 'static>(
18385 &mut self,
18386 range: Range<Anchor>,
18387 color: Hsla,
18388 options: RowHighlightOptions,
18389 cx: &mut Context<Self>,
18390 ) {
18391 let snapshot = self.buffer().read(cx).snapshot(cx);
18392 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18393 let ix = row_highlights.binary_search_by(|highlight| {
18394 Ordering::Equal
18395 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18396 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18397 });
18398
18399 if let Err(mut ix) = ix {
18400 let index = post_inc(&mut self.highlight_order);
18401
18402 // If this range intersects with the preceding highlight, then merge it with
18403 // the preceding highlight. Otherwise insert a new highlight.
18404 let mut merged = false;
18405 if ix > 0 {
18406 let prev_highlight = &mut row_highlights[ix - 1];
18407 if prev_highlight
18408 .range
18409 .end
18410 .cmp(&range.start, &snapshot)
18411 .is_ge()
18412 {
18413 ix -= 1;
18414 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18415 prev_highlight.range.end = range.end;
18416 }
18417 merged = true;
18418 prev_highlight.index = index;
18419 prev_highlight.color = color;
18420 prev_highlight.options = options;
18421 }
18422 }
18423
18424 if !merged {
18425 row_highlights.insert(
18426 ix,
18427 RowHighlight {
18428 range: range.clone(),
18429 index,
18430 color,
18431 options,
18432 type_id: TypeId::of::<T>(),
18433 },
18434 );
18435 }
18436
18437 // If any of the following highlights intersect with this one, merge them.
18438 while let Some(next_highlight) = row_highlights.get(ix + 1) {
18439 let highlight = &row_highlights[ix];
18440 if next_highlight
18441 .range
18442 .start
18443 .cmp(&highlight.range.end, &snapshot)
18444 .is_le()
18445 {
18446 if next_highlight
18447 .range
18448 .end
18449 .cmp(&highlight.range.end, &snapshot)
18450 .is_gt()
18451 {
18452 row_highlights[ix].range.end = next_highlight.range.end;
18453 }
18454 row_highlights.remove(ix + 1);
18455 } else {
18456 break;
18457 }
18458 }
18459 }
18460 }
18461
18462 /// Remove any highlighted row ranges of the given type that intersect the
18463 /// given ranges.
18464 pub fn remove_highlighted_rows<T: 'static>(
18465 &mut self,
18466 ranges_to_remove: Vec<Range<Anchor>>,
18467 cx: &mut Context<Self>,
18468 ) {
18469 let snapshot = self.buffer().read(cx).snapshot(cx);
18470 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18471 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18472 row_highlights.retain(|highlight| {
18473 while let Some(range_to_remove) = ranges_to_remove.peek() {
18474 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18475 Ordering::Less | Ordering::Equal => {
18476 ranges_to_remove.next();
18477 }
18478 Ordering::Greater => {
18479 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18480 Ordering::Less | Ordering::Equal => {
18481 return false;
18482 }
18483 Ordering::Greater => break,
18484 }
18485 }
18486 }
18487 }
18488
18489 true
18490 })
18491 }
18492
18493 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18494 pub fn clear_row_highlights<T: 'static>(&mut self) {
18495 self.highlighted_rows.remove(&TypeId::of::<T>());
18496 }
18497
18498 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18499 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18500 self.highlighted_rows
18501 .get(&TypeId::of::<T>())
18502 .map_or(&[] as &[_], |vec| vec.as_slice())
18503 .iter()
18504 .map(|highlight| (highlight.range.clone(), highlight.color))
18505 }
18506
18507 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18508 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18509 /// Allows to ignore certain kinds of highlights.
18510 pub fn highlighted_display_rows(
18511 &self,
18512 window: &mut Window,
18513 cx: &mut App,
18514 ) -> BTreeMap<DisplayRow, LineHighlight> {
18515 let snapshot = self.snapshot(window, cx);
18516 let mut used_highlight_orders = HashMap::default();
18517 self.highlighted_rows
18518 .iter()
18519 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18520 .fold(
18521 BTreeMap::<DisplayRow, LineHighlight>::new(),
18522 |mut unique_rows, highlight| {
18523 let start = highlight.range.start.to_display_point(&snapshot);
18524 let end = highlight.range.end.to_display_point(&snapshot);
18525 let start_row = start.row().0;
18526 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18527 && end.column() == 0
18528 {
18529 end.row().0.saturating_sub(1)
18530 } else {
18531 end.row().0
18532 };
18533 for row in start_row..=end_row {
18534 let used_index =
18535 used_highlight_orders.entry(row).or_insert(highlight.index);
18536 if highlight.index >= *used_index {
18537 *used_index = highlight.index;
18538 unique_rows.insert(
18539 DisplayRow(row),
18540 LineHighlight {
18541 include_gutter: highlight.options.include_gutter,
18542 border: None,
18543 background: highlight.color.into(),
18544 type_id: Some(highlight.type_id),
18545 },
18546 );
18547 }
18548 }
18549 unique_rows
18550 },
18551 )
18552 }
18553
18554 pub fn highlighted_display_row_for_autoscroll(
18555 &self,
18556 snapshot: &DisplaySnapshot,
18557 ) -> Option<DisplayRow> {
18558 self.highlighted_rows
18559 .values()
18560 .flat_map(|highlighted_rows| highlighted_rows.iter())
18561 .filter_map(|highlight| {
18562 if highlight.options.autoscroll {
18563 Some(highlight.range.start.to_display_point(snapshot).row())
18564 } else {
18565 None
18566 }
18567 })
18568 .min()
18569 }
18570
18571 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18572 self.highlight_background::<SearchWithinRange>(
18573 ranges,
18574 |theme| theme.colors().editor_document_highlight_read_background,
18575 cx,
18576 )
18577 }
18578
18579 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18580 self.breadcrumb_header = Some(new_header);
18581 }
18582
18583 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18584 self.clear_background_highlights::<SearchWithinRange>(cx);
18585 }
18586
18587 pub fn highlight_background<T: 'static>(
18588 &mut self,
18589 ranges: &[Range<Anchor>],
18590 color_fetcher: fn(&Theme) -> Hsla,
18591 cx: &mut Context<Self>,
18592 ) {
18593 let highlights = ranges
18594 .iter()
18595 .map(|range| BackgroundHighlight {
18596 range: range.clone(),
18597 color_fetcher,
18598 })
18599 .collect();
18600 self.background_highlights
18601 .insert(TypeId::of::<T>(), highlights);
18602 self.scrollbar_marker_state.dirty = true;
18603 cx.notify();
18604 }
18605
18606 pub fn highlight_background_ranges<T: 'static>(
18607 &mut self,
18608 background_highlights: Vec<BackgroundHighlight>,
18609 cx: &mut Context<'_, Editor>,
18610 ) {
18611 self.background_highlights
18612 .insert(TypeId::of::<T>(), background_highlights);
18613 self.scrollbar_marker_state.dirty = true;
18614 cx.notify();
18615 }
18616
18617 pub fn clear_background_highlights<T: 'static>(
18618 &mut self,
18619 cx: &mut Context<Self>,
18620 ) -> Option<Vec<BackgroundHighlight>> {
18621 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
18622 if !text_highlights.is_empty() {
18623 self.scrollbar_marker_state.dirty = true;
18624 cx.notify();
18625 }
18626 Some(text_highlights)
18627 }
18628
18629 pub fn highlight_gutter<T: 'static>(
18630 &mut self,
18631 ranges: impl Into<Vec<Range<Anchor>>>,
18632 color_fetcher: fn(&App) -> Hsla,
18633 cx: &mut Context<Self>,
18634 ) {
18635 self.gutter_highlights
18636 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
18637 cx.notify();
18638 }
18639
18640 pub fn clear_gutter_highlights<T: 'static>(
18641 &mut self,
18642 cx: &mut Context<Self>,
18643 ) -> Option<GutterHighlight> {
18644 cx.notify();
18645 self.gutter_highlights.remove(&TypeId::of::<T>())
18646 }
18647
18648 pub fn insert_gutter_highlight<T: 'static>(
18649 &mut self,
18650 range: Range<Anchor>,
18651 color_fetcher: fn(&App) -> Hsla,
18652 cx: &mut Context<Self>,
18653 ) {
18654 let snapshot = self.buffer().read(cx).snapshot(cx);
18655 let mut highlights = self
18656 .gutter_highlights
18657 .remove(&TypeId::of::<T>())
18658 .map(|(_, highlights)| highlights)
18659 .unwrap_or_default();
18660 let ix = highlights.binary_search_by(|highlight| {
18661 Ordering::Equal
18662 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
18663 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
18664 });
18665 if let Err(ix) = ix {
18666 highlights.insert(ix, range);
18667 }
18668 self.gutter_highlights
18669 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
18670 }
18671
18672 pub fn remove_gutter_highlights<T: 'static>(
18673 &mut self,
18674 ranges_to_remove: Vec<Range<Anchor>>,
18675 cx: &mut Context<Self>,
18676 ) {
18677 let snapshot = self.buffer().read(cx).snapshot(cx);
18678 let Some((color_fetcher, mut gutter_highlights)) =
18679 self.gutter_highlights.remove(&TypeId::of::<T>())
18680 else {
18681 return;
18682 };
18683 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18684 gutter_highlights.retain(|highlight| {
18685 while let Some(range_to_remove) = ranges_to_remove.peek() {
18686 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
18687 Ordering::Less | Ordering::Equal => {
18688 ranges_to_remove.next();
18689 }
18690 Ordering::Greater => {
18691 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
18692 Ordering::Less | Ordering::Equal => {
18693 return false;
18694 }
18695 Ordering::Greater => break,
18696 }
18697 }
18698 }
18699 }
18700
18701 true
18702 });
18703 self.gutter_highlights
18704 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
18705 }
18706
18707 #[cfg(feature = "test-support")]
18708 pub fn all_text_background_highlights(
18709 &self,
18710 window: &mut Window,
18711 cx: &mut Context<Self>,
18712 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18713 let snapshot = self.snapshot(window, cx);
18714 let buffer = &snapshot.buffer_snapshot;
18715 let start = buffer.anchor_before(0);
18716 let end = buffer.anchor_after(buffer.len());
18717 let theme = cx.theme();
18718 self.background_highlights_in_range(start..end, &snapshot, theme)
18719 }
18720
18721 #[cfg(feature = "test-support")]
18722 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18723 let snapshot = self.buffer().read(cx).snapshot(cx);
18724
18725 let highlights = self
18726 .background_highlights
18727 .get(&TypeId::of::<items::BufferSearchHighlights>());
18728
18729 if let Some(highlights) = highlights {
18730 highlights
18731 .iter()
18732 .map(|highlight| {
18733 highlight.range.start.to_point(&snapshot)
18734 ..highlight.range.end.to_point(&snapshot)
18735 })
18736 .collect_vec()
18737 } else {
18738 vec![]
18739 }
18740 }
18741
18742 fn document_highlights_for_position<'a>(
18743 &'a self,
18744 position: Anchor,
18745 buffer: &'a MultiBufferSnapshot,
18746 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
18747 let read_highlights = self
18748 .background_highlights
18749 .get(&TypeId::of::<DocumentHighlightRead>());
18750 let write_highlights = self
18751 .background_highlights
18752 .get(&TypeId::of::<DocumentHighlightWrite>());
18753 let left_position = position.bias_left(buffer);
18754 let right_position = position.bias_right(buffer);
18755 read_highlights
18756 .into_iter()
18757 .chain(write_highlights)
18758 .flat_map(move |highlights| {
18759 let start_ix = match highlights.binary_search_by(|probe| {
18760 let cmp = probe.range.end.cmp(&left_position, buffer);
18761 if cmp.is_ge() {
18762 Ordering::Greater
18763 } else {
18764 Ordering::Less
18765 }
18766 }) {
18767 Ok(i) | Err(i) => i,
18768 };
18769
18770 highlights[start_ix..]
18771 .iter()
18772 .take_while(move |highlight| {
18773 highlight.range.start.cmp(&right_position, buffer).is_le()
18774 })
18775 .map(|highlight| &highlight.range)
18776 })
18777 }
18778
18779 pub fn has_background_highlights<T: 'static>(&self) -> bool {
18780 self.background_highlights
18781 .get(&TypeId::of::<T>())
18782 .map_or(false, |highlights| !highlights.is_empty())
18783 }
18784
18785 pub fn background_highlights_in_range(
18786 &self,
18787 search_range: Range<Anchor>,
18788 display_snapshot: &DisplaySnapshot,
18789 theme: &Theme,
18790 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18791 let mut results = Vec::new();
18792 for highlights in self.background_highlights.values() {
18793 let start_ix = match highlights.binary_search_by(|probe| {
18794 let cmp = probe
18795 .range
18796 .end
18797 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18798 if cmp.is_gt() {
18799 Ordering::Greater
18800 } else {
18801 Ordering::Less
18802 }
18803 }) {
18804 Ok(i) | Err(i) => i,
18805 };
18806 for highlight in &highlights[start_ix..] {
18807 if highlight
18808 .range
18809 .start
18810 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18811 .is_ge()
18812 {
18813 break;
18814 }
18815
18816 let start = highlight.range.start.to_display_point(display_snapshot);
18817 let end = highlight.range.end.to_display_point(display_snapshot);
18818 let color = (highlight.color_fetcher)(theme);
18819 results.push((start..end, color))
18820 }
18821 }
18822 results
18823 }
18824
18825 pub fn background_highlight_row_ranges<T: 'static>(
18826 &self,
18827 search_range: Range<Anchor>,
18828 display_snapshot: &DisplaySnapshot,
18829 count: usize,
18830 ) -> Vec<RangeInclusive<DisplayPoint>> {
18831 let mut results = Vec::new();
18832 let Some(highlights) = self.background_highlights.get(&TypeId::of::<T>()) else {
18833 return vec![];
18834 };
18835
18836 let start_ix = match highlights.binary_search_by(|probe| {
18837 let cmp = probe
18838 .range
18839 .end
18840 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18841 if cmp.is_gt() {
18842 Ordering::Greater
18843 } else {
18844 Ordering::Less
18845 }
18846 }) {
18847 Ok(i) | Err(i) => i,
18848 };
18849 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18850 if let (Some(start_display), Some(end_display)) = (start, end) {
18851 results.push(
18852 start_display.to_display_point(display_snapshot)
18853 ..=end_display.to_display_point(display_snapshot),
18854 );
18855 }
18856 };
18857 let mut start_row: Option<Point> = None;
18858 let mut end_row: Option<Point> = None;
18859 if highlights.len() > count {
18860 return Vec::new();
18861 }
18862 for highlight in &highlights[start_ix..] {
18863 if highlight
18864 .range
18865 .start
18866 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18867 .is_ge()
18868 {
18869 break;
18870 }
18871 let end = highlight
18872 .range
18873 .end
18874 .to_point(&display_snapshot.buffer_snapshot);
18875 if let Some(current_row) = &end_row {
18876 if end.row == current_row.row {
18877 continue;
18878 }
18879 }
18880 let start = highlight
18881 .range
18882 .start
18883 .to_point(&display_snapshot.buffer_snapshot);
18884 if start_row.is_none() {
18885 assert_eq!(end_row, None);
18886 start_row = Some(start);
18887 end_row = Some(end);
18888 continue;
18889 }
18890 if let Some(current_end) = end_row.as_mut() {
18891 if start.row > current_end.row + 1 {
18892 push_region(start_row, end_row);
18893 start_row = Some(start);
18894 end_row = Some(end);
18895 } else {
18896 // Merge two hunks.
18897 *current_end = end;
18898 }
18899 } else {
18900 unreachable!();
18901 }
18902 }
18903 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18904 push_region(start_row, end_row);
18905 results
18906 }
18907
18908 pub fn gutter_highlights_in_range(
18909 &self,
18910 search_range: Range<Anchor>,
18911 display_snapshot: &DisplaySnapshot,
18912 cx: &App,
18913 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18914 let mut results = Vec::new();
18915 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18916 let color = color_fetcher(cx);
18917 let start_ix = match ranges.binary_search_by(|probe| {
18918 let cmp = probe
18919 .end
18920 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18921 if cmp.is_gt() {
18922 Ordering::Greater
18923 } else {
18924 Ordering::Less
18925 }
18926 }) {
18927 Ok(i) | Err(i) => i,
18928 };
18929 for range in &ranges[start_ix..] {
18930 if range
18931 .start
18932 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18933 .is_ge()
18934 {
18935 break;
18936 }
18937
18938 let start = range.start.to_display_point(display_snapshot);
18939 let end = range.end.to_display_point(display_snapshot);
18940 results.push((start..end, color))
18941 }
18942 }
18943 results
18944 }
18945
18946 /// Get the text ranges corresponding to the redaction query
18947 pub fn redacted_ranges(
18948 &self,
18949 search_range: Range<Anchor>,
18950 display_snapshot: &DisplaySnapshot,
18951 cx: &App,
18952 ) -> Vec<Range<DisplayPoint>> {
18953 display_snapshot
18954 .buffer_snapshot
18955 .redacted_ranges(search_range, |file| {
18956 if let Some(file) = file {
18957 file.is_private()
18958 && EditorSettings::get(
18959 Some(SettingsLocation {
18960 worktree_id: file.worktree_id(cx),
18961 path: file.path().as_ref(),
18962 }),
18963 cx,
18964 )
18965 .redact_private_values
18966 } else {
18967 false
18968 }
18969 })
18970 .map(|range| {
18971 range.start.to_display_point(display_snapshot)
18972 ..range.end.to_display_point(display_snapshot)
18973 })
18974 .collect()
18975 }
18976
18977 pub fn highlight_text<T: 'static>(
18978 &mut self,
18979 ranges: Vec<(Range<Anchor>, HighlightStyle)>,
18980 cx: &mut Context<Self>,
18981 ) {
18982 self.display_map
18983 .update(cx, |map, _| map.highlight_text(TypeId::of::<T>(), ranges));
18984 cx.notify();
18985 }
18986
18987 pub(crate) fn highlight_inlays<T: 'static>(
18988 &mut self,
18989 highlights: Vec<InlayHighlight>,
18990 style: HighlightStyle,
18991 cx: &mut Context<Self>,
18992 ) {
18993 self.display_map.update(cx, |map, _| {
18994 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18995 });
18996 cx.notify();
18997 }
18998
18999 pub fn text_highlights<'a, T: 'static>(
19000 &'a self,
19001 cx: &'a App,
19002 ) -> Option<&'a [(Range<Anchor>, HighlightStyle)]> {
19003 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19004 }
19005
19006 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19007 let cleared = self
19008 .display_map
19009 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19010 if cleared {
19011 cx.notify();
19012 }
19013 }
19014
19015 pub fn remove_text_highlights<T: 'static>(
19016 &mut self,
19017 cx: &mut Context<Self>,
19018 ) -> Option<Vec<(Range<Anchor>, HighlightStyle)>> {
19019 self.display_map
19020 .update(cx, |map, _| map.remove_text_highlights(TypeId::of::<T>()))
19021 }
19022
19023 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19024 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19025 && self.focus_handle.is_focused(window)
19026 }
19027
19028 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19029 self.show_cursor_when_unfocused = is_enabled;
19030 cx.notify();
19031 }
19032
19033 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19034 cx.notify();
19035 }
19036
19037 fn on_debug_session_event(
19038 &mut self,
19039 _session: Entity<Session>,
19040 event: &SessionEvent,
19041 cx: &mut Context<Self>,
19042 ) {
19043 match event {
19044 SessionEvent::InvalidateInlineValue => {
19045 self.refresh_inline_values(cx);
19046 }
19047 _ => {}
19048 }
19049 }
19050
19051 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19052 let Some(project) = self.project.clone() else {
19053 return;
19054 };
19055
19056 if !self.inline_value_cache.enabled {
19057 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19058 self.splice_inlays(&inlays, Vec::new(), cx);
19059 return;
19060 }
19061
19062 let current_execution_position = self
19063 .highlighted_rows
19064 .get(&TypeId::of::<ActiveDebugLine>())
19065 .and_then(|lines| lines.last().map(|line| line.range.start));
19066
19067 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19068 let inline_values = editor
19069 .update(cx, |editor, cx| {
19070 let Some(current_execution_position) = current_execution_position else {
19071 return Some(Task::ready(Ok(Vec::new())));
19072 };
19073
19074 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19075 let snapshot = buffer.snapshot(cx);
19076
19077 let excerpt = snapshot.excerpt_containing(
19078 current_execution_position..current_execution_position,
19079 )?;
19080
19081 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19082 })?;
19083
19084 let range =
19085 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19086
19087 project.inline_values(buffer, range, cx)
19088 })
19089 .ok()
19090 .flatten()?
19091 .await
19092 .context("refreshing debugger inlays")
19093 .log_err()?;
19094
19095 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19096
19097 for (buffer_id, inline_value) in inline_values
19098 .into_iter()
19099 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19100 {
19101 buffer_inline_values
19102 .entry(buffer_id)
19103 .or_default()
19104 .push(inline_value);
19105 }
19106
19107 editor
19108 .update(cx, |editor, cx| {
19109 let snapshot = editor.buffer.read(cx).snapshot(cx);
19110 let mut new_inlays = Vec::default();
19111
19112 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19113 let buffer_id = buffer_snapshot.remote_id();
19114 buffer_inline_values
19115 .get(&buffer_id)
19116 .into_iter()
19117 .flatten()
19118 .for_each(|hint| {
19119 let inlay = Inlay::debugger(
19120 post_inc(&mut editor.next_inlay_id),
19121 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19122 hint.text(),
19123 );
19124
19125 new_inlays.push(inlay);
19126 });
19127 }
19128
19129 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19130 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19131
19132 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19133 })
19134 .ok()?;
19135 Some(())
19136 });
19137 }
19138
19139 fn on_buffer_event(
19140 &mut self,
19141 multibuffer: &Entity<MultiBuffer>,
19142 event: &multi_buffer::Event,
19143 window: &mut Window,
19144 cx: &mut Context<Self>,
19145 ) {
19146 match event {
19147 multi_buffer::Event::Edited {
19148 singleton_buffer_edited,
19149 edited_buffer,
19150 } => {
19151 self.scrollbar_marker_state.dirty = true;
19152 self.active_indent_guides_state.dirty = true;
19153 self.refresh_active_diagnostics(cx);
19154 self.refresh_code_actions(window, cx);
19155 self.refresh_selected_text_highlights(true, window, cx);
19156 refresh_matching_bracket_highlights(self, window, cx);
19157 if self.has_active_inline_completion() {
19158 self.update_visible_inline_completion(window, cx);
19159 }
19160 if let Some(project) = self.project.as_ref() {
19161 if let Some(edited_buffer) = edited_buffer {
19162 project.update(cx, |project, cx| {
19163 self.registered_buffers
19164 .entry(edited_buffer.read(cx).remote_id())
19165 .or_insert_with(|| {
19166 project
19167 .register_buffer_with_language_servers(&edited_buffer, cx)
19168 });
19169 });
19170 }
19171 }
19172 cx.emit(EditorEvent::BufferEdited);
19173 cx.emit(SearchEvent::MatchesInvalidated);
19174
19175 if let Some(buffer) = edited_buffer {
19176 self.update_lsp_data(true, None, Some(buffer.read(cx).remote_id()), window, cx);
19177 }
19178
19179 if *singleton_buffer_edited {
19180 if let Some(buffer) = edited_buffer {
19181 if buffer.read(cx).file().is_none() {
19182 cx.emit(EditorEvent::TitleChanged);
19183 }
19184 }
19185 if let Some(project) = &self.project {
19186 #[allow(clippy::mutable_key_type)]
19187 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19188 multibuffer
19189 .all_buffers()
19190 .into_iter()
19191 .filter_map(|buffer| {
19192 buffer.update(cx, |buffer, cx| {
19193 let language = buffer.language()?;
19194 let should_discard = project.update(cx, |project, cx| {
19195 project.is_local()
19196 && !project.has_language_servers_for(buffer, cx)
19197 });
19198 should_discard.not().then_some(language.clone())
19199 })
19200 })
19201 .collect::<HashSet<_>>()
19202 });
19203 if !languages_affected.is_empty() {
19204 self.refresh_inlay_hints(
19205 InlayHintRefreshReason::BufferEdited(languages_affected),
19206 cx,
19207 );
19208 }
19209 }
19210 }
19211
19212 let Some(project) = &self.project else { return };
19213 let (telemetry, is_via_ssh) = {
19214 let project = project.read(cx);
19215 let telemetry = project.client().telemetry().clone();
19216 let is_via_ssh = project.is_via_ssh();
19217 (telemetry, is_via_ssh)
19218 };
19219 refresh_linked_ranges(self, window, cx);
19220 telemetry.log_edit_event("editor", is_via_ssh);
19221 }
19222 multi_buffer::Event::ExcerptsAdded {
19223 buffer,
19224 predecessor,
19225 excerpts,
19226 } => {
19227 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19228 let buffer_id = buffer.read(cx).remote_id();
19229 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19230 if let Some(project) = &self.project {
19231 update_uncommitted_diff_for_buffer(
19232 cx.entity(),
19233 project,
19234 [buffer.clone()],
19235 self.buffer.clone(),
19236 cx,
19237 )
19238 .detach();
19239 }
19240 }
19241 self.update_lsp_data(false, None, Some(buffer_id), window, cx);
19242 cx.emit(EditorEvent::ExcerptsAdded {
19243 buffer: buffer.clone(),
19244 predecessor: *predecessor,
19245 excerpts: excerpts.clone(),
19246 });
19247 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19248 }
19249 multi_buffer::Event::ExcerptsRemoved {
19250 ids,
19251 removed_buffer_ids,
19252 } => {
19253 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19254 let buffer = self.buffer.read(cx);
19255 self.registered_buffers
19256 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19257 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19258 cx.emit(EditorEvent::ExcerptsRemoved {
19259 ids: ids.clone(),
19260 removed_buffer_ids: removed_buffer_ids.clone(),
19261 });
19262 }
19263 multi_buffer::Event::ExcerptsEdited {
19264 excerpt_ids,
19265 buffer_ids,
19266 } => {
19267 self.display_map.update(cx, |map, cx| {
19268 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19269 });
19270 cx.emit(EditorEvent::ExcerptsEdited {
19271 ids: excerpt_ids.clone(),
19272 });
19273 }
19274 multi_buffer::Event::ExcerptsExpanded { ids } => {
19275 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19276 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19277 }
19278 multi_buffer::Event::Reparsed(buffer_id) => {
19279 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19280 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19281
19282 cx.emit(EditorEvent::Reparsed(*buffer_id));
19283 }
19284 multi_buffer::Event::DiffHunksToggled => {
19285 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19286 }
19287 multi_buffer::Event::LanguageChanged(buffer_id) => {
19288 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19289 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19290 cx.emit(EditorEvent::Reparsed(*buffer_id));
19291 cx.notify();
19292 }
19293 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19294 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19295 multi_buffer::Event::FileHandleChanged
19296 | multi_buffer::Event::Reloaded
19297 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19298 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19299 multi_buffer::Event::DiagnosticsUpdated => {
19300 self.update_diagnostics_state(window, cx);
19301 }
19302 _ => {}
19303 };
19304 }
19305
19306 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19307 self.refresh_active_diagnostics(cx);
19308 self.refresh_inline_diagnostics(true, window, cx);
19309 self.scrollbar_marker_state.dirty = true;
19310 cx.notify();
19311 }
19312
19313 pub fn start_temporary_diff_override(&mut self) {
19314 self.load_diff_task.take();
19315 self.temporary_diff_override = true;
19316 }
19317
19318 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19319 self.temporary_diff_override = false;
19320 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19321 self.buffer.update(cx, |buffer, cx| {
19322 buffer.set_all_diff_hunks_collapsed(cx);
19323 });
19324
19325 if let Some(project) = self.project.clone() {
19326 self.load_diff_task = Some(
19327 update_uncommitted_diff_for_buffer(
19328 cx.entity(),
19329 &project,
19330 self.buffer.read(cx).all_buffers(),
19331 self.buffer.clone(),
19332 cx,
19333 )
19334 .shared(),
19335 );
19336 }
19337 }
19338
19339 fn on_display_map_changed(
19340 &mut self,
19341 _: Entity<DisplayMap>,
19342 _: &mut Window,
19343 cx: &mut Context<Self>,
19344 ) {
19345 cx.notify();
19346 }
19347
19348 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19349 let new_severity = if self.diagnostics_enabled() {
19350 EditorSettings::get_global(cx)
19351 .diagnostics_max_severity
19352 .unwrap_or(DiagnosticSeverity::Hint)
19353 } else {
19354 DiagnosticSeverity::Off
19355 };
19356 self.set_max_diagnostics_severity(new_severity, cx);
19357 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19358 self.update_edit_prediction_settings(cx);
19359 self.refresh_inline_completion(true, false, window, cx);
19360 self.refresh_inlay_hints(
19361 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19362 self.selections.newest_anchor().head(),
19363 &self.buffer.read(cx).snapshot(cx),
19364 cx,
19365 )),
19366 cx,
19367 );
19368
19369 let old_cursor_shape = self.cursor_shape;
19370
19371 {
19372 let editor_settings = EditorSettings::get_global(cx);
19373 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19374 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19375 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19376 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19377 self.drag_and_drop_selection_enabled = editor_settings.drag_and_drop_selection;
19378 }
19379
19380 if old_cursor_shape != self.cursor_shape {
19381 cx.emit(EditorEvent::CursorShapeChanged);
19382 }
19383
19384 let project_settings = ProjectSettings::get_global(cx);
19385 self.serialize_dirty_buffers =
19386 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19387
19388 if self.mode.is_full() {
19389 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19390 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19391 if self.show_inline_diagnostics != show_inline_diagnostics {
19392 self.show_inline_diagnostics = show_inline_diagnostics;
19393 self.refresh_inline_diagnostics(false, window, cx);
19394 }
19395
19396 if self.git_blame_inline_enabled != inline_blame_enabled {
19397 self.toggle_git_blame_inline_internal(false, window, cx);
19398 }
19399
19400 let minimap_settings = EditorSettings::get_global(cx).minimap;
19401 if self.minimap_visibility != MinimapVisibility::Disabled {
19402 if self.minimap_visibility.settings_visibility()
19403 != minimap_settings.minimap_enabled()
19404 {
19405 self.set_minimap_visibility(
19406 MinimapVisibility::for_mode(self.mode(), cx),
19407 window,
19408 cx,
19409 );
19410 } else if let Some(minimap_entity) = self.minimap.as_ref() {
19411 minimap_entity.update(cx, |minimap_editor, cx| {
19412 minimap_editor.update_minimap_configuration(minimap_settings, cx)
19413 })
19414 }
19415 }
19416 }
19417
19418 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
19419 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
19420 }) {
19421 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
19422 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
19423 }
19424 self.refresh_colors(true, None, None, window, cx);
19425 }
19426
19427 cx.notify();
19428 }
19429
19430 pub fn set_searchable(&mut self, searchable: bool) {
19431 self.searchable = searchable;
19432 }
19433
19434 pub fn searchable(&self) -> bool {
19435 self.searchable
19436 }
19437
19438 fn open_proposed_changes_editor(
19439 &mut self,
19440 _: &OpenProposedChangesEditor,
19441 window: &mut Window,
19442 cx: &mut Context<Self>,
19443 ) {
19444 let Some(workspace) = self.workspace() else {
19445 cx.propagate();
19446 return;
19447 };
19448
19449 let selections = self.selections.all::<usize>(cx);
19450 let multi_buffer = self.buffer.read(cx);
19451 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19452 let mut new_selections_by_buffer = HashMap::default();
19453 for selection in selections {
19454 for (buffer, range, _) in
19455 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
19456 {
19457 let mut range = range.to_point(buffer);
19458 range.start.column = 0;
19459 range.end.column = buffer.line_len(range.end.row);
19460 new_selections_by_buffer
19461 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
19462 .or_insert(Vec::new())
19463 .push(range)
19464 }
19465 }
19466
19467 let proposed_changes_buffers = new_selections_by_buffer
19468 .into_iter()
19469 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
19470 .collect::<Vec<_>>();
19471 let proposed_changes_editor = cx.new(|cx| {
19472 ProposedChangesEditor::new(
19473 "Proposed changes",
19474 proposed_changes_buffers,
19475 self.project.clone(),
19476 window,
19477 cx,
19478 )
19479 });
19480
19481 window.defer(cx, move |window, cx| {
19482 workspace.update(cx, |workspace, cx| {
19483 workspace.active_pane().update(cx, |pane, cx| {
19484 pane.add_item(
19485 Box::new(proposed_changes_editor),
19486 true,
19487 true,
19488 None,
19489 window,
19490 cx,
19491 );
19492 });
19493 });
19494 });
19495 }
19496
19497 pub fn open_excerpts_in_split(
19498 &mut self,
19499 _: &OpenExcerptsSplit,
19500 window: &mut Window,
19501 cx: &mut Context<Self>,
19502 ) {
19503 self.open_excerpts_common(None, true, window, cx)
19504 }
19505
19506 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
19507 self.open_excerpts_common(None, false, window, cx)
19508 }
19509
19510 fn open_excerpts_common(
19511 &mut self,
19512 jump_data: Option<JumpData>,
19513 split: bool,
19514 window: &mut Window,
19515 cx: &mut Context<Self>,
19516 ) {
19517 let Some(workspace) = self.workspace() else {
19518 cx.propagate();
19519 return;
19520 };
19521
19522 if self.buffer.read(cx).is_singleton() {
19523 cx.propagate();
19524 return;
19525 }
19526
19527 let mut new_selections_by_buffer = HashMap::default();
19528 match &jump_data {
19529 Some(JumpData::MultiBufferPoint {
19530 excerpt_id,
19531 position,
19532 anchor,
19533 line_offset_from_top,
19534 }) => {
19535 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19536 if let Some(buffer) = multi_buffer_snapshot
19537 .buffer_id_for_excerpt(*excerpt_id)
19538 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
19539 {
19540 let buffer_snapshot = buffer.read(cx).snapshot();
19541 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
19542 language::ToPoint::to_point(anchor, &buffer_snapshot)
19543 } else {
19544 buffer_snapshot.clip_point(*position, Bias::Left)
19545 };
19546 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
19547 new_selections_by_buffer.insert(
19548 buffer,
19549 (
19550 vec![jump_to_offset..jump_to_offset],
19551 Some(*line_offset_from_top),
19552 ),
19553 );
19554 }
19555 }
19556 Some(JumpData::MultiBufferRow {
19557 row,
19558 line_offset_from_top,
19559 }) => {
19560 let point = MultiBufferPoint::new(row.0, 0);
19561 if let Some((buffer, buffer_point, _)) =
19562 self.buffer.read(cx).point_to_buffer_point(point, cx)
19563 {
19564 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
19565 new_selections_by_buffer
19566 .entry(buffer)
19567 .or_insert((Vec::new(), Some(*line_offset_from_top)))
19568 .0
19569 .push(buffer_offset..buffer_offset)
19570 }
19571 }
19572 None => {
19573 let selections = self.selections.all::<usize>(cx);
19574 let multi_buffer = self.buffer.read(cx);
19575 for selection in selections {
19576 for (snapshot, range, _, anchor) in multi_buffer
19577 .snapshot(cx)
19578 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
19579 {
19580 if let Some(anchor) = anchor {
19581 // selection is in a deleted hunk
19582 let Some(buffer_id) = anchor.buffer_id else {
19583 continue;
19584 };
19585 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
19586 continue;
19587 };
19588 let offset = text::ToOffset::to_offset(
19589 &anchor.text_anchor,
19590 &buffer_handle.read(cx).snapshot(),
19591 );
19592 let range = offset..offset;
19593 new_selections_by_buffer
19594 .entry(buffer_handle)
19595 .or_insert((Vec::new(), None))
19596 .0
19597 .push(range)
19598 } else {
19599 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
19600 else {
19601 continue;
19602 };
19603 new_selections_by_buffer
19604 .entry(buffer_handle)
19605 .or_insert((Vec::new(), None))
19606 .0
19607 .push(range)
19608 }
19609 }
19610 }
19611 }
19612 }
19613
19614 new_selections_by_buffer
19615 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
19616
19617 if new_selections_by_buffer.is_empty() {
19618 return;
19619 }
19620
19621 // We defer the pane interaction because we ourselves are a workspace item
19622 // and activating a new item causes the pane to call a method on us reentrantly,
19623 // which panics if we're on the stack.
19624 window.defer(cx, move |window, cx| {
19625 workspace.update(cx, |workspace, cx| {
19626 let pane = if split {
19627 workspace.adjacent_pane(window, cx)
19628 } else {
19629 workspace.active_pane().clone()
19630 };
19631
19632 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
19633 let editor = buffer
19634 .read(cx)
19635 .file()
19636 .is_none()
19637 .then(|| {
19638 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
19639 // so `workspace.open_project_item` will never find them, always opening a new editor.
19640 // Instead, we try to activate the existing editor in the pane first.
19641 let (editor, pane_item_index) =
19642 pane.read(cx).items().enumerate().find_map(|(i, item)| {
19643 let editor = item.downcast::<Editor>()?;
19644 let singleton_buffer =
19645 editor.read(cx).buffer().read(cx).as_singleton()?;
19646 if singleton_buffer == buffer {
19647 Some((editor, i))
19648 } else {
19649 None
19650 }
19651 })?;
19652 pane.update(cx, |pane, cx| {
19653 pane.activate_item(pane_item_index, true, true, window, cx)
19654 });
19655 Some(editor)
19656 })
19657 .flatten()
19658 .unwrap_or_else(|| {
19659 workspace.open_project_item::<Self>(
19660 pane.clone(),
19661 buffer,
19662 true,
19663 true,
19664 window,
19665 cx,
19666 )
19667 });
19668
19669 editor.update(cx, |editor, cx| {
19670 let autoscroll = match scroll_offset {
19671 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
19672 None => Autoscroll::newest(),
19673 };
19674 let nav_history = editor.nav_history.take();
19675 editor.change_selections(Some(autoscroll), window, cx, |s| {
19676 s.select_ranges(ranges);
19677 });
19678 editor.nav_history = nav_history;
19679 });
19680 }
19681 })
19682 });
19683 }
19684
19685 // For now, don't allow opening excerpts in buffers that aren't backed by
19686 // regular project files.
19687 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
19688 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
19689 }
19690
19691 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
19692 let snapshot = self.buffer.read(cx).read(cx);
19693 let ranges = self.text_highlights::<InputComposition>(cx)?;
19694 Some(
19695 ranges
19696 .iter()
19697 .map(move |(range, _)| {
19698 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19699 })
19700 .collect(),
19701 )
19702 }
19703
19704 fn selection_replacement_ranges(
19705 &self,
19706 range: Range<OffsetUtf16>,
19707 cx: &mut App,
19708 ) -> Vec<Range<OffsetUtf16>> {
19709 let selections = self.selections.all::<OffsetUtf16>(cx);
19710 let newest_selection = selections
19711 .iter()
19712 .max_by_key(|selection| selection.id)
19713 .unwrap();
19714 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19715 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19716 let snapshot = self.buffer.read(cx).read(cx);
19717 selections
19718 .into_iter()
19719 .map(|mut selection| {
19720 selection.start.0 =
19721 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19722 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19723 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19724 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19725 })
19726 .collect()
19727 }
19728
19729 fn report_editor_event(
19730 &self,
19731 event_type: &'static str,
19732 file_extension: Option<String>,
19733 cx: &App,
19734 ) {
19735 if cfg!(any(test, feature = "test-support")) {
19736 return;
19737 }
19738
19739 let Some(project) = &self.project else { return };
19740
19741 // If None, we are in a file without an extension
19742 let file = self
19743 .buffer
19744 .read(cx)
19745 .as_singleton()
19746 .and_then(|b| b.read(cx).file());
19747 let file_extension = file_extension.or(file
19748 .as_ref()
19749 .and_then(|file| Path::new(file.file_name(cx)).extension())
19750 .and_then(|e| e.to_str())
19751 .map(|a| a.to_string()));
19752
19753 let vim_mode = vim_enabled(cx);
19754
19755 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
19756 let copilot_enabled = edit_predictions_provider
19757 == language::language_settings::EditPredictionProvider::Copilot;
19758 let copilot_enabled_for_language = self
19759 .buffer
19760 .read(cx)
19761 .language_settings(cx)
19762 .show_edit_predictions;
19763
19764 let project = project.read(cx);
19765 telemetry::event!(
19766 event_type,
19767 file_extension,
19768 vim_mode,
19769 copilot_enabled,
19770 copilot_enabled_for_language,
19771 edit_predictions_provider,
19772 is_via_ssh = project.is_via_ssh(),
19773 );
19774 }
19775
19776 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
19777 /// with each line being an array of {text, highlight} objects.
19778 fn copy_highlight_json(
19779 &mut self,
19780 _: &CopyHighlightJson,
19781 window: &mut Window,
19782 cx: &mut Context<Self>,
19783 ) {
19784 #[derive(Serialize)]
19785 struct Chunk<'a> {
19786 text: String,
19787 highlight: Option<&'a str>,
19788 }
19789
19790 let snapshot = self.buffer.read(cx).snapshot(cx);
19791 let range = self
19792 .selected_text_range(false, window, cx)
19793 .and_then(|selection| {
19794 if selection.range.is_empty() {
19795 None
19796 } else {
19797 Some(selection.range)
19798 }
19799 })
19800 .unwrap_or_else(|| 0..snapshot.len());
19801
19802 let chunks = snapshot.chunks(range, true);
19803 let mut lines = Vec::new();
19804 let mut line: VecDeque<Chunk> = VecDeque::new();
19805
19806 let Some(style) = self.style.as_ref() else {
19807 return;
19808 };
19809
19810 for chunk in chunks {
19811 let highlight = chunk
19812 .syntax_highlight_id
19813 .and_then(|id| id.name(&style.syntax));
19814 let mut chunk_lines = chunk.text.split('\n').peekable();
19815 while let Some(text) = chunk_lines.next() {
19816 let mut merged_with_last_token = false;
19817 if let Some(last_token) = line.back_mut() {
19818 if last_token.highlight == highlight {
19819 last_token.text.push_str(text);
19820 merged_with_last_token = true;
19821 }
19822 }
19823
19824 if !merged_with_last_token {
19825 line.push_back(Chunk {
19826 text: text.into(),
19827 highlight,
19828 });
19829 }
19830
19831 if chunk_lines.peek().is_some() {
19832 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19833 line.pop_front();
19834 }
19835 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19836 line.pop_back();
19837 }
19838
19839 lines.push(mem::take(&mut line));
19840 }
19841 }
19842 }
19843
19844 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19845 return;
19846 };
19847 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19848 }
19849
19850 pub fn open_context_menu(
19851 &mut self,
19852 _: &OpenContextMenu,
19853 window: &mut Window,
19854 cx: &mut Context<Self>,
19855 ) {
19856 self.request_autoscroll(Autoscroll::newest(), cx);
19857 let position = self.selections.newest_display(cx).start;
19858 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19859 }
19860
19861 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19862 &self.inlay_hint_cache
19863 }
19864
19865 pub fn replay_insert_event(
19866 &mut self,
19867 text: &str,
19868 relative_utf16_range: Option<Range<isize>>,
19869 window: &mut Window,
19870 cx: &mut Context<Self>,
19871 ) {
19872 if !self.input_enabled {
19873 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19874 return;
19875 }
19876 if let Some(relative_utf16_range) = relative_utf16_range {
19877 let selections = self.selections.all::<OffsetUtf16>(cx);
19878 self.change_selections(None, window, cx, |s| {
19879 let new_ranges = selections.into_iter().map(|range| {
19880 let start = OffsetUtf16(
19881 range
19882 .head()
19883 .0
19884 .saturating_add_signed(relative_utf16_range.start),
19885 );
19886 let end = OffsetUtf16(
19887 range
19888 .head()
19889 .0
19890 .saturating_add_signed(relative_utf16_range.end),
19891 );
19892 start..end
19893 });
19894 s.select_ranges(new_ranges);
19895 });
19896 }
19897
19898 self.handle_input(text, window, cx);
19899 }
19900
19901 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19902 let Some(provider) = self.semantics_provider.as_ref() else {
19903 return false;
19904 };
19905
19906 let mut supports = false;
19907 self.buffer().update(cx, |this, cx| {
19908 this.for_each_buffer(|buffer| {
19909 supports |= provider.supports_inlay_hints(buffer, cx);
19910 });
19911 });
19912
19913 supports
19914 }
19915
19916 pub fn is_focused(&self, window: &Window) -> bool {
19917 self.focus_handle.is_focused(window)
19918 }
19919
19920 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19921 cx.emit(EditorEvent::Focused);
19922
19923 if let Some(descendant) = self
19924 .last_focused_descendant
19925 .take()
19926 .and_then(|descendant| descendant.upgrade())
19927 {
19928 window.focus(&descendant);
19929 } else {
19930 if let Some(blame) = self.blame.as_ref() {
19931 blame.update(cx, GitBlame::focus)
19932 }
19933
19934 self.blink_manager.update(cx, BlinkManager::enable);
19935 self.show_cursor_names(window, cx);
19936 self.buffer.update(cx, |buffer, cx| {
19937 buffer.finalize_last_transaction(cx);
19938 if self.leader_id.is_none() {
19939 buffer.set_active_selections(
19940 &self.selections.disjoint_anchors(),
19941 self.selections.line_mode,
19942 self.cursor_shape,
19943 cx,
19944 );
19945 }
19946 });
19947 }
19948 }
19949
19950 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19951 cx.emit(EditorEvent::FocusedIn)
19952 }
19953
19954 fn handle_focus_out(
19955 &mut self,
19956 event: FocusOutEvent,
19957 _window: &mut Window,
19958 cx: &mut Context<Self>,
19959 ) {
19960 if event.blurred != self.focus_handle {
19961 self.last_focused_descendant = Some(event.blurred);
19962 }
19963 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19964 }
19965
19966 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19967 self.blink_manager.update(cx, BlinkManager::disable);
19968 self.buffer
19969 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19970
19971 if let Some(blame) = self.blame.as_ref() {
19972 blame.update(cx, GitBlame::blur)
19973 }
19974 if !self.hover_state.focused(window, cx) {
19975 hide_hover(self, cx);
19976 }
19977 if !self
19978 .context_menu
19979 .borrow()
19980 .as_ref()
19981 .is_some_and(|context_menu| context_menu.focused(window, cx))
19982 {
19983 self.hide_context_menu(window, cx);
19984 }
19985 self.discard_inline_completion(false, cx);
19986 cx.emit(EditorEvent::Blurred);
19987 cx.notify();
19988 }
19989
19990 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19991 let mut pending: String = window
19992 .pending_input_keystrokes()
19993 .into_iter()
19994 .flatten()
19995 .filter_map(|keystroke| {
19996 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
19997 keystroke.key_char.clone()
19998 } else {
19999 None
20000 }
20001 })
20002 .collect();
20003
20004 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20005 pending = "".to_string();
20006 }
20007
20008 let existing_pending = self.text_highlights::<PendingInput>(cx).map(|ranges| {
20009 ranges
20010 .iter()
20011 .map(|(range, _)| range.clone())
20012 .collect::<Vec<_>>()
20013 });
20014 if existing_pending.is_none() && pending.is_empty() {
20015 return;
20016 }
20017 let transaction =
20018 self.transact(window, cx, |this, window, cx| {
20019 let selections = this.selections.all::<usize>(cx);
20020 let edits = selections
20021 .iter()
20022 .map(|selection| (selection.end..selection.end, pending.clone()));
20023 this.edit(edits, cx);
20024 this.change_selections(None, window, cx, |s| {
20025 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20026 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20027 }));
20028 });
20029 if let Some(existing_ranges) = existing_pending {
20030 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20031 this.edit(edits, cx);
20032 }
20033 });
20034
20035 let snapshot = self.snapshot(window, cx);
20036 let ranges = self
20037 .selections
20038 .all::<usize>(cx)
20039 .into_iter()
20040 .map(|selection| {
20041 (
20042 snapshot.buffer_snapshot.anchor_after(selection.end)
20043 ..snapshot
20044 .buffer_snapshot
20045 .anchor_before(selection.end + pending.len()),
20046 HighlightStyle {
20047 underline: Some(UnderlineStyle {
20048 thickness: px(1.),
20049 color: None,
20050 wavy: false,
20051 }),
20052 ..Default::default()
20053 },
20054 )
20055 })
20056 .collect();
20057
20058 if pending.is_empty() {
20059 self.clear_highlights::<PendingInput>(cx);
20060 } else {
20061 self.highlight_text::<PendingInput>(ranges, cx);
20062 }
20063
20064 self.ime_transaction = self.ime_transaction.or(transaction);
20065 if let Some(transaction) = self.ime_transaction {
20066 self.buffer.update(cx, |buffer, cx| {
20067 buffer.group_until_transaction(transaction, cx);
20068 });
20069 }
20070
20071 if self.text_highlights::<PendingInput>(cx).is_none() {
20072 self.ime_transaction.take();
20073 }
20074 }
20075
20076 pub fn register_action_renderer(
20077 &mut self,
20078 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20079 ) -> Subscription {
20080 let id = self.next_editor_action_id.post_inc();
20081 self.editor_actions
20082 .borrow_mut()
20083 .insert(id, Box::new(listener));
20084
20085 let editor_actions = self.editor_actions.clone();
20086 Subscription::new(move || {
20087 editor_actions.borrow_mut().remove(&id);
20088 })
20089 }
20090
20091 pub fn register_action<A: Action>(
20092 &mut self,
20093 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20094 ) -> Subscription {
20095 let id = self.next_editor_action_id.post_inc();
20096 let listener = Arc::new(listener);
20097 self.editor_actions.borrow_mut().insert(
20098 id,
20099 Box::new(move |_, window, _| {
20100 let listener = listener.clone();
20101 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20102 let action = action.downcast_ref().unwrap();
20103 if phase == DispatchPhase::Bubble {
20104 listener(action, window, cx)
20105 }
20106 })
20107 }),
20108 );
20109
20110 let editor_actions = self.editor_actions.clone();
20111 Subscription::new(move || {
20112 editor_actions.borrow_mut().remove(&id);
20113 })
20114 }
20115
20116 pub fn file_header_size(&self) -> u32 {
20117 FILE_HEADER_HEIGHT
20118 }
20119
20120 pub fn restore(
20121 &mut self,
20122 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20123 window: &mut Window,
20124 cx: &mut Context<Self>,
20125 ) {
20126 let workspace = self.workspace();
20127 let project = self.project.as_ref();
20128 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20129 let mut tasks = Vec::new();
20130 for (buffer_id, changes) in revert_changes {
20131 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20132 buffer.update(cx, |buffer, cx| {
20133 buffer.edit(
20134 changes
20135 .into_iter()
20136 .map(|(range, text)| (range, text.to_string())),
20137 None,
20138 cx,
20139 );
20140 });
20141
20142 if let Some(project) =
20143 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20144 {
20145 project.update(cx, |project, cx| {
20146 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20147 })
20148 }
20149 }
20150 }
20151 tasks
20152 });
20153 cx.spawn_in(window, async move |_, cx| {
20154 for (buffer, task) in save_tasks {
20155 let result = task.await;
20156 if result.is_err() {
20157 let Some(path) = buffer
20158 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20159 .ok()
20160 else {
20161 continue;
20162 };
20163 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20164 let Some(task) = cx
20165 .update_window_entity(&workspace, |workspace, window, cx| {
20166 workspace
20167 .open_path_preview(path, None, false, false, false, window, cx)
20168 })
20169 .ok()
20170 else {
20171 continue;
20172 };
20173 task.await.log_err();
20174 }
20175 }
20176 }
20177 })
20178 .detach();
20179 self.change_selections(None, window, cx, |selections| selections.refresh());
20180 }
20181
20182 pub fn to_pixel_point(
20183 &self,
20184 source: multi_buffer::Anchor,
20185 editor_snapshot: &EditorSnapshot,
20186 window: &mut Window,
20187 ) -> Option<gpui::Point<Pixels>> {
20188 let source_point = source.to_display_point(editor_snapshot);
20189 self.display_to_pixel_point(source_point, editor_snapshot, window)
20190 }
20191
20192 pub fn display_to_pixel_point(
20193 &self,
20194 source: DisplayPoint,
20195 editor_snapshot: &EditorSnapshot,
20196 window: &mut Window,
20197 ) -> Option<gpui::Point<Pixels>> {
20198 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20199 let text_layout_details = self.text_layout_details(window);
20200 let scroll_top = text_layout_details
20201 .scroll_anchor
20202 .scroll_position(editor_snapshot)
20203 .y;
20204
20205 if source.row().as_f32() < scroll_top.floor() {
20206 return None;
20207 }
20208 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20209 let source_y = line_height * (source.row().as_f32() - scroll_top);
20210 Some(gpui::Point::new(source_x, source_y))
20211 }
20212
20213 pub fn has_visible_completions_menu(&self) -> bool {
20214 !self.edit_prediction_preview_is_active()
20215 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20216 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20217 })
20218 }
20219
20220 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20221 if self.mode.is_minimap() {
20222 return;
20223 }
20224 self.addons
20225 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20226 }
20227
20228 pub fn unregister_addon<T: Addon>(&mut self) {
20229 self.addons.remove(&std::any::TypeId::of::<T>());
20230 }
20231
20232 pub fn addon<T: Addon>(&self) -> Option<&T> {
20233 let type_id = std::any::TypeId::of::<T>();
20234 self.addons
20235 .get(&type_id)
20236 .and_then(|item| item.to_any().downcast_ref::<T>())
20237 }
20238
20239 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20240 let type_id = std::any::TypeId::of::<T>();
20241 self.addons
20242 .get_mut(&type_id)
20243 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20244 }
20245
20246 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
20247 let text_layout_details = self.text_layout_details(window);
20248 let style = &text_layout_details.editor_style;
20249 let font_id = window.text_system().resolve_font(&style.text.font());
20250 let font_size = style.text.font_size.to_pixels(window.rem_size());
20251 let line_height = style.text.line_height_in_pixels(window.rem_size());
20252 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20253
20254 gpui::Size::new(em_width, line_height)
20255 }
20256
20257 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20258 self.load_diff_task.clone()
20259 }
20260
20261 fn read_metadata_from_db(
20262 &mut self,
20263 item_id: u64,
20264 workspace_id: WorkspaceId,
20265 window: &mut Window,
20266 cx: &mut Context<Editor>,
20267 ) {
20268 if self.is_singleton(cx)
20269 && !self.mode.is_minimap()
20270 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20271 {
20272 let buffer_snapshot = OnceCell::new();
20273
20274 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20275 if !folds.is_empty() {
20276 let snapshot =
20277 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20278 self.fold_ranges(
20279 folds
20280 .into_iter()
20281 .map(|(start, end)| {
20282 snapshot.clip_offset(start, Bias::Left)
20283 ..snapshot.clip_offset(end, Bias::Right)
20284 })
20285 .collect(),
20286 false,
20287 window,
20288 cx,
20289 );
20290 }
20291 }
20292
20293 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20294 if !selections.is_empty() {
20295 let snapshot =
20296 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20297 // skip adding the initial selection to selection history
20298 self.selection_history.mode = SelectionHistoryMode::Skipping;
20299 self.change_selections(None, window, cx, |s| {
20300 s.select_ranges(selections.into_iter().map(|(start, end)| {
20301 snapshot.clip_offset(start, Bias::Left)
20302 ..snapshot.clip_offset(end, Bias::Right)
20303 }));
20304 });
20305 self.selection_history.mode = SelectionHistoryMode::Normal;
20306 }
20307 };
20308 }
20309
20310 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20311 }
20312
20313 fn update_lsp_data(
20314 &mut self,
20315 update_on_edit: bool,
20316 for_server_id: Option<LanguageServerId>,
20317 for_buffer: Option<BufferId>,
20318 window: &mut Window,
20319 cx: &mut Context<'_, Self>,
20320 ) {
20321 self.pull_diagnostics(for_buffer, window, cx);
20322 self.refresh_colors(update_on_edit, for_server_id, for_buffer, window, cx);
20323 }
20324}
20325
20326fn vim_enabled(cx: &App) -> bool {
20327 cx.global::<SettingsStore>()
20328 .raw_user_settings()
20329 .get("vim_mode")
20330 == Some(&serde_json::Value::Bool(true))
20331}
20332
20333fn process_completion_for_edit(
20334 completion: &Completion,
20335 intent: CompletionIntent,
20336 buffer: &Entity<Buffer>,
20337 cursor_position: &text::Anchor,
20338 cx: &mut Context<Editor>,
20339) -> CompletionEdit {
20340 let buffer = buffer.read(cx);
20341 let buffer_snapshot = buffer.snapshot();
20342 let (snippet, new_text) = if completion.is_snippet() {
20343 // Workaround for typescript language server issues so that methods don't expand within
20344 // strings and functions with type expressions. The previous point is used because the query
20345 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
20346 let mut snippet_source = completion.new_text.clone();
20347 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
20348 previous_point.column = previous_point.column.saturating_sub(1);
20349 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
20350 if scope.prefers_label_for_snippet_in_completion() {
20351 if let Some(label) = completion.label() {
20352 if matches!(
20353 completion.kind(),
20354 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20355 ) {
20356 snippet_source = label;
20357 }
20358 }
20359 }
20360 }
20361 match Snippet::parse(&snippet_source).log_err() {
20362 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20363 None => (None, completion.new_text.clone()),
20364 }
20365 } else {
20366 (None, completion.new_text.clone())
20367 };
20368
20369 let mut range_to_replace = {
20370 let replace_range = &completion.replace_range;
20371 if let CompletionSource::Lsp {
20372 insert_range: Some(insert_range),
20373 ..
20374 } = &completion.source
20375 {
20376 debug_assert_eq!(
20377 insert_range.start, replace_range.start,
20378 "insert_range and replace_range should start at the same position"
20379 );
20380 debug_assert!(
20381 insert_range
20382 .start
20383 .cmp(&cursor_position, &buffer_snapshot)
20384 .is_le(),
20385 "insert_range should start before or at cursor position"
20386 );
20387 debug_assert!(
20388 replace_range
20389 .start
20390 .cmp(&cursor_position, &buffer_snapshot)
20391 .is_le(),
20392 "replace_range should start before or at cursor position"
20393 );
20394 debug_assert!(
20395 insert_range
20396 .end
20397 .cmp(&cursor_position, &buffer_snapshot)
20398 .is_le(),
20399 "insert_range should end before or at cursor position"
20400 );
20401
20402 let should_replace = match intent {
20403 CompletionIntent::CompleteWithInsert => false,
20404 CompletionIntent::CompleteWithReplace => true,
20405 CompletionIntent::Complete | CompletionIntent::Compose => {
20406 let insert_mode =
20407 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
20408 .completions
20409 .lsp_insert_mode;
20410 match insert_mode {
20411 LspInsertMode::Insert => false,
20412 LspInsertMode::Replace => true,
20413 LspInsertMode::ReplaceSubsequence => {
20414 let mut text_to_replace = buffer.chars_for_range(
20415 buffer.anchor_before(replace_range.start)
20416 ..buffer.anchor_after(replace_range.end),
20417 );
20418 let mut current_needle = text_to_replace.next();
20419 for haystack_ch in completion.label.text.chars() {
20420 if let Some(needle_ch) = current_needle {
20421 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
20422 current_needle = text_to_replace.next();
20423 }
20424 }
20425 }
20426 current_needle.is_none()
20427 }
20428 LspInsertMode::ReplaceSuffix => {
20429 if replace_range
20430 .end
20431 .cmp(&cursor_position, &buffer_snapshot)
20432 .is_gt()
20433 {
20434 let range_after_cursor = *cursor_position..replace_range.end;
20435 let text_after_cursor = buffer
20436 .text_for_range(
20437 buffer.anchor_before(range_after_cursor.start)
20438 ..buffer.anchor_after(range_after_cursor.end),
20439 )
20440 .collect::<String>()
20441 .to_ascii_lowercase();
20442 completion
20443 .label
20444 .text
20445 .to_ascii_lowercase()
20446 .ends_with(&text_after_cursor)
20447 } else {
20448 true
20449 }
20450 }
20451 }
20452 }
20453 };
20454
20455 if should_replace {
20456 replace_range.clone()
20457 } else {
20458 insert_range.clone()
20459 }
20460 } else {
20461 replace_range.clone()
20462 }
20463 };
20464
20465 if range_to_replace
20466 .end
20467 .cmp(&cursor_position, &buffer_snapshot)
20468 .is_lt()
20469 {
20470 range_to_replace.end = *cursor_position;
20471 }
20472
20473 CompletionEdit {
20474 new_text,
20475 replace_range: range_to_replace.to_offset(&buffer),
20476 snippet,
20477 }
20478}
20479
20480struct CompletionEdit {
20481 new_text: String,
20482 replace_range: Range<usize>,
20483 snippet: Option<Snippet>,
20484}
20485
20486fn insert_extra_newline_brackets(
20487 buffer: &MultiBufferSnapshot,
20488 range: Range<usize>,
20489 language: &language::LanguageScope,
20490) -> bool {
20491 let leading_whitespace_len = buffer
20492 .reversed_chars_at(range.start)
20493 .take_while(|c| c.is_whitespace() && *c != '\n')
20494 .map(|c| c.len_utf8())
20495 .sum::<usize>();
20496 let trailing_whitespace_len = buffer
20497 .chars_at(range.end)
20498 .take_while(|c| c.is_whitespace() && *c != '\n')
20499 .map(|c| c.len_utf8())
20500 .sum::<usize>();
20501 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
20502
20503 language.brackets().any(|(pair, enabled)| {
20504 let pair_start = pair.start.trim_end();
20505 let pair_end = pair.end.trim_start();
20506
20507 enabled
20508 && pair.newline
20509 && buffer.contains_str_at(range.end, pair_end)
20510 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
20511 })
20512}
20513
20514fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
20515 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
20516 [(buffer, range, _)] => (*buffer, range.clone()),
20517 _ => return false,
20518 };
20519 let pair = {
20520 let mut result: Option<BracketMatch> = None;
20521
20522 for pair in buffer
20523 .all_bracket_ranges(range.clone())
20524 .filter(move |pair| {
20525 pair.open_range.start <= range.start && pair.close_range.end >= range.end
20526 })
20527 {
20528 let len = pair.close_range.end - pair.open_range.start;
20529
20530 if let Some(existing) = &result {
20531 let existing_len = existing.close_range.end - existing.open_range.start;
20532 if len > existing_len {
20533 continue;
20534 }
20535 }
20536
20537 result = Some(pair);
20538 }
20539
20540 result
20541 };
20542 let Some(pair) = pair else {
20543 return false;
20544 };
20545 pair.newline_only
20546 && buffer
20547 .chars_for_range(pair.open_range.end..range.start)
20548 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
20549 .all(|c| c.is_whitespace() && c != '\n')
20550}
20551
20552fn update_uncommitted_diff_for_buffer(
20553 editor: Entity<Editor>,
20554 project: &Entity<Project>,
20555 buffers: impl IntoIterator<Item = Entity<Buffer>>,
20556 buffer: Entity<MultiBuffer>,
20557 cx: &mut App,
20558) -> Task<()> {
20559 let mut tasks = Vec::new();
20560 project.update(cx, |project, cx| {
20561 for buffer in buffers {
20562 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
20563 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
20564 }
20565 }
20566 });
20567 cx.spawn(async move |cx| {
20568 let diffs = future::join_all(tasks).await;
20569 if editor
20570 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
20571 .unwrap_or(false)
20572 {
20573 return;
20574 }
20575
20576 buffer
20577 .update(cx, |buffer, cx| {
20578 for diff in diffs.into_iter().flatten() {
20579 buffer.add_diff(diff, cx);
20580 }
20581 })
20582 .ok();
20583 })
20584}
20585
20586fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
20587 let tab_size = tab_size.get() as usize;
20588 let mut width = offset;
20589
20590 for ch in text.chars() {
20591 width += if ch == '\t' {
20592 tab_size - (width % tab_size)
20593 } else {
20594 1
20595 };
20596 }
20597
20598 width - offset
20599}
20600
20601#[cfg(test)]
20602mod tests {
20603 use super::*;
20604
20605 #[test]
20606 fn test_string_size_with_expanded_tabs() {
20607 let nz = |val| NonZeroU32::new(val).unwrap();
20608 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
20609 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
20610 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
20611 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
20612 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
20613 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
20614 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
20615 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
20616 }
20617}
20618
20619/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
20620struct WordBreakingTokenizer<'a> {
20621 input: &'a str,
20622}
20623
20624impl<'a> WordBreakingTokenizer<'a> {
20625 fn new(input: &'a str) -> Self {
20626 Self { input }
20627 }
20628}
20629
20630fn is_char_ideographic(ch: char) -> bool {
20631 use unicode_script::Script::*;
20632 use unicode_script::UnicodeScript;
20633 matches!(ch.script(), Han | Tangut | Yi)
20634}
20635
20636fn is_grapheme_ideographic(text: &str) -> bool {
20637 text.chars().any(is_char_ideographic)
20638}
20639
20640fn is_grapheme_whitespace(text: &str) -> bool {
20641 text.chars().any(|x| x.is_whitespace())
20642}
20643
20644fn should_stay_with_preceding_ideograph(text: &str) -> bool {
20645 text.chars().next().map_or(false, |ch| {
20646 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
20647 })
20648}
20649
20650#[derive(PartialEq, Eq, Debug, Clone, Copy)]
20651enum WordBreakToken<'a> {
20652 Word { token: &'a str, grapheme_len: usize },
20653 InlineWhitespace { token: &'a str, grapheme_len: usize },
20654 Newline,
20655}
20656
20657impl<'a> Iterator for WordBreakingTokenizer<'a> {
20658 /// Yields a span, the count of graphemes in the token, and whether it was
20659 /// whitespace. Note that it also breaks at word boundaries.
20660 type Item = WordBreakToken<'a>;
20661
20662 fn next(&mut self) -> Option<Self::Item> {
20663 use unicode_segmentation::UnicodeSegmentation;
20664 if self.input.is_empty() {
20665 return None;
20666 }
20667
20668 let mut iter = self.input.graphemes(true).peekable();
20669 let mut offset = 0;
20670 let mut grapheme_len = 0;
20671 if let Some(first_grapheme) = iter.next() {
20672 let is_newline = first_grapheme == "\n";
20673 let is_whitespace = is_grapheme_whitespace(first_grapheme);
20674 offset += first_grapheme.len();
20675 grapheme_len += 1;
20676 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
20677 if let Some(grapheme) = iter.peek().copied() {
20678 if should_stay_with_preceding_ideograph(grapheme) {
20679 offset += grapheme.len();
20680 grapheme_len += 1;
20681 }
20682 }
20683 } else {
20684 let mut words = self.input[offset..].split_word_bound_indices().peekable();
20685 let mut next_word_bound = words.peek().copied();
20686 if next_word_bound.map_or(false, |(i, _)| i == 0) {
20687 next_word_bound = words.next();
20688 }
20689 while let Some(grapheme) = iter.peek().copied() {
20690 if next_word_bound.map_or(false, |(i, _)| i == offset) {
20691 break;
20692 };
20693 if is_grapheme_whitespace(grapheme) != is_whitespace
20694 || (grapheme == "\n") != is_newline
20695 {
20696 break;
20697 };
20698 offset += grapheme.len();
20699 grapheme_len += 1;
20700 iter.next();
20701 }
20702 }
20703 let token = &self.input[..offset];
20704 self.input = &self.input[offset..];
20705 if token == "\n" {
20706 Some(WordBreakToken::Newline)
20707 } else if is_whitespace {
20708 Some(WordBreakToken::InlineWhitespace {
20709 token,
20710 grapheme_len,
20711 })
20712 } else {
20713 Some(WordBreakToken::Word {
20714 token,
20715 grapheme_len,
20716 })
20717 }
20718 } else {
20719 None
20720 }
20721 }
20722}
20723
20724#[test]
20725fn test_word_breaking_tokenizer() {
20726 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
20727 ("", &[]),
20728 (" ", &[whitespace(" ", 2)]),
20729 ("Ʒ", &[word("Ʒ", 1)]),
20730 ("Ǽ", &[word("Ǽ", 1)]),
20731 ("⋑", &[word("⋑", 1)]),
20732 ("⋑⋑", &[word("⋑⋑", 2)]),
20733 (
20734 "原理,进而",
20735 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
20736 ),
20737 (
20738 "hello world",
20739 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
20740 ),
20741 (
20742 "hello, world",
20743 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
20744 ),
20745 (
20746 " hello world",
20747 &[
20748 whitespace(" ", 2),
20749 word("hello", 5),
20750 whitespace(" ", 1),
20751 word("world", 5),
20752 ],
20753 ),
20754 (
20755 "这是什么 \n 钢笔",
20756 &[
20757 word("这", 1),
20758 word("是", 1),
20759 word("什", 1),
20760 word("么", 1),
20761 whitespace(" ", 1),
20762 newline(),
20763 whitespace(" ", 1),
20764 word("钢", 1),
20765 word("笔", 1),
20766 ],
20767 ),
20768 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
20769 ];
20770
20771 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20772 WordBreakToken::Word {
20773 token,
20774 grapheme_len,
20775 }
20776 }
20777
20778 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20779 WordBreakToken::InlineWhitespace {
20780 token,
20781 grapheme_len,
20782 }
20783 }
20784
20785 fn newline() -> WordBreakToken<'static> {
20786 WordBreakToken::Newline
20787 }
20788
20789 for (input, result) in tests {
20790 assert_eq!(
20791 WordBreakingTokenizer::new(input)
20792 .collect::<Vec<_>>()
20793 .as_slice(),
20794 *result,
20795 );
20796 }
20797}
20798
20799fn wrap_with_prefix(
20800 line_prefix: String,
20801 unwrapped_text: String,
20802 wrap_column: usize,
20803 tab_size: NonZeroU32,
20804 preserve_existing_whitespace: bool,
20805) -> String {
20806 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
20807 let mut wrapped_text = String::new();
20808 let mut current_line = line_prefix.clone();
20809
20810 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
20811 let mut current_line_len = line_prefix_len;
20812 let mut in_whitespace = false;
20813 for token in tokenizer {
20814 let have_preceding_whitespace = in_whitespace;
20815 match token {
20816 WordBreakToken::Word {
20817 token,
20818 grapheme_len,
20819 } => {
20820 in_whitespace = false;
20821 if current_line_len + grapheme_len > wrap_column
20822 && current_line_len != line_prefix_len
20823 {
20824 wrapped_text.push_str(current_line.trim_end());
20825 wrapped_text.push('\n');
20826 current_line.truncate(line_prefix.len());
20827 current_line_len = line_prefix_len;
20828 }
20829 current_line.push_str(token);
20830 current_line_len += grapheme_len;
20831 }
20832 WordBreakToken::InlineWhitespace {
20833 mut token,
20834 mut grapheme_len,
20835 } => {
20836 in_whitespace = true;
20837 if have_preceding_whitespace && !preserve_existing_whitespace {
20838 continue;
20839 }
20840 if !preserve_existing_whitespace {
20841 token = " ";
20842 grapheme_len = 1;
20843 }
20844 if current_line_len + grapheme_len > wrap_column {
20845 wrapped_text.push_str(current_line.trim_end());
20846 wrapped_text.push('\n');
20847 current_line.truncate(line_prefix.len());
20848 current_line_len = line_prefix_len;
20849 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
20850 current_line.push_str(token);
20851 current_line_len += grapheme_len;
20852 }
20853 }
20854 WordBreakToken::Newline => {
20855 in_whitespace = true;
20856 if preserve_existing_whitespace {
20857 wrapped_text.push_str(current_line.trim_end());
20858 wrapped_text.push('\n');
20859 current_line.truncate(line_prefix.len());
20860 current_line_len = line_prefix_len;
20861 } else if have_preceding_whitespace {
20862 continue;
20863 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
20864 {
20865 wrapped_text.push_str(current_line.trim_end());
20866 wrapped_text.push('\n');
20867 current_line.truncate(line_prefix.len());
20868 current_line_len = line_prefix_len;
20869 } else if current_line_len != line_prefix_len {
20870 current_line.push(' ');
20871 current_line_len += 1;
20872 }
20873 }
20874 }
20875 }
20876
20877 if !current_line.is_empty() {
20878 wrapped_text.push_str(¤t_line);
20879 }
20880 wrapped_text
20881}
20882
20883#[test]
20884fn test_wrap_with_prefix() {
20885 assert_eq!(
20886 wrap_with_prefix(
20887 "# ".to_string(),
20888 "abcdefg".to_string(),
20889 4,
20890 NonZeroU32::new(4).unwrap(),
20891 false,
20892 ),
20893 "# abcdefg"
20894 );
20895 assert_eq!(
20896 wrap_with_prefix(
20897 "".to_string(),
20898 "\thello world".to_string(),
20899 8,
20900 NonZeroU32::new(4).unwrap(),
20901 false,
20902 ),
20903 "hello\nworld"
20904 );
20905 assert_eq!(
20906 wrap_with_prefix(
20907 "// ".to_string(),
20908 "xx \nyy zz aa bb cc".to_string(),
20909 12,
20910 NonZeroU32::new(4).unwrap(),
20911 false,
20912 ),
20913 "// xx yy zz\n// aa bb cc"
20914 );
20915 assert_eq!(
20916 wrap_with_prefix(
20917 String::new(),
20918 "这是什么 \n 钢笔".to_string(),
20919 3,
20920 NonZeroU32::new(4).unwrap(),
20921 false,
20922 ),
20923 "这是什\n么 钢\n笔"
20924 );
20925}
20926
20927pub trait CollaborationHub {
20928 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
20929 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
20930 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
20931}
20932
20933impl CollaborationHub for Entity<Project> {
20934 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
20935 self.read(cx).collaborators()
20936 }
20937
20938 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
20939 self.read(cx).user_store().read(cx).participant_indices()
20940 }
20941
20942 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
20943 let this = self.read(cx);
20944 let user_ids = this.collaborators().values().map(|c| c.user_id);
20945 this.user_store().read(cx).participant_names(user_ids, cx)
20946 }
20947}
20948
20949pub trait SemanticsProvider {
20950 fn hover(
20951 &self,
20952 buffer: &Entity<Buffer>,
20953 position: text::Anchor,
20954 cx: &mut App,
20955 ) -> Option<Task<Vec<project::Hover>>>;
20956
20957 fn inline_values(
20958 &self,
20959 buffer_handle: Entity<Buffer>,
20960 range: Range<text::Anchor>,
20961 cx: &mut App,
20962 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20963
20964 fn inlay_hints(
20965 &self,
20966 buffer_handle: Entity<Buffer>,
20967 range: Range<text::Anchor>,
20968 cx: &mut App,
20969 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20970
20971 fn resolve_inlay_hint(
20972 &self,
20973 hint: InlayHint,
20974 buffer_handle: Entity<Buffer>,
20975 server_id: LanguageServerId,
20976 cx: &mut App,
20977 ) -> Option<Task<anyhow::Result<InlayHint>>>;
20978
20979 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
20980
20981 fn document_highlights(
20982 &self,
20983 buffer: &Entity<Buffer>,
20984 position: text::Anchor,
20985 cx: &mut App,
20986 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
20987
20988 fn definitions(
20989 &self,
20990 buffer: &Entity<Buffer>,
20991 position: text::Anchor,
20992 kind: GotoDefinitionKind,
20993 cx: &mut App,
20994 ) -> Option<Task<Result<Vec<LocationLink>>>>;
20995
20996 fn range_for_rename(
20997 &self,
20998 buffer: &Entity<Buffer>,
20999 position: text::Anchor,
21000 cx: &mut App,
21001 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21002
21003 fn perform_rename(
21004 &self,
21005 buffer: &Entity<Buffer>,
21006 position: text::Anchor,
21007 new_name: String,
21008 cx: &mut App,
21009 ) -> Option<Task<Result<ProjectTransaction>>>;
21010}
21011
21012pub trait CompletionProvider {
21013 fn completions(
21014 &self,
21015 excerpt_id: ExcerptId,
21016 buffer: &Entity<Buffer>,
21017 buffer_position: text::Anchor,
21018 trigger: CompletionContext,
21019 window: &mut Window,
21020 cx: &mut Context<Editor>,
21021 ) -> Task<Result<Vec<CompletionResponse>>>;
21022
21023 fn resolve_completions(
21024 &self,
21025 _buffer: Entity<Buffer>,
21026 _completion_indices: Vec<usize>,
21027 _completions: Rc<RefCell<Box<[Completion]>>>,
21028 _cx: &mut Context<Editor>,
21029 ) -> Task<Result<bool>> {
21030 Task::ready(Ok(false))
21031 }
21032
21033 fn apply_additional_edits_for_completion(
21034 &self,
21035 _buffer: Entity<Buffer>,
21036 _completions: Rc<RefCell<Box<[Completion]>>>,
21037 _completion_index: usize,
21038 _push_to_history: bool,
21039 _cx: &mut Context<Editor>,
21040 ) -> Task<Result<Option<language::Transaction>>> {
21041 Task::ready(Ok(None))
21042 }
21043
21044 fn is_completion_trigger(
21045 &self,
21046 buffer: &Entity<Buffer>,
21047 position: language::Anchor,
21048 text: &str,
21049 trigger_in_words: bool,
21050 menu_is_open: bool,
21051 cx: &mut Context<Editor>,
21052 ) -> bool;
21053
21054 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21055
21056 fn sort_completions(&self) -> bool {
21057 true
21058 }
21059
21060 fn filter_completions(&self) -> bool {
21061 true
21062 }
21063}
21064
21065pub trait CodeActionProvider {
21066 fn id(&self) -> Arc<str>;
21067
21068 fn code_actions(
21069 &self,
21070 buffer: &Entity<Buffer>,
21071 range: Range<text::Anchor>,
21072 window: &mut Window,
21073 cx: &mut App,
21074 ) -> Task<Result<Vec<CodeAction>>>;
21075
21076 fn apply_code_action(
21077 &self,
21078 buffer_handle: Entity<Buffer>,
21079 action: CodeAction,
21080 excerpt_id: ExcerptId,
21081 push_to_history: bool,
21082 window: &mut Window,
21083 cx: &mut App,
21084 ) -> Task<Result<ProjectTransaction>>;
21085}
21086
21087impl CodeActionProvider for Entity<Project> {
21088 fn id(&self) -> Arc<str> {
21089 "project".into()
21090 }
21091
21092 fn code_actions(
21093 &self,
21094 buffer: &Entity<Buffer>,
21095 range: Range<text::Anchor>,
21096 _window: &mut Window,
21097 cx: &mut App,
21098 ) -> Task<Result<Vec<CodeAction>>> {
21099 self.update(cx, |project, cx| {
21100 let code_lens = project.code_lens(buffer, range.clone(), cx);
21101 let code_actions = project.code_actions(buffer, range, None, cx);
21102 cx.background_spawn(async move {
21103 let (code_lens, code_actions) = join(code_lens, code_actions).await;
21104 Ok(code_lens
21105 .context("code lens fetch")?
21106 .into_iter()
21107 .chain(code_actions.context("code action fetch")?)
21108 .collect())
21109 })
21110 })
21111 }
21112
21113 fn apply_code_action(
21114 &self,
21115 buffer_handle: Entity<Buffer>,
21116 action: CodeAction,
21117 _excerpt_id: ExcerptId,
21118 push_to_history: bool,
21119 _window: &mut Window,
21120 cx: &mut App,
21121 ) -> Task<Result<ProjectTransaction>> {
21122 self.update(cx, |project, cx| {
21123 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21124 })
21125 }
21126}
21127
21128fn snippet_completions(
21129 project: &Project,
21130 buffer: &Entity<Buffer>,
21131 buffer_position: text::Anchor,
21132 cx: &mut App,
21133) -> Task<Result<CompletionResponse>> {
21134 let languages = buffer.read(cx).languages_at(buffer_position);
21135 let snippet_store = project.snippets().read(cx);
21136
21137 let scopes: Vec<_> = languages
21138 .iter()
21139 .filter_map(|language| {
21140 let language_name = language.lsp_id();
21141 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21142
21143 if snippets.is_empty() {
21144 None
21145 } else {
21146 Some((language.default_scope(), snippets))
21147 }
21148 })
21149 .collect();
21150
21151 if scopes.is_empty() {
21152 return Task::ready(Ok(CompletionResponse {
21153 completions: vec![],
21154 is_incomplete: false,
21155 }));
21156 }
21157
21158 let snapshot = buffer.read(cx).text_snapshot();
21159 let chars: String = snapshot
21160 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
21161 .collect();
21162 let executor = cx.background_executor().clone();
21163
21164 cx.background_spawn(async move {
21165 let mut is_incomplete = false;
21166 let mut completions: Vec<Completion> = Vec::new();
21167 for (scope, snippets) in scopes.into_iter() {
21168 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
21169 let mut last_word = chars
21170 .chars()
21171 .take_while(|c| classifier.is_word(*c))
21172 .collect::<String>();
21173 last_word = last_word.chars().rev().collect();
21174
21175 if last_word.is_empty() {
21176 return Ok(CompletionResponse {
21177 completions: vec![],
21178 is_incomplete: true,
21179 });
21180 }
21181
21182 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
21183 let to_lsp = |point: &text::Anchor| {
21184 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
21185 point_to_lsp(end)
21186 };
21187 let lsp_end = to_lsp(&buffer_position);
21188
21189 let candidates = snippets
21190 .iter()
21191 .enumerate()
21192 .flat_map(|(ix, snippet)| {
21193 snippet
21194 .prefix
21195 .iter()
21196 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21197 })
21198 .collect::<Vec<StringMatchCandidate>>();
21199
21200 const MAX_RESULTS: usize = 100;
21201 let mut matches = fuzzy::match_strings(
21202 &candidates,
21203 &last_word,
21204 last_word.chars().any(|c| c.is_uppercase()),
21205 MAX_RESULTS,
21206 &Default::default(),
21207 executor.clone(),
21208 )
21209 .await;
21210
21211 if matches.len() >= MAX_RESULTS {
21212 is_incomplete = true;
21213 }
21214
21215 // Remove all candidates where the query's start does not match the start of any word in the candidate
21216 if let Some(query_start) = last_word.chars().next() {
21217 matches.retain(|string_match| {
21218 split_words(&string_match.string).any(|word| {
21219 // Check that the first codepoint of the word as lowercase matches the first
21220 // codepoint of the query as lowercase
21221 word.chars()
21222 .flat_map(|codepoint| codepoint.to_lowercase())
21223 .zip(query_start.to_lowercase())
21224 .all(|(word_cp, query_cp)| word_cp == query_cp)
21225 })
21226 });
21227 }
21228
21229 let matched_strings = matches
21230 .into_iter()
21231 .map(|m| m.string)
21232 .collect::<HashSet<_>>();
21233
21234 completions.extend(snippets.iter().filter_map(|snippet| {
21235 let matching_prefix = snippet
21236 .prefix
21237 .iter()
21238 .find(|prefix| matched_strings.contains(*prefix))?;
21239 let start = as_offset - last_word.len();
21240 let start = snapshot.anchor_before(start);
21241 let range = start..buffer_position;
21242 let lsp_start = to_lsp(&start);
21243 let lsp_range = lsp::Range {
21244 start: lsp_start,
21245 end: lsp_end,
21246 };
21247 Some(Completion {
21248 replace_range: range,
21249 new_text: snippet.body.clone(),
21250 source: CompletionSource::Lsp {
21251 insert_range: None,
21252 server_id: LanguageServerId(usize::MAX),
21253 resolved: true,
21254 lsp_completion: Box::new(lsp::CompletionItem {
21255 label: snippet.prefix.first().unwrap().clone(),
21256 kind: Some(CompletionItemKind::SNIPPET),
21257 label_details: snippet.description.as_ref().map(|description| {
21258 lsp::CompletionItemLabelDetails {
21259 detail: Some(description.clone()),
21260 description: None,
21261 }
21262 }),
21263 insert_text_format: Some(InsertTextFormat::SNIPPET),
21264 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21265 lsp::InsertReplaceEdit {
21266 new_text: snippet.body.clone(),
21267 insert: lsp_range,
21268 replace: lsp_range,
21269 },
21270 )),
21271 filter_text: Some(snippet.body.clone()),
21272 sort_text: Some(char::MAX.to_string()),
21273 ..lsp::CompletionItem::default()
21274 }),
21275 lsp_defaults: None,
21276 },
21277 label: CodeLabel {
21278 text: matching_prefix.clone(),
21279 runs: Vec::new(),
21280 filter_range: 0..matching_prefix.len(),
21281 },
21282 icon_path: None,
21283 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
21284 single_line: snippet.name.clone().into(),
21285 plain_text: snippet
21286 .description
21287 .clone()
21288 .map(|description| description.into()),
21289 }),
21290 insert_text_mode: None,
21291 confirm: None,
21292 })
21293 }))
21294 }
21295
21296 Ok(CompletionResponse {
21297 completions,
21298 is_incomplete,
21299 })
21300 })
21301}
21302
21303impl CompletionProvider for Entity<Project> {
21304 fn completions(
21305 &self,
21306 _excerpt_id: ExcerptId,
21307 buffer: &Entity<Buffer>,
21308 buffer_position: text::Anchor,
21309 options: CompletionContext,
21310 _window: &mut Window,
21311 cx: &mut Context<Editor>,
21312 ) -> Task<Result<Vec<CompletionResponse>>> {
21313 self.update(cx, |project, cx| {
21314 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21315 let project_completions = project.completions(buffer, buffer_position, options, cx);
21316 cx.background_spawn(async move {
21317 let mut responses = project_completions.await?;
21318 let snippets = snippets.await?;
21319 if !snippets.completions.is_empty() {
21320 responses.push(snippets);
21321 }
21322 Ok(responses)
21323 })
21324 })
21325 }
21326
21327 fn resolve_completions(
21328 &self,
21329 buffer: Entity<Buffer>,
21330 completion_indices: Vec<usize>,
21331 completions: Rc<RefCell<Box<[Completion]>>>,
21332 cx: &mut Context<Editor>,
21333 ) -> Task<Result<bool>> {
21334 self.update(cx, |project, cx| {
21335 project.lsp_store().update(cx, |lsp_store, cx| {
21336 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21337 })
21338 })
21339 }
21340
21341 fn apply_additional_edits_for_completion(
21342 &self,
21343 buffer: Entity<Buffer>,
21344 completions: Rc<RefCell<Box<[Completion]>>>,
21345 completion_index: usize,
21346 push_to_history: bool,
21347 cx: &mut Context<Editor>,
21348 ) -> Task<Result<Option<language::Transaction>>> {
21349 self.update(cx, |project, cx| {
21350 project.lsp_store().update(cx, |lsp_store, cx| {
21351 lsp_store.apply_additional_edits_for_completion(
21352 buffer,
21353 completions,
21354 completion_index,
21355 push_to_history,
21356 cx,
21357 )
21358 })
21359 })
21360 }
21361
21362 fn is_completion_trigger(
21363 &self,
21364 buffer: &Entity<Buffer>,
21365 position: language::Anchor,
21366 text: &str,
21367 trigger_in_words: bool,
21368 menu_is_open: bool,
21369 cx: &mut Context<Editor>,
21370 ) -> bool {
21371 let mut chars = text.chars();
21372 let char = if let Some(char) = chars.next() {
21373 char
21374 } else {
21375 return false;
21376 };
21377 if chars.next().is_some() {
21378 return false;
21379 }
21380
21381 let buffer = buffer.read(cx);
21382 let snapshot = buffer.snapshot();
21383 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
21384 return false;
21385 }
21386 let classifier = snapshot.char_classifier_at(position).for_completion(true);
21387 if trigger_in_words && classifier.is_word(char) {
21388 return true;
21389 }
21390
21391 buffer.completion_triggers().contains(text)
21392 }
21393}
21394
21395impl SemanticsProvider for Entity<Project> {
21396 fn hover(
21397 &self,
21398 buffer: &Entity<Buffer>,
21399 position: text::Anchor,
21400 cx: &mut App,
21401 ) -> Option<Task<Vec<project::Hover>>> {
21402 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
21403 }
21404
21405 fn document_highlights(
21406 &self,
21407 buffer: &Entity<Buffer>,
21408 position: text::Anchor,
21409 cx: &mut App,
21410 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
21411 Some(self.update(cx, |project, cx| {
21412 project.document_highlights(buffer, position, cx)
21413 }))
21414 }
21415
21416 fn definitions(
21417 &self,
21418 buffer: &Entity<Buffer>,
21419 position: text::Anchor,
21420 kind: GotoDefinitionKind,
21421 cx: &mut App,
21422 ) -> Option<Task<Result<Vec<LocationLink>>>> {
21423 Some(self.update(cx, |project, cx| match kind {
21424 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
21425 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
21426 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
21427 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
21428 }))
21429 }
21430
21431 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
21432 // TODO: make this work for remote projects
21433 self.update(cx, |project, cx| {
21434 if project
21435 .active_debug_session(cx)
21436 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
21437 {
21438 return true;
21439 }
21440
21441 buffer.update(cx, |buffer, cx| {
21442 project.any_language_server_supports_inlay_hints(buffer, cx)
21443 })
21444 })
21445 }
21446
21447 fn inline_values(
21448 &self,
21449 buffer_handle: Entity<Buffer>,
21450
21451 range: Range<text::Anchor>,
21452 cx: &mut App,
21453 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21454 self.update(cx, |project, cx| {
21455 let (session, active_stack_frame) = project.active_debug_session(cx)?;
21456
21457 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
21458 })
21459 }
21460
21461 fn inlay_hints(
21462 &self,
21463 buffer_handle: Entity<Buffer>,
21464 range: Range<text::Anchor>,
21465 cx: &mut App,
21466 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21467 Some(self.update(cx, |project, cx| {
21468 project.inlay_hints(buffer_handle, range, cx)
21469 }))
21470 }
21471
21472 fn resolve_inlay_hint(
21473 &self,
21474 hint: InlayHint,
21475 buffer_handle: Entity<Buffer>,
21476 server_id: LanguageServerId,
21477 cx: &mut App,
21478 ) -> Option<Task<anyhow::Result<InlayHint>>> {
21479 Some(self.update(cx, |project, cx| {
21480 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
21481 }))
21482 }
21483
21484 fn range_for_rename(
21485 &self,
21486 buffer: &Entity<Buffer>,
21487 position: text::Anchor,
21488 cx: &mut App,
21489 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
21490 Some(self.update(cx, |project, cx| {
21491 let buffer = buffer.clone();
21492 let task = project.prepare_rename(buffer.clone(), position, cx);
21493 cx.spawn(async move |_, cx| {
21494 Ok(match task.await? {
21495 PrepareRenameResponse::Success(range) => Some(range),
21496 PrepareRenameResponse::InvalidPosition => None,
21497 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
21498 // Fallback on using TreeSitter info to determine identifier range
21499 buffer.read_with(cx, |buffer, _| {
21500 let snapshot = buffer.snapshot();
21501 let (range, kind) = snapshot.surrounding_word(position);
21502 if kind != Some(CharKind::Word) {
21503 return None;
21504 }
21505 Some(
21506 snapshot.anchor_before(range.start)
21507 ..snapshot.anchor_after(range.end),
21508 )
21509 })?
21510 }
21511 })
21512 })
21513 }))
21514 }
21515
21516 fn perform_rename(
21517 &self,
21518 buffer: &Entity<Buffer>,
21519 position: text::Anchor,
21520 new_name: String,
21521 cx: &mut App,
21522 ) -> Option<Task<Result<ProjectTransaction>>> {
21523 Some(self.update(cx, |project, cx| {
21524 project.perform_rename(buffer.clone(), position, new_name, cx)
21525 }))
21526 }
21527}
21528
21529fn inlay_hint_settings(
21530 location: Anchor,
21531 snapshot: &MultiBufferSnapshot,
21532 cx: &mut Context<Editor>,
21533) -> InlayHintSettings {
21534 let file = snapshot.file_at(location);
21535 let language = snapshot.language_at(location).map(|l| l.name());
21536 language_settings(language, file, cx).inlay_hints
21537}
21538
21539fn consume_contiguous_rows(
21540 contiguous_row_selections: &mut Vec<Selection<Point>>,
21541 selection: &Selection<Point>,
21542 display_map: &DisplaySnapshot,
21543 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
21544) -> (MultiBufferRow, MultiBufferRow) {
21545 contiguous_row_selections.push(selection.clone());
21546 let start_row = MultiBufferRow(selection.start.row);
21547 let mut end_row = ending_row(selection, display_map);
21548
21549 while let Some(next_selection) = selections.peek() {
21550 if next_selection.start.row <= end_row.0 {
21551 end_row = ending_row(next_selection, display_map);
21552 contiguous_row_selections.push(selections.next().unwrap().clone());
21553 } else {
21554 break;
21555 }
21556 }
21557 (start_row, end_row)
21558}
21559
21560fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
21561 if next_selection.end.column > 0 || next_selection.is_empty() {
21562 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
21563 } else {
21564 MultiBufferRow(next_selection.end.row)
21565 }
21566}
21567
21568impl EditorSnapshot {
21569 pub fn remote_selections_in_range<'a>(
21570 &'a self,
21571 range: &'a Range<Anchor>,
21572 collaboration_hub: &dyn CollaborationHub,
21573 cx: &'a App,
21574 ) -> impl 'a + Iterator<Item = RemoteSelection> {
21575 let participant_names = collaboration_hub.user_names(cx);
21576 let participant_indices = collaboration_hub.user_participant_indices(cx);
21577 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
21578 let collaborators_by_replica_id = collaborators_by_peer_id
21579 .values()
21580 .map(|collaborator| (collaborator.replica_id, collaborator))
21581 .collect::<HashMap<_, _>>();
21582 self.buffer_snapshot
21583 .selections_in_range(range, false)
21584 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
21585 if replica_id == AGENT_REPLICA_ID {
21586 Some(RemoteSelection {
21587 replica_id,
21588 selection,
21589 cursor_shape,
21590 line_mode,
21591 collaborator_id: CollaboratorId::Agent,
21592 user_name: Some("Agent".into()),
21593 color: cx.theme().players().agent(),
21594 })
21595 } else {
21596 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
21597 let participant_index = participant_indices.get(&collaborator.user_id).copied();
21598 let user_name = participant_names.get(&collaborator.user_id).cloned();
21599 Some(RemoteSelection {
21600 replica_id,
21601 selection,
21602 cursor_shape,
21603 line_mode,
21604 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
21605 user_name,
21606 color: if let Some(index) = participant_index {
21607 cx.theme().players().color_for_participant(index.0)
21608 } else {
21609 cx.theme().players().absent()
21610 },
21611 })
21612 }
21613 })
21614 }
21615
21616 pub fn hunks_for_ranges(
21617 &self,
21618 ranges: impl IntoIterator<Item = Range<Point>>,
21619 ) -> Vec<MultiBufferDiffHunk> {
21620 let mut hunks = Vec::new();
21621 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
21622 HashMap::default();
21623 for query_range in ranges {
21624 let query_rows =
21625 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
21626 for hunk in self.buffer_snapshot.diff_hunks_in_range(
21627 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
21628 ) {
21629 // Include deleted hunks that are adjacent to the query range, because
21630 // otherwise they would be missed.
21631 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
21632 if hunk.status().is_deleted() {
21633 intersects_range |= hunk.row_range.start == query_rows.end;
21634 intersects_range |= hunk.row_range.end == query_rows.start;
21635 }
21636 if intersects_range {
21637 if !processed_buffer_rows
21638 .entry(hunk.buffer_id)
21639 .or_default()
21640 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
21641 {
21642 continue;
21643 }
21644 hunks.push(hunk);
21645 }
21646 }
21647 }
21648
21649 hunks
21650 }
21651
21652 fn display_diff_hunks_for_rows<'a>(
21653 &'a self,
21654 display_rows: Range<DisplayRow>,
21655 folded_buffers: &'a HashSet<BufferId>,
21656 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
21657 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
21658 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
21659
21660 self.buffer_snapshot
21661 .diff_hunks_in_range(buffer_start..buffer_end)
21662 .filter_map(|hunk| {
21663 if folded_buffers.contains(&hunk.buffer_id) {
21664 return None;
21665 }
21666
21667 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
21668 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
21669
21670 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
21671 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
21672
21673 let display_hunk = if hunk_display_start.column() != 0 {
21674 DisplayDiffHunk::Folded {
21675 display_row: hunk_display_start.row(),
21676 }
21677 } else {
21678 let mut end_row = hunk_display_end.row();
21679 if hunk_display_end.column() > 0 {
21680 end_row.0 += 1;
21681 }
21682 let is_created_file = hunk.is_created_file();
21683 DisplayDiffHunk::Unfolded {
21684 status: hunk.status(),
21685 diff_base_byte_range: hunk.diff_base_byte_range,
21686 display_row_range: hunk_display_start.row()..end_row,
21687 multi_buffer_range: Anchor::range_in_buffer(
21688 hunk.excerpt_id,
21689 hunk.buffer_id,
21690 hunk.buffer_range,
21691 ),
21692 is_created_file,
21693 }
21694 };
21695
21696 Some(display_hunk)
21697 })
21698 }
21699
21700 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
21701 self.display_snapshot.buffer_snapshot.language_at(position)
21702 }
21703
21704 pub fn is_focused(&self) -> bool {
21705 self.is_focused
21706 }
21707
21708 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
21709 self.placeholder_text.as_ref()
21710 }
21711
21712 pub fn scroll_position(&self) -> gpui::Point<f32> {
21713 self.scroll_anchor.scroll_position(&self.display_snapshot)
21714 }
21715
21716 fn gutter_dimensions(
21717 &self,
21718 font_id: FontId,
21719 font_size: Pixels,
21720 max_line_number_width: Pixels,
21721 cx: &App,
21722 ) -> Option<GutterDimensions> {
21723 if !self.show_gutter {
21724 return None;
21725 }
21726
21727 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
21728 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
21729
21730 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
21731 matches!(
21732 ProjectSettings::get_global(cx).git.git_gutter,
21733 Some(GitGutterSetting::TrackedFiles)
21734 )
21735 });
21736 let gutter_settings = EditorSettings::get_global(cx).gutter;
21737 let show_line_numbers = self
21738 .show_line_numbers
21739 .unwrap_or(gutter_settings.line_numbers);
21740 let line_gutter_width = if show_line_numbers {
21741 // Avoid flicker-like gutter resizes when the line number gains another digit by
21742 // only resizing the gutter on files with > 10**min_line_number_digits lines.
21743 let min_width_for_number_on_gutter =
21744 ch_advance * gutter_settings.min_line_number_digits as f32;
21745 max_line_number_width.max(min_width_for_number_on_gutter)
21746 } else {
21747 0.0.into()
21748 };
21749
21750 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
21751 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
21752
21753 let git_blame_entries_width =
21754 self.git_blame_gutter_max_author_length
21755 .map(|max_author_length| {
21756 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
21757 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
21758
21759 /// The number of characters to dedicate to gaps and margins.
21760 const SPACING_WIDTH: usize = 4;
21761
21762 let max_char_count = max_author_length.min(renderer.max_author_length())
21763 + ::git::SHORT_SHA_LENGTH
21764 + MAX_RELATIVE_TIMESTAMP.len()
21765 + SPACING_WIDTH;
21766
21767 ch_advance * max_char_count
21768 });
21769
21770 let is_singleton = self.buffer_snapshot.is_singleton();
21771
21772 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
21773 left_padding += if !is_singleton {
21774 ch_width * 4.0
21775 } else if show_runnables || show_breakpoints {
21776 ch_width * 3.0
21777 } else if show_git_gutter && show_line_numbers {
21778 ch_width * 2.0
21779 } else if show_git_gutter || show_line_numbers {
21780 ch_width
21781 } else {
21782 px(0.)
21783 };
21784
21785 let shows_folds = is_singleton && gutter_settings.folds;
21786
21787 let right_padding = if shows_folds && show_line_numbers {
21788 ch_width * 4.0
21789 } else if shows_folds || (!is_singleton && show_line_numbers) {
21790 ch_width * 3.0
21791 } else if show_line_numbers {
21792 ch_width
21793 } else {
21794 px(0.)
21795 };
21796
21797 Some(GutterDimensions {
21798 left_padding,
21799 right_padding,
21800 width: line_gutter_width + left_padding + right_padding,
21801 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
21802 git_blame_entries_width,
21803 })
21804 }
21805
21806 pub fn render_crease_toggle(
21807 &self,
21808 buffer_row: MultiBufferRow,
21809 row_contains_cursor: bool,
21810 editor: Entity<Editor>,
21811 window: &mut Window,
21812 cx: &mut App,
21813 ) -> Option<AnyElement> {
21814 let folded = self.is_line_folded(buffer_row);
21815 let mut is_foldable = false;
21816
21817 if let Some(crease) = self
21818 .crease_snapshot
21819 .query_row(buffer_row, &self.buffer_snapshot)
21820 {
21821 is_foldable = true;
21822 match crease {
21823 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
21824 if let Some(render_toggle) = render_toggle {
21825 let toggle_callback =
21826 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
21827 if folded {
21828 editor.update(cx, |editor, cx| {
21829 editor.fold_at(buffer_row, window, cx)
21830 });
21831 } else {
21832 editor.update(cx, |editor, cx| {
21833 editor.unfold_at(buffer_row, window, cx)
21834 });
21835 }
21836 });
21837 return Some((render_toggle)(
21838 buffer_row,
21839 folded,
21840 toggle_callback,
21841 window,
21842 cx,
21843 ));
21844 }
21845 }
21846 }
21847 }
21848
21849 is_foldable |= self.starts_indent(buffer_row);
21850
21851 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
21852 Some(
21853 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
21854 .toggle_state(folded)
21855 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
21856 if folded {
21857 this.unfold_at(buffer_row, window, cx);
21858 } else {
21859 this.fold_at(buffer_row, window, cx);
21860 }
21861 }))
21862 .into_any_element(),
21863 )
21864 } else {
21865 None
21866 }
21867 }
21868
21869 pub fn render_crease_trailer(
21870 &self,
21871 buffer_row: MultiBufferRow,
21872 window: &mut Window,
21873 cx: &mut App,
21874 ) -> Option<AnyElement> {
21875 let folded = self.is_line_folded(buffer_row);
21876 if let Crease::Inline { render_trailer, .. } = self
21877 .crease_snapshot
21878 .query_row(buffer_row, &self.buffer_snapshot)?
21879 {
21880 let render_trailer = render_trailer.as_ref()?;
21881 Some(render_trailer(buffer_row, folded, window, cx))
21882 } else {
21883 None
21884 }
21885 }
21886}
21887
21888impl Deref for EditorSnapshot {
21889 type Target = DisplaySnapshot;
21890
21891 fn deref(&self) -> &Self::Target {
21892 &self.display_snapshot
21893 }
21894}
21895
21896#[derive(Clone, Debug, PartialEq, Eq)]
21897pub enum EditorEvent {
21898 InputIgnored {
21899 text: Arc<str>,
21900 },
21901 InputHandled {
21902 utf16_range_to_replace: Option<Range<isize>>,
21903 text: Arc<str>,
21904 },
21905 ExcerptsAdded {
21906 buffer: Entity<Buffer>,
21907 predecessor: ExcerptId,
21908 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
21909 },
21910 ExcerptsRemoved {
21911 ids: Vec<ExcerptId>,
21912 removed_buffer_ids: Vec<BufferId>,
21913 },
21914 BufferFoldToggled {
21915 ids: Vec<ExcerptId>,
21916 folded: bool,
21917 },
21918 ExcerptsEdited {
21919 ids: Vec<ExcerptId>,
21920 },
21921 ExcerptsExpanded {
21922 ids: Vec<ExcerptId>,
21923 },
21924 BufferEdited,
21925 Edited {
21926 transaction_id: clock::Lamport,
21927 },
21928 Reparsed(BufferId),
21929 Focused,
21930 FocusedIn,
21931 Blurred,
21932 DirtyChanged,
21933 Saved,
21934 TitleChanged,
21935 DiffBaseChanged,
21936 SelectionsChanged {
21937 local: bool,
21938 },
21939 ScrollPositionChanged {
21940 local: bool,
21941 autoscroll: bool,
21942 },
21943 Closed,
21944 TransactionUndone {
21945 transaction_id: clock::Lamport,
21946 },
21947 TransactionBegun {
21948 transaction_id: clock::Lamport,
21949 },
21950 Reloaded,
21951 CursorShapeChanged,
21952 PushedToNavHistory {
21953 anchor: Anchor,
21954 is_deactivate: bool,
21955 },
21956}
21957
21958impl EventEmitter<EditorEvent> for Editor {}
21959
21960impl Focusable for Editor {
21961 fn focus_handle(&self, _cx: &App) -> FocusHandle {
21962 self.focus_handle.clone()
21963 }
21964}
21965
21966impl Render for Editor {
21967 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21968 let settings = ThemeSettings::get_global(cx);
21969
21970 let mut text_style = match self.mode {
21971 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
21972 color: cx.theme().colors().editor_foreground,
21973 font_family: settings.ui_font.family.clone(),
21974 font_features: settings.ui_font.features.clone(),
21975 font_fallbacks: settings.ui_font.fallbacks.clone(),
21976 font_size: rems(0.875).into(),
21977 font_weight: settings.ui_font.weight,
21978 line_height: relative(settings.buffer_line_height.value()),
21979 ..Default::default()
21980 },
21981 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
21982 color: cx.theme().colors().editor_foreground,
21983 font_family: settings.buffer_font.family.clone(),
21984 font_features: settings.buffer_font.features.clone(),
21985 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21986 font_size: settings.buffer_font_size(cx).into(),
21987 font_weight: settings.buffer_font.weight,
21988 line_height: relative(settings.buffer_line_height.value()),
21989 ..Default::default()
21990 },
21991 };
21992 if let Some(text_style_refinement) = &self.text_style_refinement {
21993 text_style.refine(text_style_refinement)
21994 }
21995
21996 let background = match self.mode {
21997 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
21998 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
21999 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22000 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22001 };
22002
22003 EditorElement::new(
22004 &cx.entity(),
22005 EditorStyle {
22006 background,
22007 local_player: cx.theme().players().local(),
22008 text: text_style,
22009 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22010 syntax: cx.theme().syntax().clone(),
22011 status: cx.theme().status().clone(),
22012 inlay_hints_style: make_inlay_hints_style(cx),
22013 inline_completion_styles: make_suggestion_styles(cx),
22014 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22015 show_underlines: !self.mode.is_minimap(),
22016 },
22017 )
22018 }
22019}
22020
22021impl EntityInputHandler for Editor {
22022 fn text_for_range(
22023 &mut self,
22024 range_utf16: Range<usize>,
22025 adjusted_range: &mut Option<Range<usize>>,
22026 _: &mut Window,
22027 cx: &mut Context<Self>,
22028 ) -> Option<String> {
22029 let snapshot = self.buffer.read(cx).read(cx);
22030 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22031 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22032 if (start.0..end.0) != range_utf16 {
22033 adjusted_range.replace(start.0..end.0);
22034 }
22035 Some(snapshot.text_for_range(start..end).collect())
22036 }
22037
22038 fn selected_text_range(
22039 &mut self,
22040 ignore_disabled_input: bool,
22041 _: &mut Window,
22042 cx: &mut Context<Self>,
22043 ) -> Option<UTF16Selection> {
22044 // Prevent the IME menu from appearing when holding down an alphabetic key
22045 // while input is disabled.
22046 if !ignore_disabled_input && !self.input_enabled {
22047 return None;
22048 }
22049
22050 let selection = self.selections.newest::<OffsetUtf16>(cx);
22051 let range = selection.range();
22052
22053 Some(UTF16Selection {
22054 range: range.start.0..range.end.0,
22055 reversed: selection.reversed,
22056 })
22057 }
22058
22059 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22060 let snapshot = self.buffer.read(cx).read(cx);
22061 let (range, _) = self.text_highlights::<InputComposition>(cx)?.first()?;
22062 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22063 }
22064
22065 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22066 self.clear_highlights::<InputComposition>(cx);
22067 self.ime_transaction.take();
22068 }
22069
22070 fn replace_text_in_range(
22071 &mut self,
22072 range_utf16: Option<Range<usize>>,
22073 text: &str,
22074 window: &mut Window,
22075 cx: &mut Context<Self>,
22076 ) {
22077 if !self.input_enabled {
22078 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22079 return;
22080 }
22081
22082 self.transact(window, cx, |this, window, cx| {
22083 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22084 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22085 Some(this.selection_replacement_ranges(range_utf16, cx))
22086 } else {
22087 this.marked_text_ranges(cx)
22088 };
22089
22090 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22091 let newest_selection_id = this.selections.newest_anchor().id;
22092 this.selections
22093 .all::<OffsetUtf16>(cx)
22094 .iter()
22095 .zip(ranges_to_replace.iter())
22096 .find_map(|(selection, range)| {
22097 if selection.id == newest_selection_id {
22098 Some(
22099 (range.start.0 as isize - selection.head().0 as isize)
22100 ..(range.end.0 as isize - selection.head().0 as isize),
22101 )
22102 } else {
22103 None
22104 }
22105 })
22106 });
22107
22108 cx.emit(EditorEvent::InputHandled {
22109 utf16_range_to_replace: range_to_replace,
22110 text: text.into(),
22111 });
22112
22113 if let Some(new_selected_ranges) = new_selected_ranges {
22114 this.change_selections(None, window, cx, |selections| {
22115 selections.select_ranges(new_selected_ranges)
22116 });
22117 this.backspace(&Default::default(), window, cx);
22118 }
22119
22120 this.handle_input(text, window, cx);
22121 });
22122
22123 if let Some(transaction) = self.ime_transaction {
22124 self.buffer.update(cx, |buffer, cx| {
22125 buffer.group_until_transaction(transaction, cx);
22126 });
22127 }
22128
22129 self.unmark_text(window, cx);
22130 }
22131
22132 fn replace_and_mark_text_in_range(
22133 &mut self,
22134 range_utf16: Option<Range<usize>>,
22135 text: &str,
22136 new_selected_range_utf16: Option<Range<usize>>,
22137 window: &mut Window,
22138 cx: &mut Context<Self>,
22139 ) {
22140 if !self.input_enabled {
22141 return;
22142 }
22143
22144 let transaction = self.transact(window, cx, |this, window, cx| {
22145 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22146 let snapshot = this.buffer.read(cx).read(cx);
22147 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22148 for marked_range in &mut marked_ranges {
22149 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22150 marked_range.start.0 += relative_range_utf16.start;
22151 marked_range.start =
22152 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22153 marked_range.end =
22154 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22155 }
22156 }
22157 Some(marked_ranges)
22158 } else if let Some(range_utf16) = range_utf16 {
22159 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22160 Some(this.selection_replacement_ranges(range_utf16, cx))
22161 } else {
22162 None
22163 };
22164
22165 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22166 let newest_selection_id = this.selections.newest_anchor().id;
22167 this.selections
22168 .all::<OffsetUtf16>(cx)
22169 .iter()
22170 .zip(ranges_to_replace.iter())
22171 .find_map(|(selection, range)| {
22172 if selection.id == newest_selection_id {
22173 Some(
22174 (range.start.0 as isize - selection.head().0 as isize)
22175 ..(range.end.0 as isize - selection.head().0 as isize),
22176 )
22177 } else {
22178 None
22179 }
22180 })
22181 });
22182
22183 cx.emit(EditorEvent::InputHandled {
22184 utf16_range_to_replace: range_to_replace,
22185 text: text.into(),
22186 });
22187
22188 if let Some(ranges) = ranges_to_replace {
22189 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
22190 }
22191
22192 let marked_ranges = {
22193 let snapshot = this.buffer.read(cx).read(cx);
22194 this.selections
22195 .disjoint_anchors()
22196 .iter()
22197 .map(|selection| {
22198 (
22199 selection.start.bias_left(&snapshot)
22200 ..selection.end.bias_right(&snapshot),
22201 HighlightStyle {
22202 underline: Some(UnderlineStyle {
22203 thickness: px(1.),
22204 color: None,
22205 wavy: false,
22206 }),
22207 ..Default::default()
22208 },
22209 )
22210 })
22211 .collect::<Vec<_>>()
22212 };
22213
22214 if text.is_empty() {
22215 this.unmark_text(window, cx);
22216 } else {
22217 this.highlight_text::<InputComposition>(marked_ranges.clone(), cx);
22218 }
22219
22220 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22221 let use_autoclose = this.use_autoclose;
22222 let use_auto_surround = this.use_auto_surround;
22223 this.set_use_autoclose(false);
22224 this.set_use_auto_surround(false);
22225 this.handle_input(text, window, cx);
22226 this.set_use_autoclose(use_autoclose);
22227 this.set_use_auto_surround(use_auto_surround);
22228
22229 if let Some(new_selected_range) = new_selected_range_utf16 {
22230 let snapshot = this.buffer.read(cx).read(cx);
22231 let new_selected_ranges = marked_ranges
22232 .into_iter()
22233 .map(|(marked_range, _)| {
22234 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22235 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22236 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22237 snapshot.clip_offset_utf16(new_start, Bias::Left)
22238 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22239 })
22240 .collect::<Vec<_>>();
22241
22242 drop(snapshot);
22243 this.change_selections(None, window, cx, |selections| {
22244 selections.select_ranges(new_selected_ranges)
22245 });
22246 }
22247 });
22248
22249 self.ime_transaction = self.ime_transaction.or(transaction);
22250 if let Some(transaction) = self.ime_transaction {
22251 self.buffer.update(cx, |buffer, cx| {
22252 buffer.group_until_transaction(transaction, cx);
22253 });
22254 }
22255
22256 if self.text_highlights::<InputComposition>(cx).is_none() {
22257 self.ime_transaction.take();
22258 }
22259 }
22260
22261 fn bounds_for_range(
22262 &mut self,
22263 range_utf16: Range<usize>,
22264 element_bounds: gpui::Bounds<Pixels>,
22265 window: &mut Window,
22266 cx: &mut Context<Self>,
22267 ) -> Option<gpui::Bounds<Pixels>> {
22268 let text_layout_details = self.text_layout_details(window);
22269 let gpui::Size {
22270 width: em_width,
22271 height: line_height,
22272 } = self.character_size(window);
22273
22274 let snapshot = self.snapshot(window, cx);
22275 let scroll_position = snapshot.scroll_position();
22276 let scroll_left = scroll_position.x * em_width;
22277
22278 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22279 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22280 + self.gutter_dimensions.width
22281 + self.gutter_dimensions.margin;
22282 let y = line_height * (start.row().as_f32() - scroll_position.y);
22283
22284 Some(Bounds {
22285 origin: element_bounds.origin + point(x, y),
22286 size: size(em_width, line_height),
22287 })
22288 }
22289
22290 fn character_index_for_point(
22291 &mut self,
22292 point: gpui::Point<Pixels>,
22293 _window: &mut Window,
22294 _cx: &mut Context<Self>,
22295 ) -> Option<usize> {
22296 let position_map = self.last_position_map.as_ref()?;
22297 if !position_map.text_hitbox.contains(&point) {
22298 return None;
22299 }
22300 let display_point = position_map.point_for_position(point).previous_valid;
22301 let anchor = position_map
22302 .snapshot
22303 .display_point_to_anchor(display_point, Bias::Left);
22304 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22305 Some(utf16_offset.0)
22306 }
22307}
22308
22309trait SelectionExt {
22310 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22311 fn spanned_rows(
22312 &self,
22313 include_end_if_at_line_start: bool,
22314 map: &DisplaySnapshot,
22315 ) -> Range<MultiBufferRow>;
22316}
22317
22318impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22319 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22320 let start = self
22321 .start
22322 .to_point(&map.buffer_snapshot)
22323 .to_display_point(map);
22324 let end = self
22325 .end
22326 .to_point(&map.buffer_snapshot)
22327 .to_display_point(map);
22328 if self.reversed {
22329 end..start
22330 } else {
22331 start..end
22332 }
22333 }
22334
22335 fn spanned_rows(
22336 &self,
22337 include_end_if_at_line_start: bool,
22338 map: &DisplaySnapshot,
22339 ) -> Range<MultiBufferRow> {
22340 let start = self.start.to_point(&map.buffer_snapshot);
22341 let mut end = self.end.to_point(&map.buffer_snapshot);
22342 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22343 end.row -= 1;
22344 }
22345
22346 let buffer_start = map.prev_line_boundary(start).0;
22347 let buffer_end = map.next_line_boundary(end).0;
22348 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22349 }
22350}
22351
22352impl<T: InvalidationRegion> InvalidationStack<T> {
22353 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22354 where
22355 S: Clone + ToOffset,
22356 {
22357 while let Some(region) = self.last() {
22358 let all_selections_inside_invalidation_ranges =
22359 if selections.len() == region.ranges().len() {
22360 selections
22361 .iter()
22362 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
22363 .all(|(selection, invalidation_range)| {
22364 let head = selection.head().to_offset(buffer);
22365 invalidation_range.start <= head && invalidation_range.end >= head
22366 })
22367 } else {
22368 false
22369 };
22370
22371 if all_selections_inside_invalidation_ranges {
22372 break;
22373 } else {
22374 self.pop();
22375 }
22376 }
22377 }
22378}
22379
22380impl<T> Default for InvalidationStack<T> {
22381 fn default() -> Self {
22382 Self(Default::default())
22383 }
22384}
22385
22386impl<T> Deref for InvalidationStack<T> {
22387 type Target = Vec<T>;
22388
22389 fn deref(&self) -> &Self::Target {
22390 &self.0
22391 }
22392}
22393
22394impl<T> DerefMut for InvalidationStack<T> {
22395 fn deref_mut(&mut self) -> &mut Self::Target {
22396 &mut self.0
22397 }
22398}
22399
22400impl InvalidationRegion for SnippetState {
22401 fn ranges(&self) -> &[Range<Anchor>] {
22402 &self.ranges[self.active_index]
22403 }
22404}
22405
22406fn inline_completion_edit_text(
22407 current_snapshot: &BufferSnapshot,
22408 edits: &[(Range<Anchor>, String)],
22409 edit_preview: &EditPreview,
22410 include_deletions: bool,
22411 cx: &App,
22412) -> HighlightedText {
22413 let edits = edits
22414 .iter()
22415 .map(|(anchor, text)| {
22416 (
22417 anchor.start.text_anchor..anchor.end.text_anchor,
22418 text.clone(),
22419 )
22420 })
22421 .collect::<Vec<_>>();
22422
22423 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
22424}
22425
22426pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
22427 match severity {
22428 lsp::DiagnosticSeverity::ERROR => colors.error,
22429 lsp::DiagnosticSeverity::WARNING => colors.warning,
22430 lsp::DiagnosticSeverity::INFORMATION => colors.info,
22431 lsp::DiagnosticSeverity::HINT => colors.info,
22432 _ => colors.ignored,
22433 }
22434}
22435
22436pub fn styled_runs_for_code_label<'a>(
22437 label: &'a CodeLabel,
22438 syntax_theme: &'a theme::SyntaxTheme,
22439) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
22440 let fade_out = HighlightStyle {
22441 fade_out: Some(0.35),
22442 ..Default::default()
22443 };
22444
22445 let mut prev_end = label.filter_range.end;
22446 label
22447 .runs
22448 .iter()
22449 .enumerate()
22450 .flat_map(move |(ix, (range, highlight_id))| {
22451 let style = if let Some(style) = highlight_id.style(syntax_theme) {
22452 style
22453 } else {
22454 return Default::default();
22455 };
22456 let mut muted_style = style;
22457 muted_style.highlight(fade_out);
22458
22459 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
22460 if range.start >= label.filter_range.end {
22461 if range.start > prev_end {
22462 runs.push((prev_end..range.start, fade_out));
22463 }
22464 runs.push((range.clone(), muted_style));
22465 } else if range.end <= label.filter_range.end {
22466 runs.push((range.clone(), style));
22467 } else {
22468 runs.push((range.start..label.filter_range.end, style));
22469 runs.push((label.filter_range.end..range.end, muted_style));
22470 }
22471 prev_end = cmp::max(prev_end, range.end);
22472
22473 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
22474 runs.push((prev_end..label.text.len(), fade_out));
22475 }
22476
22477 runs
22478 })
22479}
22480
22481pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
22482 let mut prev_index = 0;
22483 let mut prev_codepoint: Option<char> = None;
22484 text.char_indices()
22485 .chain([(text.len(), '\0')])
22486 .filter_map(move |(index, codepoint)| {
22487 let prev_codepoint = prev_codepoint.replace(codepoint)?;
22488 let is_boundary = index == text.len()
22489 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
22490 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
22491 if is_boundary {
22492 let chunk = &text[prev_index..index];
22493 prev_index = index;
22494 Some(chunk)
22495 } else {
22496 None
22497 }
22498 })
22499}
22500
22501pub trait RangeToAnchorExt: Sized {
22502 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
22503
22504 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
22505 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
22506 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
22507 }
22508}
22509
22510impl<T: ToOffset> RangeToAnchorExt for Range<T> {
22511 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
22512 let start_offset = self.start.to_offset(snapshot);
22513 let end_offset = self.end.to_offset(snapshot);
22514 if start_offset == end_offset {
22515 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
22516 } else {
22517 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
22518 }
22519 }
22520}
22521
22522pub trait RowExt {
22523 fn as_f32(&self) -> f32;
22524
22525 fn next_row(&self) -> Self;
22526
22527 fn previous_row(&self) -> Self;
22528
22529 fn minus(&self, other: Self) -> u32;
22530}
22531
22532impl RowExt for DisplayRow {
22533 fn as_f32(&self) -> f32 {
22534 self.0 as f32
22535 }
22536
22537 fn next_row(&self) -> Self {
22538 Self(self.0 + 1)
22539 }
22540
22541 fn previous_row(&self) -> Self {
22542 Self(self.0.saturating_sub(1))
22543 }
22544
22545 fn minus(&self, other: Self) -> u32 {
22546 self.0 - other.0
22547 }
22548}
22549
22550impl RowExt for MultiBufferRow {
22551 fn as_f32(&self) -> f32 {
22552 self.0 as f32
22553 }
22554
22555 fn next_row(&self) -> Self {
22556 Self(self.0 + 1)
22557 }
22558
22559 fn previous_row(&self) -> Self {
22560 Self(self.0.saturating_sub(1))
22561 }
22562
22563 fn minus(&self, other: Self) -> u32 {
22564 self.0 - other.0
22565 }
22566}
22567
22568trait RowRangeExt {
22569 type Row;
22570
22571 fn len(&self) -> usize;
22572
22573 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
22574}
22575
22576impl RowRangeExt for Range<MultiBufferRow> {
22577 type Row = MultiBufferRow;
22578
22579 fn len(&self) -> usize {
22580 (self.end.0 - self.start.0) as usize
22581 }
22582
22583 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
22584 (self.start.0..self.end.0).map(MultiBufferRow)
22585 }
22586}
22587
22588impl RowRangeExt for Range<DisplayRow> {
22589 type Row = DisplayRow;
22590
22591 fn len(&self) -> usize {
22592 (self.end.0 - self.start.0) as usize
22593 }
22594
22595 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
22596 (self.start.0..self.end.0).map(DisplayRow)
22597 }
22598}
22599
22600/// If select range has more than one line, we
22601/// just point the cursor to range.start.
22602fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
22603 if range.start.row == range.end.row {
22604 range
22605 } else {
22606 range.start..range.start
22607 }
22608}
22609pub struct KillRing(ClipboardItem);
22610impl Global for KillRing {}
22611
22612const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
22613
22614enum BreakpointPromptEditAction {
22615 Log,
22616 Condition,
22617 HitCondition,
22618}
22619
22620struct BreakpointPromptEditor {
22621 pub(crate) prompt: Entity<Editor>,
22622 editor: WeakEntity<Editor>,
22623 breakpoint_anchor: Anchor,
22624 breakpoint: Breakpoint,
22625 edit_action: BreakpointPromptEditAction,
22626 block_ids: HashSet<CustomBlockId>,
22627 editor_margins: Arc<Mutex<EditorMargins>>,
22628 _subscriptions: Vec<Subscription>,
22629}
22630
22631impl BreakpointPromptEditor {
22632 const MAX_LINES: u8 = 4;
22633
22634 fn new(
22635 editor: WeakEntity<Editor>,
22636 breakpoint_anchor: Anchor,
22637 breakpoint: Breakpoint,
22638 edit_action: BreakpointPromptEditAction,
22639 window: &mut Window,
22640 cx: &mut Context<Self>,
22641 ) -> Self {
22642 let base_text = match edit_action {
22643 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
22644 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
22645 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
22646 }
22647 .map(|msg| msg.to_string())
22648 .unwrap_or_default();
22649
22650 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
22651 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
22652
22653 let prompt = cx.new(|cx| {
22654 let mut prompt = Editor::new(
22655 EditorMode::AutoHeight {
22656 min_lines: 1,
22657 max_lines: Self::MAX_LINES as usize,
22658 },
22659 buffer,
22660 None,
22661 window,
22662 cx,
22663 );
22664 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
22665 prompt.set_show_cursor_when_unfocused(false, cx);
22666 prompt.set_placeholder_text(
22667 match edit_action {
22668 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
22669 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
22670 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
22671 },
22672 cx,
22673 );
22674
22675 prompt
22676 });
22677
22678 Self {
22679 prompt,
22680 editor,
22681 breakpoint_anchor,
22682 breakpoint,
22683 edit_action,
22684 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
22685 block_ids: Default::default(),
22686 _subscriptions: vec![],
22687 }
22688 }
22689
22690 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
22691 self.block_ids.extend(block_ids)
22692 }
22693
22694 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
22695 if let Some(editor) = self.editor.upgrade() {
22696 let message = self
22697 .prompt
22698 .read(cx)
22699 .buffer
22700 .read(cx)
22701 .as_singleton()
22702 .expect("A multi buffer in breakpoint prompt isn't possible")
22703 .read(cx)
22704 .as_rope()
22705 .to_string();
22706
22707 editor.update(cx, |editor, cx| {
22708 editor.edit_breakpoint_at_anchor(
22709 self.breakpoint_anchor,
22710 self.breakpoint.clone(),
22711 match self.edit_action {
22712 BreakpointPromptEditAction::Log => {
22713 BreakpointEditAction::EditLogMessage(message.into())
22714 }
22715 BreakpointPromptEditAction::Condition => {
22716 BreakpointEditAction::EditCondition(message.into())
22717 }
22718 BreakpointPromptEditAction::HitCondition => {
22719 BreakpointEditAction::EditHitCondition(message.into())
22720 }
22721 },
22722 cx,
22723 );
22724
22725 editor.remove_blocks(self.block_ids.clone(), None, cx);
22726 cx.focus_self(window);
22727 });
22728 }
22729 }
22730
22731 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
22732 self.editor
22733 .update(cx, |editor, cx| {
22734 editor.remove_blocks(self.block_ids.clone(), None, cx);
22735 window.focus(&editor.focus_handle);
22736 })
22737 .log_err();
22738 }
22739
22740 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
22741 let settings = ThemeSettings::get_global(cx);
22742 let text_style = TextStyle {
22743 color: if self.prompt.read(cx).read_only(cx) {
22744 cx.theme().colors().text_disabled
22745 } else {
22746 cx.theme().colors().text
22747 },
22748 font_family: settings.buffer_font.family.clone(),
22749 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22750 font_size: settings.buffer_font_size(cx).into(),
22751 font_weight: settings.buffer_font.weight,
22752 line_height: relative(settings.buffer_line_height.value()),
22753 ..Default::default()
22754 };
22755 EditorElement::new(
22756 &self.prompt,
22757 EditorStyle {
22758 background: cx.theme().colors().editor_background,
22759 local_player: cx.theme().players().local(),
22760 text: text_style,
22761 ..Default::default()
22762 },
22763 )
22764 }
22765}
22766
22767impl Render for BreakpointPromptEditor {
22768 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22769 let editor_margins = *self.editor_margins.lock();
22770 let gutter_dimensions = editor_margins.gutter;
22771 h_flex()
22772 .key_context("Editor")
22773 .bg(cx.theme().colors().editor_background)
22774 .border_y_1()
22775 .border_color(cx.theme().status().info_border)
22776 .size_full()
22777 .py(window.line_height() / 2.5)
22778 .on_action(cx.listener(Self::confirm))
22779 .on_action(cx.listener(Self::cancel))
22780 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
22781 .child(div().flex_1().child(self.render_prompt_editor(cx)))
22782 }
22783}
22784
22785impl Focusable for BreakpointPromptEditor {
22786 fn focus_handle(&self, cx: &App) -> FocusHandle {
22787 self.prompt.focus_handle(cx)
22788 }
22789}
22790
22791fn all_edits_insertions_or_deletions(
22792 edits: &Vec<(Range<Anchor>, String)>,
22793 snapshot: &MultiBufferSnapshot,
22794) -> bool {
22795 let mut all_insertions = true;
22796 let mut all_deletions = true;
22797
22798 for (range, new_text) in edits.iter() {
22799 let range_is_empty = range.to_offset(&snapshot).is_empty();
22800 let text_is_empty = new_text.is_empty();
22801
22802 if range_is_empty != text_is_empty {
22803 if range_is_empty {
22804 all_deletions = false;
22805 } else {
22806 all_insertions = false;
22807 }
22808 } else {
22809 return false;
22810 }
22811
22812 if !all_insertions && !all_deletions {
22813 return false;
22814 }
22815 }
22816 all_insertions || all_deletions
22817}
22818
22819struct MissingEditPredictionKeybindingTooltip;
22820
22821impl Render for MissingEditPredictionKeybindingTooltip {
22822 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22823 ui::tooltip_container(window, cx, |container, _, cx| {
22824 container
22825 .flex_shrink_0()
22826 .max_w_80()
22827 .min_h(rems_from_px(124.))
22828 .justify_between()
22829 .child(
22830 v_flex()
22831 .flex_1()
22832 .text_ui_sm(cx)
22833 .child(Label::new("Conflict with Accept Keybinding"))
22834 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
22835 )
22836 .child(
22837 h_flex()
22838 .pb_1()
22839 .gap_1()
22840 .items_end()
22841 .w_full()
22842 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
22843 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
22844 }))
22845 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
22846 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
22847 })),
22848 )
22849 })
22850 }
22851}
22852
22853#[derive(Debug, Clone, Copy, PartialEq)]
22854pub struct LineHighlight {
22855 pub background: Background,
22856 pub border: Option<gpui::Hsla>,
22857 pub include_gutter: bool,
22858 pub type_id: Option<TypeId>,
22859}
22860
22861fn render_diff_hunk_controls(
22862 row: u32,
22863 status: &DiffHunkStatus,
22864 hunk_range: Range<Anchor>,
22865 is_created_file: bool,
22866 line_height: Pixels,
22867 editor: &Entity<Editor>,
22868 _window: &mut Window,
22869 cx: &mut App,
22870) -> AnyElement {
22871 h_flex()
22872 .h(line_height)
22873 .mr_1()
22874 .gap_1()
22875 .px_0p5()
22876 .pb_1()
22877 .border_x_1()
22878 .border_b_1()
22879 .border_color(cx.theme().colors().border_variant)
22880 .rounded_b_lg()
22881 .bg(cx.theme().colors().editor_background)
22882 .gap_1()
22883 .block_mouse_except_scroll()
22884 .shadow_md()
22885 .child(if status.has_secondary_hunk() {
22886 Button::new(("stage", row as u64), "Stage")
22887 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22888 .tooltip({
22889 let focus_handle = editor.focus_handle(cx);
22890 move |window, cx| {
22891 Tooltip::for_action_in(
22892 "Stage Hunk",
22893 &::git::ToggleStaged,
22894 &focus_handle,
22895 window,
22896 cx,
22897 )
22898 }
22899 })
22900 .on_click({
22901 let editor = editor.clone();
22902 move |_event, _window, cx| {
22903 editor.update(cx, |editor, cx| {
22904 editor.stage_or_unstage_diff_hunks(
22905 true,
22906 vec![hunk_range.start..hunk_range.start],
22907 cx,
22908 );
22909 });
22910 }
22911 })
22912 } else {
22913 Button::new(("unstage", row as u64), "Unstage")
22914 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22915 .tooltip({
22916 let focus_handle = editor.focus_handle(cx);
22917 move |window, cx| {
22918 Tooltip::for_action_in(
22919 "Unstage Hunk",
22920 &::git::ToggleStaged,
22921 &focus_handle,
22922 window,
22923 cx,
22924 )
22925 }
22926 })
22927 .on_click({
22928 let editor = editor.clone();
22929 move |_event, _window, cx| {
22930 editor.update(cx, |editor, cx| {
22931 editor.stage_or_unstage_diff_hunks(
22932 false,
22933 vec![hunk_range.start..hunk_range.start],
22934 cx,
22935 );
22936 });
22937 }
22938 })
22939 })
22940 .child(
22941 Button::new(("restore", row as u64), "Restore")
22942 .tooltip({
22943 let focus_handle = editor.focus_handle(cx);
22944 move |window, cx| {
22945 Tooltip::for_action_in(
22946 "Restore Hunk",
22947 &::git::Restore,
22948 &focus_handle,
22949 window,
22950 cx,
22951 )
22952 }
22953 })
22954 .on_click({
22955 let editor = editor.clone();
22956 move |_event, window, cx| {
22957 editor.update(cx, |editor, cx| {
22958 let snapshot = editor.snapshot(window, cx);
22959 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
22960 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
22961 });
22962 }
22963 })
22964 .disabled(is_created_file),
22965 )
22966 .when(
22967 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
22968 |el| {
22969 el.child(
22970 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
22971 .shape(IconButtonShape::Square)
22972 .icon_size(IconSize::Small)
22973 // .disabled(!has_multiple_hunks)
22974 .tooltip({
22975 let focus_handle = editor.focus_handle(cx);
22976 move |window, cx| {
22977 Tooltip::for_action_in(
22978 "Next Hunk",
22979 &GoToHunk,
22980 &focus_handle,
22981 window,
22982 cx,
22983 )
22984 }
22985 })
22986 .on_click({
22987 let editor = editor.clone();
22988 move |_event, window, cx| {
22989 editor.update(cx, |editor, cx| {
22990 let snapshot = editor.snapshot(window, cx);
22991 let position =
22992 hunk_range.end.to_point(&snapshot.buffer_snapshot);
22993 editor.go_to_hunk_before_or_after_position(
22994 &snapshot,
22995 position,
22996 Direction::Next,
22997 window,
22998 cx,
22999 );
23000 editor.expand_selected_diff_hunks(cx);
23001 });
23002 }
23003 }),
23004 )
23005 .child(
23006 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23007 .shape(IconButtonShape::Square)
23008 .icon_size(IconSize::Small)
23009 // .disabled(!has_multiple_hunks)
23010 .tooltip({
23011 let focus_handle = editor.focus_handle(cx);
23012 move |window, cx| {
23013 Tooltip::for_action_in(
23014 "Previous Hunk",
23015 &GoToPreviousHunk,
23016 &focus_handle,
23017 window,
23018 cx,
23019 )
23020 }
23021 })
23022 .on_click({
23023 let editor = editor.clone();
23024 move |_event, window, cx| {
23025 editor.update(cx, |editor, cx| {
23026 let snapshot = editor.snapshot(window, cx);
23027 let point =
23028 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23029 editor.go_to_hunk_before_or_after_position(
23030 &snapshot,
23031 point,
23032 Direction::Prev,
23033 window,
23034 cx,
23035 );
23036 editor.expand_selected_diff_hunks(cx);
23037 });
23038 }
23039 }),
23040 )
23041 },
23042 )
23043 .into_any_element()
23044}