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_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod editor_tests;
46#[cfg(test)]
47mod inline_completion_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
54use aho_corasick::AhoCorasick;
55use anyhow::{Context as _, Result, anyhow};
56use blink_manager::BlinkManager;
57use buffer_diff::DiffHunkStatus;
58use client::{Collaborator, ParticipantIndex};
59use clock::{AGENT_REPLICA_ID, ReplicaId};
60use collections::{BTreeMap, HashMap, HashSet, VecDeque};
61use convert_case::{Case, Casing};
62use dap::TelemetrySpawnLocation;
63use display_map::*;
64pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
65pub use editor_settings::{
66 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, ScrollbarAxes,
67 SearchSettings, ShowScrollbar,
68};
69use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
70pub use editor_settings_controls::*;
71use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
72pub use element::{
73 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
74};
75use feature_flags::{DebuggerFeatureFlag, FeatureFlagAppExt};
76use futures::{
77 FutureExt, StreamExt as _,
78 future::{self, Shared, join},
79 stream::FuturesUnordered,
80};
81use fuzzy::{StringMatch, StringMatchCandidate};
82
83use ::git::blame::BlameEntry;
84use ::git::{Restore, blame::ParsedCommitMessage};
85use code_context_menus::{
86 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
87 CompletionsMenu, ContextMenuOrigin,
88};
89use git::blame::{GitBlame, GlobalBlameRenderer};
90use gpui::{
91 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
92 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
93 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
94 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
95 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
96 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
97 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
98 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
99};
100use highlight_matching_bracket::refresh_matching_bracket_highlights;
101use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
102pub use hover_popover::hover_markdown_style;
103use hover_popover::{HoverState, hide_hover};
104use indent_guides::ActiveIndentGuidesState;
105use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
106pub use inline_completion::Direction;
107use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
108pub use items::MAX_TAB_TITLE_LEN;
109use itertools::Itertools;
110use language::{
111 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
112 CursorShape, DiagnosticEntry, DiagnosticSourceKind, DiffOptions, DocumentationConfig,
113 EditPredictionsMode, EditPreview, HighlightedText, IndentKind, IndentSize, Language,
114 OffsetRangeExt, Point, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions,
115 WordsQuery,
116 language_settings::{
117 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
118 all_language_settings, language_settings,
119 },
120 point_from_lsp, text_diff_with_options,
121};
122use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
123use linked_editing_ranges::refresh_linked_ranges;
124use markdown::Markdown;
125use mouse_context_menu::MouseContextMenu;
126use persistence::DB;
127use project::{
128 BreakpointWithPosition, CompletionResponse, LspPullDiagnostics, ProjectPath, PulledDiagnostics,
129 debugger::{
130 breakpoint_store::{
131 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
132 BreakpointStoreEvent,
133 },
134 session::{Session, SessionEvent},
135 },
136 project_settings::DiagnosticSeverity,
137};
138
139pub use git::blame::BlameRenderer;
140pub use proposed_changes_editor::{
141 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
142};
143use std::{cell::OnceCell, iter::Peekable, ops::Not};
144use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
145
146pub use lsp::CompletionContext;
147use lsp::{
148 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
149 LanguageServerId, LanguageServerName,
150};
151
152use language::BufferSnapshot;
153pub use lsp_ext::lsp_tasks;
154use movement::TextLayoutDetails;
155pub use multi_buffer::{
156 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
157 RowInfo, ToOffset, ToPoint,
158};
159use multi_buffer::{
160 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
161 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
162};
163use parking_lot::Mutex;
164use project::{
165 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
166 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
167 TaskSourceKind,
168 debugger::breakpoint_store::Breakpoint,
169 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
170 project_settings::{GitGutterSetting, ProjectSettings},
171};
172use rand::prelude::*;
173use rpc::{ErrorExt, proto::*};
174use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
175use selections_collection::{
176 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
177};
178use serde::{Deserialize, Serialize};
179use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
180use smallvec::{SmallVec, smallvec};
181use snippet::Snippet;
182use std::sync::Arc;
183use std::{
184 any::TypeId,
185 borrow::Cow,
186 cell::RefCell,
187 cmp::{self, Ordering, Reverse},
188 mem,
189 num::NonZeroU32,
190 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
191 path::{Path, PathBuf},
192 rc::Rc,
193 time::{Duration, Instant},
194};
195pub use sum_tree::Bias;
196use sum_tree::TreeMap;
197use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
198use theme::{
199 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
200 observe_buffer_font_size_adjustment,
201};
202use ui::{
203 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
204 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
205};
206use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
207use workspace::{
208 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
209 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
210 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
211 item::{ItemHandle, PreviewTabsSettings},
212 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
213 searchable::SearchEvent,
214};
215
216use crate::{
217 code_context_menus::CompletionsMenuSource,
218 hover_links::{find_url, find_url_from_range},
219};
220use crate::{
221 editor_settings::MultiCursorModifier,
222 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
223};
224
225pub const FILE_HEADER_HEIGHT: u32 = 2;
226pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
227pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
228const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
229const MAX_LINE_LEN: usize = 1024;
230const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
231const MAX_SELECTION_HISTORY_LEN: usize = 1024;
232pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
233#[doc(hidden)]
234pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
235const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
236
237pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
238pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
239pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
240
241pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
242pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
243pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
244
245pub type RenderDiffHunkControlsFn = Arc<
246 dyn Fn(
247 u32,
248 &DiffHunkStatus,
249 Range<Anchor>,
250 bool,
251 Pixels,
252 &Entity<Editor>,
253 &mut Window,
254 &mut App,
255 ) -> AnyElement,
256>;
257
258struct InlineValueCache {
259 enabled: bool,
260 inlays: Vec<InlayId>,
261 refresh_task: Task<Option<()>>,
262}
263
264impl InlineValueCache {
265 fn new(enabled: bool) -> Self {
266 Self {
267 enabled,
268 inlays: Vec::new(),
269 refresh_task: Task::ready(None),
270 }
271 }
272}
273
274#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
275pub enum InlayId {
276 InlineCompletion(usize),
277 Hint(usize),
278 DebuggerValue(usize),
279}
280
281impl InlayId {
282 fn id(&self) -> usize {
283 match self {
284 Self::InlineCompletion(id) => *id,
285 Self::Hint(id) => *id,
286 Self::DebuggerValue(id) => *id,
287 }
288 }
289}
290
291pub enum ActiveDebugLine {}
292pub enum DebugStackFrameLine {}
293enum DocumentHighlightRead {}
294enum DocumentHighlightWrite {}
295enum InputComposition {}
296pub enum PendingInput {}
297enum SelectedTextHighlight {}
298
299pub enum ConflictsOuter {}
300pub enum ConflictsOurs {}
301pub enum ConflictsTheirs {}
302pub enum ConflictsOursMarker {}
303pub enum ConflictsTheirsMarker {}
304
305#[derive(Debug, Copy, Clone, PartialEq, Eq)]
306pub enum Navigated {
307 Yes,
308 No,
309}
310
311impl Navigated {
312 pub fn from_bool(yes: bool) -> Navigated {
313 if yes { Navigated::Yes } else { Navigated::No }
314 }
315}
316
317#[derive(Debug, Clone, PartialEq, Eq)]
318enum DisplayDiffHunk {
319 Folded {
320 display_row: DisplayRow,
321 },
322 Unfolded {
323 is_created_file: bool,
324 diff_base_byte_range: Range<usize>,
325 display_row_range: Range<DisplayRow>,
326 multi_buffer_range: Range<Anchor>,
327 status: DiffHunkStatus,
328 },
329}
330
331pub enum HideMouseCursorOrigin {
332 TypingAction,
333 MovementAction,
334}
335
336pub fn init_settings(cx: &mut App) {
337 EditorSettings::register(cx);
338}
339
340pub fn init(cx: &mut App) {
341 init_settings(cx);
342
343 cx.set_global(GlobalBlameRenderer(Arc::new(())));
344
345 workspace::register_project_item::<Editor>(cx);
346 workspace::FollowableViewRegistry::register::<Editor>(cx);
347 workspace::register_serializable_item::<Editor>(cx);
348
349 cx.observe_new(
350 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
351 workspace.register_action(Editor::new_file);
352 workspace.register_action(Editor::new_file_vertical);
353 workspace.register_action(Editor::new_file_horizontal);
354 workspace.register_action(Editor::cancel_language_server_work);
355 },
356 )
357 .detach();
358
359 cx.on_action(move |_: &workspace::NewFile, cx| {
360 let app_state = workspace::AppState::global(cx);
361 if let Some(app_state) = app_state.upgrade() {
362 workspace::open_new(
363 Default::default(),
364 app_state,
365 cx,
366 |workspace, window, cx| {
367 Editor::new_file(workspace, &Default::default(), window, cx)
368 },
369 )
370 .detach();
371 }
372 });
373 cx.on_action(move |_: &workspace::NewWindow, cx| {
374 let app_state = workspace::AppState::global(cx);
375 if let Some(app_state) = app_state.upgrade() {
376 workspace::open_new(
377 Default::default(),
378 app_state,
379 cx,
380 |workspace, window, cx| {
381 cx.activate(true);
382 Editor::new_file(workspace, &Default::default(), window, cx)
383 },
384 )
385 .detach();
386 }
387 });
388}
389
390pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
391 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
392}
393
394pub trait DiagnosticRenderer {
395 fn render_group(
396 &self,
397 diagnostic_group: Vec<DiagnosticEntry<Point>>,
398 buffer_id: BufferId,
399 snapshot: EditorSnapshot,
400 editor: WeakEntity<Editor>,
401 cx: &mut App,
402 ) -> Vec<BlockProperties<Anchor>>;
403
404 fn render_hover(
405 &self,
406 diagnostic_group: Vec<DiagnosticEntry<Point>>,
407 range: Range<Point>,
408 buffer_id: BufferId,
409 cx: &mut App,
410 ) -> Option<Entity<markdown::Markdown>>;
411
412 fn open_link(
413 &self,
414 editor: &mut Editor,
415 link: SharedString,
416 window: &mut Window,
417 cx: &mut Context<Editor>,
418 );
419}
420
421pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
422
423impl GlobalDiagnosticRenderer {
424 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
425 cx.try_global::<Self>().map(|g| g.0.clone())
426 }
427}
428
429impl gpui::Global for GlobalDiagnosticRenderer {}
430pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
431 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
432}
433
434pub struct SearchWithinRange;
435
436trait InvalidationRegion {
437 fn ranges(&self) -> &[Range<Anchor>];
438}
439
440#[derive(Clone, Debug, PartialEq)]
441pub enum SelectPhase {
442 Begin {
443 position: DisplayPoint,
444 add: bool,
445 click_count: usize,
446 },
447 BeginColumnar {
448 position: DisplayPoint,
449 reset: bool,
450 goal_column: u32,
451 },
452 Extend {
453 position: DisplayPoint,
454 click_count: usize,
455 },
456 Update {
457 position: DisplayPoint,
458 goal_column: u32,
459 scroll_delta: gpui::Point<f32>,
460 },
461 End,
462}
463
464#[derive(Clone, Debug)]
465pub enum SelectMode {
466 Character,
467 Word(Range<Anchor>),
468 Line(Range<Anchor>),
469 All,
470}
471
472#[derive(Clone, PartialEq, Eq, Debug)]
473pub enum EditorMode {
474 SingleLine {
475 auto_width: bool,
476 },
477 AutoHeight {
478 max_lines: usize,
479 },
480 Full {
481 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
482 scale_ui_elements_with_buffer_font_size: bool,
483 /// When set to `true`, the editor will render a background for the active line.
484 show_active_line_background: bool,
485 /// When set to `true`, the editor's height will be determined by its content.
486 sized_by_content: bool,
487 },
488 Minimap {
489 parent: WeakEntity<Editor>,
490 },
491}
492
493impl EditorMode {
494 pub fn full() -> Self {
495 Self::Full {
496 scale_ui_elements_with_buffer_font_size: true,
497 show_active_line_background: true,
498 sized_by_content: false,
499 }
500 }
501
502 pub fn is_full(&self) -> bool {
503 matches!(self, Self::Full { .. })
504 }
505
506 fn is_minimap(&self) -> bool {
507 matches!(self, Self::Minimap { .. })
508 }
509}
510
511#[derive(Copy, Clone, Debug)]
512pub enum SoftWrap {
513 /// Prefer not to wrap at all.
514 ///
515 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
516 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
517 GitDiff,
518 /// Prefer a single line generally, unless an overly long line is encountered.
519 None,
520 /// Soft wrap lines that exceed the editor width.
521 EditorWidth,
522 /// Soft wrap lines at the preferred line length.
523 Column(u32),
524 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
525 Bounded(u32),
526}
527
528#[derive(Clone)]
529pub struct EditorStyle {
530 pub background: Hsla,
531 pub local_player: PlayerColor,
532 pub text: TextStyle,
533 pub scrollbar_width: Pixels,
534 pub syntax: Arc<SyntaxTheme>,
535 pub status: StatusColors,
536 pub inlay_hints_style: HighlightStyle,
537 pub inline_completion_styles: InlineCompletionStyles,
538 pub unnecessary_code_fade: f32,
539 pub show_underlines: bool,
540}
541
542impl Default for EditorStyle {
543 fn default() -> Self {
544 Self {
545 background: Hsla::default(),
546 local_player: PlayerColor::default(),
547 text: TextStyle::default(),
548 scrollbar_width: Pixels::default(),
549 syntax: Default::default(),
550 // HACK: Status colors don't have a real default.
551 // We should look into removing the status colors from the editor
552 // style and retrieve them directly from the theme.
553 status: StatusColors::dark(),
554 inlay_hints_style: HighlightStyle::default(),
555 inline_completion_styles: InlineCompletionStyles {
556 insertion: HighlightStyle::default(),
557 whitespace: HighlightStyle::default(),
558 },
559 unnecessary_code_fade: Default::default(),
560 show_underlines: true,
561 }
562 }
563}
564
565pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
566 let show_background = language_settings::language_settings(None, None, cx)
567 .inlay_hints
568 .show_background;
569
570 HighlightStyle {
571 color: Some(cx.theme().status().hint),
572 background_color: show_background.then(|| cx.theme().status().hint_background),
573 ..HighlightStyle::default()
574 }
575}
576
577pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
578 InlineCompletionStyles {
579 insertion: HighlightStyle {
580 color: Some(cx.theme().status().predictive),
581 ..HighlightStyle::default()
582 },
583 whitespace: HighlightStyle {
584 background_color: Some(cx.theme().status().created_background),
585 ..HighlightStyle::default()
586 },
587 }
588}
589
590type CompletionId = usize;
591
592pub(crate) enum EditDisplayMode {
593 TabAccept,
594 DiffPopover,
595 Inline,
596}
597
598enum InlineCompletion {
599 Edit {
600 edits: Vec<(Range<Anchor>, String)>,
601 edit_preview: Option<EditPreview>,
602 display_mode: EditDisplayMode,
603 snapshot: BufferSnapshot,
604 },
605 Move {
606 target: Anchor,
607 snapshot: BufferSnapshot,
608 },
609}
610
611struct InlineCompletionState {
612 inlay_ids: Vec<InlayId>,
613 completion: InlineCompletion,
614 completion_id: Option<SharedString>,
615 invalidation_range: Range<Anchor>,
616}
617
618enum EditPredictionSettings {
619 Disabled,
620 Enabled {
621 show_in_menu: bool,
622 preview_requires_modifier: bool,
623 },
624}
625
626enum InlineCompletionHighlight {}
627
628#[derive(Debug, Clone)]
629struct InlineDiagnostic {
630 message: SharedString,
631 group_id: usize,
632 is_primary: bool,
633 start: Point,
634 severity: lsp::DiagnosticSeverity,
635}
636
637pub enum MenuInlineCompletionsPolicy {
638 Never,
639 ByProvider,
640}
641
642pub enum EditPredictionPreview {
643 /// Modifier is not pressed
644 Inactive { released_too_fast: bool },
645 /// Modifier pressed
646 Active {
647 since: Instant,
648 previous_scroll_position: Option<ScrollAnchor>,
649 },
650}
651
652impl EditPredictionPreview {
653 pub fn released_too_fast(&self) -> bool {
654 match self {
655 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
656 EditPredictionPreview::Active { .. } => false,
657 }
658 }
659
660 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
661 if let EditPredictionPreview::Active {
662 previous_scroll_position,
663 ..
664 } = self
665 {
666 *previous_scroll_position = scroll_position;
667 }
668 }
669}
670
671pub struct ContextMenuOptions {
672 pub min_entries_visible: usize,
673 pub max_entries_visible: usize,
674 pub placement: Option<ContextMenuPlacement>,
675}
676
677#[derive(Debug, Clone, PartialEq, Eq)]
678pub enum ContextMenuPlacement {
679 Above,
680 Below,
681}
682
683#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
684struct EditorActionId(usize);
685
686impl EditorActionId {
687 pub fn post_inc(&mut self) -> Self {
688 let answer = self.0;
689
690 *self = Self(answer + 1);
691
692 Self(answer)
693 }
694}
695
696// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
697// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
698
699type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
700type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
701
702#[derive(Default)]
703struct ScrollbarMarkerState {
704 scrollbar_size: Size<Pixels>,
705 dirty: bool,
706 markers: Arc<[PaintQuad]>,
707 pending_refresh: Option<Task<Result<()>>>,
708}
709
710impl ScrollbarMarkerState {
711 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
712 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
713 }
714}
715
716#[derive(Clone, Copy, PartialEq, Eq)]
717pub enum MinimapVisibility {
718 Disabled,
719 Enabled {
720 /// The configuration currently present in the users settings.
721 setting_configuration: bool,
722 /// Whether to override the currently set visibility from the users setting.
723 toggle_override: bool,
724 },
725}
726
727impl MinimapVisibility {
728 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
729 if mode.is_full() {
730 Self::Enabled {
731 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
732 toggle_override: false,
733 }
734 } else {
735 Self::Disabled
736 }
737 }
738
739 fn hidden(&self) -> Self {
740 match *self {
741 Self::Enabled {
742 setting_configuration,
743 ..
744 } => Self::Enabled {
745 setting_configuration,
746 toggle_override: setting_configuration,
747 },
748 Self::Disabled => Self::Disabled,
749 }
750 }
751
752 fn disabled(&self) -> bool {
753 match *self {
754 Self::Disabled => true,
755 _ => false,
756 }
757 }
758
759 fn settings_visibility(&self) -> bool {
760 match *self {
761 Self::Enabled {
762 setting_configuration,
763 ..
764 } => setting_configuration,
765 _ => false,
766 }
767 }
768
769 fn visible(&self) -> bool {
770 match *self {
771 Self::Enabled {
772 setting_configuration,
773 toggle_override,
774 } => setting_configuration ^ toggle_override,
775 _ => false,
776 }
777 }
778
779 fn toggle_visibility(&self) -> Self {
780 match *self {
781 Self::Enabled {
782 toggle_override,
783 setting_configuration,
784 } => Self::Enabled {
785 setting_configuration,
786 toggle_override: !toggle_override,
787 },
788 Self::Disabled => Self::Disabled,
789 }
790 }
791}
792
793#[derive(Clone, Debug)]
794struct RunnableTasks {
795 templates: Vec<(TaskSourceKind, TaskTemplate)>,
796 offset: multi_buffer::Anchor,
797 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
798 column: u32,
799 // Values of all named captures, including those starting with '_'
800 extra_variables: HashMap<String, String>,
801 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
802 context_range: Range<BufferOffset>,
803}
804
805impl RunnableTasks {
806 fn resolve<'a>(
807 &'a self,
808 cx: &'a task::TaskContext,
809 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
810 self.templates.iter().filter_map(|(kind, template)| {
811 template
812 .resolve_task(&kind.to_id_base(), cx)
813 .map(|task| (kind.clone(), task))
814 })
815 }
816}
817
818#[derive(Clone)]
819pub struct ResolvedTasks {
820 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
821 position: Anchor,
822}
823
824#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
825struct BufferOffset(usize);
826
827// Addons allow storing per-editor state in other crates (e.g. Vim)
828pub trait Addon: 'static {
829 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
830
831 fn render_buffer_header_controls(
832 &self,
833 _: &ExcerptInfo,
834 _: &Window,
835 _: &App,
836 ) -> Option<AnyElement> {
837 None
838 }
839
840 fn to_any(&self) -> &dyn std::any::Any;
841
842 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
843 None
844 }
845}
846
847/// A set of caret positions, registered when the editor was edited.
848pub struct ChangeList {
849 changes: Vec<Vec<Anchor>>,
850 /// Currently "selected" change.
851 position: Option<usize>,
852}
853
854impl ChangeList {
855 pub fn new() -> Self {
856 Self {
857 changes: Vec::new(),
858 position: None,
859 }
860 }
861
862 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
863 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
864 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
865 if self.changes.is_empty() {
866 return None;
867 }
868
869 let prev = self.position.unwrap_or(self.changes.len());
870 let next = if direction == Direction::Prev {
871 prev.saturating_sub(count)
872 } else {
873 (prev + count).min(self.changes.len() - 1)
874 };
875 self.position = Some(next);
876 self.changes.get(next).map(|anchors| anchors.as_slice())
877 }
878
879 /// Adds a new change to the list, resetting the change list position.
880 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
881 self.position.take();
882 if pop_state {
883 self.changes.pop();
884 }
885 self.changes.push(new_positions.clone());
886 }
887
888 pub fn last(&self) -> Option<&[Anchor]> {
889 self.changes.last().map(|anchors| anchors.as_slice())
890 }
891}
892
893#[derive(Clone)]
894struct InlineBlamePopoverState {
895 scroll_handle: ScrollHandle,
896 commit_message: Option<ParsedCommitMessage>,
897 markdown: Entity<Markdown>,
898}
899
900struct InlineBlamePopover {
901 position: gpui::Point<Pixels>,
902 show_task: Option<Task<()>>,
903 hide_task: Option<Task<()>>,
904 popover_bounds: Option<Bounds<Pixels>>,
905 popover_state: InlineBlamePopoverState,
906}
907
908enum SelectionDragState {
909 /// State when no drag related activity is detected.
910 None,
911 /// State when the mouse is down on a selection that is about to be dragged.
912 ReadyToDrag {
913 selection: Selection<Anchor>,
914 click_position: gpui::Point<Pixels>,
915 },
916 /// State when the mouse is dragging the selection in the editor.
917 Dragging {
918 selection: Selection<Anchor>,
919 drop_cursor: Selection<Anchor>,
920 hide_drop_cursor: bool,
921 },
922}
923
924/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
925/// a breakpoint on them.
926#[derive(Clone, Copy, Debug, PartialEq, Eq)]
927struct PhantomBreakpointIndicator {
928 display_row: DisplayRow,
929 /// There's a small debounce between hovering over the line and showing the indicator.
930 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
931 is_active: bool,
932 collides_with_existing_breakpoint: bool,
933}
934
935/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
936///
937/// See the [module level documentation](self) for more information.
938pub struct Editor {
939 focus_handle: FocusHandle,
940 last_focused_descendant: Option<WeakFocusHandle>,
941 /// The text buffer being edited
942 buffer: Entity<MultiBuffer>,
943 /// Map of how text in the buffer should be displayed.
944 /// Handles soft wraps, folds, fake inlay text insertions, etc.
945 pub display_map: Entity<DisplayMap>,
946 pub selections: SelectionsCollection,
947 pub scroll_manager: ScrollManager,
948 /// When inline assist editors are linked, they all render cursors because
949 /// typing enters text into each of them, even the ones that aren't focused.
950 pub(crate) show_cursor_when_unfocused: bool,
951 columnar_selection_tail: Option<Anchor>,
952 columnar_display_point: Option<DisplayPoint>,
953 add_selections_state: Option<AddSelectionsState>,
954 select_next_state: Option<SelectNextState>,
955 select_prev_state: Option<SelectNextState>,
956 selection_history: SelectionHistory,
957 defer_selection_effects: bool,
958 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
959 autoclose_regions: Vec<AutocloseRegion>,
960 snippet_stack: InvalidationStack<SnippetState>,
961 select_syntax_node_history: SelectSyntaxNodeHistory,
962 ime_transaction: Option<TransactionId>,
963 pub diagnostics_max_severity: DiagnosticSeverity,
964 active_diagnostics: ActiveDiagnostic,
965 show_inline_diagnostics: bool,
966 inline_diagnostics_update: Task<()>,
967 inline_diagnostics_enabled: bool,
968 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
969 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
970 hard_wrap: Option<usize>,
971
972 // TODO: make this a access method
973 pub project: Option<Entity<Project>>,
974 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
975 completion_provider: Option<Rc<dyn CompletionProvider>>,
976 collaboration_hub: Option<Box<dyn CollaborationHub>>,
977 blink_manager: Entity<BlinkManager>,
978 show_cursor_names: bool,
979 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
980 pub show_local_selections: bool,
981 mode: EditorMode,
982 show_breadcrumbs: bool,
983 show_gutter: bool,
984 show_scrollbars: ScrollbarAxes,
985 minimap_visibility: MinimapVisibility,
986 offset_content: bool,
987 disable_expand_excerpt_buttons: bool,
988 show_line_numbers: Option<bool>,
989 use_relative_line_numbers: Option<bool>,
990 show_git_diff_gutter: Option<bool>,
991 show_code_actions: Option<bool>,
992 show_runnables: Option<bool>,
993 show_breakpoints: Option<bool>,
994 show_wrap_guides: Option<bool>,
995 show_indent_guides: Option<bool>,
996 placeholder_text: Option<Arc<str>>,
997 highlight_order: usize,
998 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
999 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
1000 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1001 scrollbar_marker_state: ScrollbarMarkerState,
1002 active_indent_guides_state: ActiveIndentGuidesState,
1003 nav_history: Option<ItemNavHistory>,
1004 context_menu: RefCell<Option<CodeContextMenu>>,
1005 context_menu_options: Option<ContextMenuOptions>,
1006 mouse_context_menu: Option<MouseContextMenu>,
1007 completion_tasks: Vec<(CompletionId, Task<()>)>,
1008 inline_blame_popover: Option<InlineBlamePopover>,
1009 signature_help_state: SignatureHelpState,
1010 auto_signature_help: Option<bool>,
1011 find_all_references_task_sources: Vec<Anchor>,
1012 next_completion_id: CompletionId,
1013 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1014 code_actions_task: Option<Task<Result<()>>>,
1015 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1016 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1017 document_highlights_task: Option<Task<()>>,
1018 linked_editing_range_task: Option<Task<Option<()>>>,
1019 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1020 pending_rename: Option<RenameState>,
1021 searchable: bool,
1022 cursor_shape: CursorShape,
1023 current_line_highlight: Option<CurrentLineHighlight>,
1024 collapse_matches: bool,
1025 autoindent_mode: Option<AutoindentMode>,
1026 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1027 input_enabled: bool,
1028 use_modal_editing: bool,
1029 read_only: bool,
1030 leader_id: Option<CollaboratorId>,
1031 remote_id: Option<ViewId>,
1032 pub hover_state: HoverState,
1033 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1034 gutter_hovered: bool,
1035 hovered_link_state: Option<HoveredLinkState>,
1036 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1037 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1038 active_inline_completion: Option<InlineCompletionState>,
1039 /// Used to prevent flickering as the user types while the menu is open
1040 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1041 edit_prediction_settings: EditPredictionSettings,
1042 inline_completions_hidden_for_vim_mode: bool,
1043 show_inline_completions_override: Option<bool>,
1044 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1045 edit_prediction_preview: EditPredictionPreview,
1046 edit_prediction_indent_conflict: bool,
1047 edit_prediction_requires_modifier_in_indent_conflict: bool,
1048 inlay_hint_cache: InlayHintCache,
1049 next_inlay_id: usize,
1050 _subscriptions: Vec<Subscription>,
1051 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1052 gutter_dimensions: GutterDimensions,
1053 style: Option<EditorStyle>,
1054 text_style_refinement: Option<TextStyleRefinement>,
1055 next_editor_action_id: EditorActionId,
1056 editor_actions: Rc<
1057 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1058 >,
1059 use_autoclose: bool,
1060 use_auto_surround: bool,
1061 auto_replace_emoji_shortcode: bool,
1062 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1063 show_git_blame_gutter: bool,
1064 show_git_blame_inline: bool,
1065 show_git_blame_inline_delay_task: Option<Task<()>>,
1066 git_blame_inline_enabled: bool,
1067 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1068 serialize_dirty_buffers: bool,
1069 show_selection_menu: Option<bool>,
1070 blame: Option<Entity<GitBlame>>,
1071 blame_subscription: Option<Subscription>,
1072 custom_context_menu: Option<
1073 Box<
1074 dyn 'static
1075 + Fn(
1076 &mut Self,
1077 DisplayPoint,
1078 &mut Window,
1079 &mut Context<Self>,
1080 ) -> Option<Entity<ui::ContextMenu>>,
1081 >,
1082 >,
1083 last_bounds: Option<Bounds<Pixels>>,
1084 last_position_map: Option<Rc<PositionMap>>,
1085 expect_bounds_change: Option<Bounds<Pixels>>,
1086 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1087 tasks_update_task: Option<Task<()>>,
1088 breakpoint_store: Option<Entity<BreakpointStore>>,
1089 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1090 pull_diagnostics_task: Task<()>,
1091 in_project_search: bool,
1092 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1093 breadcrumb_header: Option<String>,
1094 focused_block: Option<FocusedBlock>,
1095 next_scroll_position: NextScrollCursorCenterTopBottom,
1096 addons: HashMap<TypeId, Box<dyn Addon>>,
1097 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1098 load_diff_task: Option<Shared<Task<()>>>,
1099 /// Whether we are temporarily displaying a diff other than git's
1100 temporary_diff_override: bool,
1101 selection_mark_mode: bool,
1102 toggle_fold_multiple_buffers: Task<()>,
1103 _scroll_cursor_center_top_bottom_task: Task<()>,
1104 serialize_selections: Task<()>,
1105 serialize_folds: Task<()>,
1106 mouse_cursor_hidden: bool,
1107 minimap: Option<Entity<Self>>,
1108 hide_mouse_mode: HideMouseMode,
1109 pub change_list: ChangeList,
1110 inline_value_cache: InlineValueCache,
1111 selection_drag_state: SelectionDragState,
1112 drag_and_drop_selection_enabled: bool,
1113}
1114
1115#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1116enum NextScrollCursorCenterTopBottom {
1117 #[default]
1118 Center,
1119 Top,
1120 Bottom,
1121}
1122
1123impl NextScrollCursorCenterTopBottom {
1124 fn next(&self) -> Self {
1125 match self {
1126 Self::Center => Self::Top,
1127 Self::Top => Self::Bottom,
1128 Self::Bottom => Self::Center,
1129 }
1130 }
1131}
1132
1133#[derive(Clone)]
1134pub struct EditorSnapshot {
1135 pub mode: EditorMode,
1136 show_gutter: bool,
1137 show_line_numbers: Option<bool>,
1138 show_git_diff_gutter: Option<bool>,
1139 show_code_actions: Option<bool>,
1140 show_runnables: Option<bool>,
1141 show_breakpoints: Option<bool>,
1142 git_blame_gutter_max_author_length: Option<usize>,
1143 pub display_snapshot: DisplaySnapshot,
1144 pub placeholder_text: Option<Arc<str>>,
1145 is_focused: bool,
1146 scroll_anchor: ScrollAnchor,
1147 ongoing_scroll: OngoingScroll,
1148 current_line_highlight: CurrentLineHighlight,
1149 gutter_hovered: bool,
1150}
1151
1152#[derive(Default, Debug, Clone, Copy)]
1153pub struct GutterDimensions {
1154 pub left_padding: Pixels,
1155 pub right_padding: Pixels,
1156 pub width: Pixels,
1157 pub margin: Pixels,
1158 pub git_blame_entries_width: Option<Pixels>,
1159}
1160
1161impl GutterDimensions {
1162 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1163 Self {
1164 margin: Self::default_gutter_margin(font_id, font_size, cx),
1165 ..Default::default()
1166 }
1167 }
1168
1169 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1170 -cx.text_system().descent(font_id, font_size)
1171 }
1172 /// The full width of the space taken up by the gutter.
1173 pub fn full_width(&self) -> Pixels {
1174 self.margin + self.width
1175 }
1176
1177 /// The width of the space reserved for the fold indicators,
1178 /// use alongside 'justify_end' and `gutter_width` to
1179 /// right align content with the line numbers
1180 pub fn fold_area_width(&self) -> Pixels {
1181 self.margin + self.right_padding
1182 }
1183}
1184
1185#[derive(Debug)]
1186pub struct RemoteSelection {
1187 pub replica_id: ReplicaId,
1188 pub selection: Selection<Anchor>,
1189 pub cursor_shape: CursorShape,
1190 pub collaborator_id: CollaboratorId,
1191 pub line_mode: bool,
1192 pub user_name: Option<SharedString>,
1193 pub color: PlayerColor,
1194}
1195
1196#[derive(Clone, Debug)]
1197struct SelectionHistoryEntry {
1198 selections: Arc<[Selection<Anchor>]>,
1199 select_next_state: Option<SelectNextState>,
1200 select_prev_state: Option<SelectNextState>,
1201 add_selections_state: Option<AddSelectionsState>,
1202}
1203
1204#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1205enum SelectionHistoryMode {
1206 Normal,
1207 Undoing,
1208 Redoing,
1209 Skipping,
1210}
1211
1212#[derive(Clone, PartialEq, Eq, Hash)]
1213struct HoveredCursor {
1214 replica_id: u16,
1215 selection_id: usize,
1216}
1217
1218impl Default for SelectionHistoryMode {
1219 fn default() -> Self {
1220 Self::Normal
1221 }
1222}
1223
1224struct DeferredSelectionEffectsState {
1225 changed: bool,
1226 should_update_completions: bool,
1227 autoscroll: Option<Autoscroll>,
1228 old_cursor_position: Anchor,
1229 history_entry: SelectionHistoryEntry,
1230}
1231
1232#[derive(Default)]
1233struct SelectionHistory {
1234 #[allow(clippy::type_complexity)]
1235 selections_by_transaction:
1236 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1237 mode: SelectionHistoryMode,
1238 undo_stack: VecDeque<SelectionHistoryEntry>,
1239 redo_stack: VecDeque<SelectionHistoryEntry>,
1240}
1241
1242impl SelectionHistory {
1243 #[track_caller]
1244 fn insert_transaction(
1245 &mut self,
1246 transaction_id: TransactionId,
1247 selections: Arc<[Selection<Anchor>]>,
1248 ) {
1249 if selections.is_empty() {
1250 log::error!(
1251 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1252 std::panic::Location::caller()
1253 );
1254 return;
1255 }
1256 self.selections_by_transaction
1257 .insert(transaction_id, (selections, None));
1258 }
1259
1260 #[allow(clippy::type_complexity)]
1261 fn transaction(
1262 &self,
1263 transaction_id: TransactionId,
1264 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1265 self.selections_by_transaction.get(&transaction_id)
1266 }
1267
1268 #[allow(clippy::type_complexity)]
1269 fn transaction_mut(
1270 &mut self,
1271 transaction_id: TransactionId,
1272 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1273 self.selections_by_transaction.get_mut(&transaction_id)
1274 }
1275
1276 fn push(&mut self, entry: SelectionHistoryEntry) {
1277 if !entry.selections.is_empty() {
1278 match self.mode {
1279 SelectionHistoryMode::Normal => {
1280 self.push_undo(entry);
1281 self.redo_stack.clear();
1282 }
1283 SelectionHistoryMode::Undoing => self.push_redo(entry),
1284 SelectionHistoryMode::Redoing => self.push_undo(entry),
1285 SelectionHistoryMode::Skipping => {}
1286 }
1287 }
1288 }
1289
1290 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1291 if self
1292 .undo_stack
1293 .back()
1294 .map_or(true, |e| e.selections != entry.selections)
1295 {
1296 self.undo_stack.push_back(entry);
1297 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1298 self.undo_stack.pop_front();
1299 }
1300 }
1301 }
1302
1303 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1304 if self
1305 .redo_stack
1306 .back()
1307 .map_or(true, |e| e.selections != entry.selections)
1308 {
1309 self.redo_stack.push_back(entry);
1310 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1311 self.redo_stack.pop_front();
1312 }
1313 }
1314 }
1315}
1316
1317#[derive(Clone, Copy)]
1318pub struct RowHighlightOptions {
1319 pub autoscroll: bool,
1320 pub include_gutter: bool,
1321}
1322
1323impl Default for RowHighlightOptions {
1324 fn default() -> Self {
1325 Self {
1326 autoscroll: Default::default(),
1327 include_gutter: true,
1328 }
1329 }
1330}
1331
1332struct RowHighlight {
1333 index: usize,
1334 range: Range<Anchor>,
1335 color: Hsla,
1336 options: RowHighlightOptions,
1337 type_id: TypeId,
1338}
1339
1340#[derive(Clone, Debug)]
1341struct AddSelectionsState {
1342 groups: Vec<AddSelectionsGroup>,
1343}
1344
1345#[derive(Clone, Debug)]
1346struct AddSelectionsGroup {
1347 above: bool,
1348 stack: Vec<usize>,
1349}
1350
1351#[derive(Clone)]
1352struct SelectNextState {
1353 query: AhoCorasick,
1354 wordwise: bool,
1355 done: bool,
1356}
1357
1358impl std::fmt::Debug for SelectNextState {
1359 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1360 f.debug_struct(std::any::type_name::<Self>())
1361 .field("wordwise", &self.wordwise)
1362 .field("done", &self.done)
1363 .finish()
1364 }
1365}
1366
1367#[derive(Debug)]
1368struct AutocloseRegion {
1369 selection_id: usize,
1370 range: Range<Anchor>,
1371 pair: BracketPair,
1372}
1373
1374#[derive(Debug)]
1375struct SnippetState {
1376 ranges: Vec<Vec<Range<Anchor>>>,
1377 active_index: usize,
1378 choices: Vec<Option<Vec<String>>>,
1379}
1380
1381#[doc(hidden)]
1382pub struct RenameState {
1383 pub range: Range<Anchor>,
1384 pub old_name: Arc<str>,
1385 pub editor: Entity<Editor>,
1386 block_id: CustomBlockId,
1387}
1388
1389struct InvalidationStack<T>(Vec<T>);
1390
1391struct RegisteredInlineCompletionProvider {
1392 provider: Arc<dyn InlineCompletionProviderHandle>,
1393 _subscription: Subscription,
1394}
1395
1396#[derive(Debug, PartialEq, Eq)]
1397pub struct ActiveDiagnosticGroup {
1398 pub active_range: Range<Anchor>,
1399 pub active_message: String,
1400 pub group_id: usize,
1401 pub blocks: HashSet<CustomBlockId>,
1402}
1403
1404#[derive(Debug, PartialEq, Eq)]
1405
1406pub(crate) enum ActiveDiagnostic {
1407 None,
1408 All,
1409 Group(ActiveDiagnosticGroup),
1410}
1411
1412#[derive(Serialize, Deserialize, Clone, Debug)]
1413pub struct ClipboardSelection {
1414 /// The number of bytes in this selection.
1415 pub len: usize,
1416 /// Whether this was a full-line selection.
1417 pub is_entire_line: bool,
1418 /// The indentation of the first line when this content was originally copied.
1419 pub first_line_indent: u32,
1420}
1421
1422// selections, scroll behavior, was newest selection reversed
1423type SelectSyntaxNodeHistoryState = (
1424 Box<[Selection<usize>]>,
1425 SelectSyntaxNodeScrollBehavior,
1426 bool,
1427);
1428
1429#[derive(Default)]
1430struct SelectSyntaxNodeHistory {
1431 stack: Vec<SelectSyntaxNodeHistoryState>,
1432 // disable temporarily to allow changing selections without losing the stack
1433 pub disable_clearing: bool,
1434}
1435
1436impl SelectSyntaxNodeHistory {
1437 pub fn try_clear(&mut self) {
1438 if !self.disable_clearing {
1439 self.stack.clear();
1440 }
1441 }
1442
1443 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1444 self.stack.push(selection);
1445 }
1446
1447 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1448 self.stack.pop()
1449 }
1450}
1451
1452enum SelectSyntaxNodeScrollBehavior {
1453 CursorTop,
1454 FitSelection,
1455 CursorBottom,
1456}
1457
1458#[derive(Debug)]
1459pub(crate) struct NavigationData {
1460 cursor_anchor: Anchor,
1461 cursor_position: Point,
1462 scroll_anchor: ScrollAnchor,
1463 scroll_top_row: u32,
1464}
1465
1466#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1467pub enum GotoDefinitionKind {
1468 Symbol,
1469 Declaration,
1470 Type,
1471 Implementation,
1472}
1473
1474#[derive(Debug, Clone)]
1475enum InlayHintRefreshReason {
1476 ModifiersChanged(bool),
1477 Toggle(bool),
1478 SettingsChange(InlayHintSettings),
1479 NewLinesShown,
1480 BufferEdited(HashSet<Arc<Language>>),
1481 RefreshRequested,
1482 ExcerptsRemoved(Vec<ExcerptId>),
1483}
1484
1485impl InlayHintRefreshReason {
1486 fn description(&self) -> &'static str {
1487 match self {
1488 Self::ModifiersChanged(_) => "modifiers changed",
1489 Self::Toggle(_) => "toggle",
1490 Self::SettingsChange(_) => "settings change",
1491 Self::NewLinesShown => "new lines shown",
1492 Self::BufferEdited(_) => "buffer edited",
1493 Self::RefreshRequested => "refresh requested",
1494 Self::ExcerptsRemoved(_) => "excerpts removed",
1495 }
1496 }
1497}
1498
1499pub enum FormatTarget {
1500 Buffers,
1501 Ranges(Vec<Range<MultiBufferPoint>>),
1502}
1503
1504pub(crate) struct FocusedBlock {
1505 id: BlockId,
1506 focus_handle: WeakFocusHandle,
1507}
1508
1509#[derive(Clone)]
1510enum JumpData {
1511 MultiBufferRow {
1512 row: MultiBufferRow,
1513 line_offset_from_top: u32,
1514 },
1515 MultiBufferPoint {
1516 excerpt_id: ExcerptId,
1517 position: Point,
1518 anchor: text::Anchor,
1519 line_offset_from_top: u32,
1520 },
1521}
1522
1523pub enum MultibufferSelectionMode {
1524 First,
1525 All,
1526}
1527
1528#[derive(Clone, Copy, Debug, Default)]
1529pub struct RewrapOptions {
1530 pub override_language_settings: bool,
1531 pub preserve_existing_whitespace: bool,
1532}
1533
1534impl Editor {
1535 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1536 let buffer = cx.new(|cx| Buffer::local("", cx));
1537 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1538 Self::new(
1539 EditorMode::SingleLine { auto_width: false },
1540 buffer,
1541 None,
1542 window,
1543 cx,
1544 )
1545 }
1546
1547 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1548 let buffer = cx.new(|cx| Buffer::local("", cx));
1549 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1550 Self::new(EditorMode::full(), buffer, None, window, cx)
1551 }
1552
1553 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1554 let buffer = cx.new(|cx| Buffer::local("", cx));
1555 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1556 Self::new(
1557 EditorMode::SingleLine { auto_width: true },
1558 buffer,
1559 None,
1560 window,
1561 cx,
1562 )
1563 }
1564
1565 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1566 let buffer = cx.new(|cx| Buffer::local("", cx));
1567 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1568 Self::new(
1569 EditorMode::AutoHeight { max_lines },
1570 buffer,
1571 None,
1572 window,
1573 cx,
1574 )
1575 }
1576
1577 pub fn for_buffer(
1578 buffer: Entity<Buffer>,
1579 project: Option<Entity<Project>>,
1580 window: &mut Window,
1581 cx: &mut Context<Self>,
1582 ) -> Self {
1583 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1584 Self::new(EditorMode::full(), buffer, project, window, cx)
1585 }
1586
1587 pub fn for_multibuffer(
1588 buffer: Entity<MultiBuffer>,
1589 project: Option<Entity<Project>>,
1590 window: &mut Window,
1591 cx: &mut Context<Self>,
1592 ) -> Self {
1593 Self::new(EditorMode::full(), buffer, project, window, cx)
1594 }
1595
1596 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1597 let mut clone = Self::new(
1598 self.mode.clone(),
1599 self.buffer.clone(),
1600 self.project.clone(),
1601 window,
1602 cx,
1603 );
1604 self.display_map.update(cx, |display_map, cx| {
1605 let snapshot = display_map.snapshot(cx);
1606 clone.display_map.update(cx, |display_map, cx| {
1607 display_map.set_state(&snapshot, cx);
1608 });
1609 });
1610 clone.folds_did_change(cx);
1611 clone.selections.clone_state(&self.selections);
1612 clone.scroll_manager.clone_state(&self.scroll_manager);
1613 clone.searchable = self.searchable;
1614 clone.read_only = self.read_only;
1615 clone
1616 }
1617
1618 pub fn new(
1619 mode: EditorMode,
1620 buffer: Entity<MultiBuffer>,
1621 project: Option<Entity<Project>>,
1622 window: &mut Window,
1623 cx: &mut Context<Self>,
1624 ) -> Self {
1625 Editor::new_internal(mode, buffer, project, None, window, cx)
1626 }
1627
1628 fn new_internal(
1629 mode: EditorMode,
1630 buffer: Entity<MultiBuffer>,
1631 project: Option<Entity<Project>>,
1632 display_map: Option<Entity<DisplayMap>>,
1633 window: &mut Window,
1634 cx: &mut Context<Self>,
1635 ) -> Self {
1636 debug_assert!(
1637 display_map.is_none() || mode.is_minimap(),
1638 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1639 );
1640
1641 let full_mode = mode.is_full();
1642 let diagnostics_max_severity = if full_mode {
1643 EditorSettings::get_global(cx)
1644 .diagnostics_max_severity
1645 .unwrap_or(DiagnosticSeverity::Hint)
1646 } else {
1647 DiagnosticSeverity::Off
1648 };
1649 let style = window.text_style();
1650 let font_size = style.font_size.to_pixels(window.rem_size());
1651 let editor = cx.entity().downgrade();
1652 let fold_placeholder = FoldPlaceholder {
1653 constrain_width: true,
1654 render: Arc::new(move |fold_id, fold_range, cx| {
1655 let editor = editor.clone();
1656 div()
1657 .id(fold_id)
1658 .bg(cx.theme().colors().ghost_element_background)
1659 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1660 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1661 .rounded_xs()
1662 .size_full()
1663 .cursor_pointer()
1664 .child("⋯")
1665 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1666 .on_click(move |_, _window, cx| {
1667 editor
1668 .update(cx, |editor, cx| {
1669 editor.unfold_ranges(
1670 &[fold_range.start..fold_range.end],
1671 true,
1672 false,
1673 cx,
1674 );
1675 cx.stop_propagation();
1676 })
1677 .ok();
1678 })
1679 .into_any()
1680 }),
1681 merge_adjacent: true,
1682 ..FoldPlaceholder::default()
1683 };
1684 let display_map = display_map.unwrap_or_else(|| {
1685 cx.new(|cx| {
1686 DisplayMap::new(
1687 buffer.clone(),
1688 style.font(),
1689 font_size,
1690 None,
1691 FILE_HEADER_HEIGHT,
1692 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1693 fold_placeholder,
1694 diagnostics_max_severity,
1695 cx,
1696 )
1697 })
1698 });
1699
1700 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1701
1702 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1703
1704 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1705 .then(|| language_settings::SoftWrap::None);
1706
1707 let mut project_subscriptions = Vec::new();
1708 if mode.is_full() {
1709 if let Some(project) = project.as_ref() {
1710 project_subscriptions.push(cx.subscribe_in(
1711 project,
1712 window,
1713 |editor, _, event, window, cx| match event {
1714 project::Event::RefreshCodeLens => {
1715 // we always query lens with actions, without storing them, always refreshing them
1716 }
1717 project::Event::RefreshInlayHints => {
1718 editor
1719 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1720 }
1721 project::Event::LanguageServerAdded(..)
1722 | project::Event::LanguageServerRemoved(..) => {
1723 if editor.tasks_update_task.is_none() {
1724 editor.tasks_update_task =
1725 Some(editor.refresh_runnables(window, cx));
1726 }
1727 editor.pull_diagnostics(None, window, cx);
1728 }
1729 project::Event::SnippetEdit(id, snippet_edits) => {
1730 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1731 let focus_handle = editor.focus_handle(cx);
1732 if focus_handle.is_focused(window) {
1733 let snapshot = buffer.read(cx).snapshot();
1734 for (range, snippet) in snippet_edits {
1735 let editor_range =
1736 language::range_from_lsp(*range).to_offset(&snapshot);
1737 editor
1738 .insert_snippet(
1739 &[editor_range],
1740 snippet.clone(),
1741 window,
1742 cx,
1743 )
1744 .ok();
1745 }
1746 }
1747 }
1748 }
1749 _ => {}
1750 },
1751 ));
1752 if let Some(task_inventory) = project
1753 .read(cx)
1754 .task_store()
1755 .read(cx)
1756 .task_inventory()
1757 .cloned()
1758 {
1759 project_subscriptions.push(cx.observe_in(
1760 &task_inventory,
1761 window,
1762 |editor, _, window, cx| {
1763 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1764 },
1765 ));
1766 };
1767
1768 project_subscriptions.push(cx.subscribe_in(
1769 &project.read(cx).breakpoint_store(),
1770 window,
1771 |editor, _, event, window, cx| match event {
1772 BreakpointStoreEvent::ClearDebugLines => {
1773 editor.clear_row_highlights::<ActiveDebugLine>();
1774 editor.refresh_inline_values(cx);
1775 }
1776 BreakpointStoreEvent::SetDebugLine => {
1777 if editor.go_to_active_debug_line(window, cx) {
1778 cx.stop_propagation();
1779 }
1780
1781 editor.refresh_inline_values(cx);
1782 }
1783 _ => {}
1784 },
1785 ));
1786 }
1787 }
1788
1789 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1790
1791 let inlay_hint_settings =
1792 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1793 let focus_handle = cx.focus_handle();
1794 cx.on_focus(&focus_handle, window, Self::handle_focus)
1795 .detach();
1796 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1797 .detach();
1798 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1799 .detach();
1800 cx.on_blur(&focus_handle, window, Self::handle_blur)
1801 .detach();
1802 cx.observe_pending_input(window, Self::observe_pending_input)
1803 .detach();
1804
1805 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1806 Some(false)
1807 } else {
1808 None
1809 };
1810
1811 let breakpoint_store = match (&mode, project.as_ref()) {
1812 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1813 _ => None,
1814 };
1815
1816 let mut code_action_providers = Vec::new();
1817 let mut load_uncommitted_diff = None;
1818 if let Some(project) = project.clone() {
1819 load_uncommitted_diff = Some(
1820 update_uncommitted_diff_for_buffer(
1821 cx.entity(),
1822 &project,
1823 buffer.read(cx).all_buffers(),
1824 buffer.clone(),
1825 cx,
1826 )
1827 .shared(),
1828 );
1829 code_action_providers.push(Rc::new(project) as Rc<_>);
1830 }
1831
1832 let mut editor = Self {
1833 focus_handle,
1834 show_cursor_when_unfocused: false,
1835 last_focused_descendant: None,
1836 buffer: buffer.clone(),
1837 display_map: display_map.clone(),
1838 selections,
1839 scroll_manager: ScrollManager::new(cx),
1840 columnar_selection_tail: None,
1841 columnar_display_point: None,
1842 add_selections_state: None,
1843 select_next_state: None,
1844 select_prev_state: None,
1845 selection_history: SelectionHistory::default(),
1846 defer_selection_effects: false,
1847 deferred_selection_effects_state: None,
1848 autoclose_regions: Vec::new(),
1849 snippet_stack: InvalidationStack::default(),
1850 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1851 ime_transaction: None,
1852 active_diagnostics: ActiveDiagnostic::None,
1853 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1854 inline_diagnostics_update: Task::ready(()),
1855 inline_diagnostics: Vec::new(),
1856 soft_wrap_mode_override,
1857 diagnostics_max_severity,
1858 hard_wrap: None,
1859 completion_provider: project.clone().map(|project| Rc::new(project) as _),
1860 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1861 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1862 project,
1863 blink_manager: blink_manager.clone(),
1864 show_local_selections: true,
1865 show_scrollbars: ScrollbarAxes {
1866 horizontal: full_mode,
1867 vertical: full_mode,
1868 },
1869 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1870 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
1871 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1872 show_gutter: mode.is_full(),
1873 show_line_numbers: None,
1874 use_relative_line_numbers: None,
1875 disable_expand_excerpt_buttons: false,
1876 show_git_diff_gutter: None,
1877 show_code_actions: None,
1878 show_runnables: None,
1879 show_breakpoints: None,
1880 show_wrap_guides: None,
1881 show_indent_guides,
1882 placeholder_text: None,
1883 highlight_order: 0,
1884 highlighted_rows: HashMap::default(),
1885 background_highlights: TreeMap::default(),
1886 gutter_highlights: TreeMap::default(),
1887 scrollbar_marker_state: ScrollbarMarkerState::default(),
1888 active_indent_guides_state: ActiveIndentGuidesState::default(),
1889 nav_history: None,
1890 context_menu: RefCell::new(None),
1891 context_menu_options: None,
1892 mouse_context_menu: None,
1893 completion_tasks: Vec::new(),
1894 inline_blame_popover: None,
1895 signature_help_state: SignatureHelpState::default(),
1896 auto_signature_help: None,
1897 find_all_references_task_sources: Vec::new(),
1898 next_completion_id: 0,
1899 next_inlay_id: 0,
1900 code_action_providers,
1901 available_code_actions: None,
1902 code_actions_task: None,
1903 quick_selection_highlight_task: None,
1904 debounced_selection_highlight_task: None,
1905 document_highlights_task: None,
1906 linked_editing_range_task: None,
1907 pending_rename: None,
1908 searchable: true,
1909 cursor_shape: EditorSettings::get_global(cx)
1910 .cursor_shape
1911 .unwrap_or_default(),
1912 current_line_highlight: None,
1913 autoindent_mode: Some(AutoindentMode::EachLine),
1914 collapse_matches: false,
1915 workspace: None,
1916 input_enabled: true,
1917 use_modal_editing: mode.is_full(),
1918 read_only: mode.is_minimap(),
1919 use_autoclose: true,
1920 use_auto_surround: true,
1921 auto_replace_emoji_shortcode: false,
1922 jsx_tag_auto_close_enabled_in_any_buffer: false,
1923 leader_id: None,
1924 remote_id: None,
1925 hover_state: HoverState::default(),
1926 pending_mouse_down: None,
1927 hovered_link_state: None,
1928 edit_prediction_provider: None,
1929 active_inline_completion: None,
1930 stale_inline_completion_in_menu: None,
1931 edit_prediction_preview: EditPredictionPreview::Inactive {
1932 released_too_fast: false,
1933 },
1934 inline_diagnostics_enabled: mode.is_full(),
1935 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1936 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1937
1938 gutter_hovered: false,
1939 pixel_position_of_newest_cursor: None,
1940 last_bounds: None,
1941 last_position_map: None,
1942 expect_bounds_change: None,
1943 gutter_dimensions: GutterDimensions::default(),
1944 style: None,
1945 show_cursor_names: false,
1946 hovered_cursors: HashMap::default(),
1947 next_editor_action_id: EditorActionId::default(),
1948 editor_actions: Rc::default(),
1949 inline_completions_hidden_for_vim_mode: false,
1950 show_inline_completions_override: None,
1951 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1952 edit_prediction_settings: EditPredictionSettings::Disabled,
1953 edit_prediction_indent_conflict: false,
1954 edit_prediction_requires_modifier_in_indent_conflict: true,
1955 custom_context_menu: None,
1956 show_git_blame_gutter: false,
1957 show_git_blame_inline: false,
1958 show_selection_menu: None,
1959 show_git_blame_inline_delay_task: None,
1960 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1961 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1962 serialize_dirty_buffers: !mode.is_minimap()
1963 && ProjectSettings::get_global(cx)
1964 .session
1965 .restore_unsaved_buffers,
1966 blame: None,
1967 blame_subscription: None,
1968 tasks: BTreeMap::default(),
1969
1970 breakpoint_store,
1971 gutter_breakpoint_indicator: (None, None),
1972 _subscriptions: vec![
1973 cx.observe(&buffer, Self::on_buffer_changed),
1974 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1975 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1976 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1977 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1978 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1979 cx.observe_window_activation(window, |editor, window, cx| {
1980 let active = window.is_window_active();
1981 editor.blink_manager.update(cx, |blink_manager, cx| {
1982 if active {
1983 blink_manager.enable(cx);
1984 } else {
1985 blink_manager.disable(cx);
1986 }
1987 });
1988 if active {
1989 editor.show_mouse_cursor();
1990 }
1991 }),
1992 ],
1993 tasks_update_task: None,
1994 pull_diagnostics_task: Task::ready(()),
1995 linked_edit_ranges: Default::default(),
1996 in_project_search: false,
1997 previous_search_ranges: None,
1998 breadcrumb_header: None,
1999 focused_block: None,
2000 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2001 addons: HashMap::default(),
2002 registered_buffers: HashMap::default(),
2003 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2004 selection_mark_mode: false,
2005 toggle_fold_multiple_buffers: Task::ready(()),
2006 serialize_selections: Task::ready(()),
2007 serialize_folds: Task::ready(()),
2008 text_style_refinement: None,
2009 load_diff_task: load_uncommitted_diff,
2010 temporary_diff_override: false,
2011 mouse_cursor_hidden: false,
2012 minimap: None,
2013 hide_mouse_mode: EditorSettings::get_global(cx)
2014 .hide_mouse
2015 .unwrap_or_default(),
2016 change_list: ChangeList::new(),
2017 mode,
2018 selection_drag_state: SelectionDragState::None,
2019 drag_and_drop_selection_enabled: EditorSettings::get_global(cx).drag_and_drop_selection,
2020 };
2021 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2022 editor
2023 ._subscriptions
2024 .push(cx.observe(breakpoints, |_, _, cx| {
2025 cx.notify();
2026 }));
2027 }
2028 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2029 editor._subscriptions.extend(project_subscriptions);
2030
2031 editor._subscriptions.push(cx.subscribe_in(
2032 &cx.entity(),
2033 window,
2034 |editor, _, e: &EditorEvent, window, cx| match e {
2035 EditorEvent::ScrollPositionChanged { local, .. } => {
2036 if *local {
2037 let new_anchor = editor.scroll_manager.anchor();
2038 let snapshot = editor.snapshot(window, cx);
2039 editor.update_restoration_data(cx, move |data| {
2040 data.scroll_position = (
2041 new_anchor.top_row(&snapshot.buffer_snapshot),
2042 new_anchor.offset,
2043 );
2044 });
2045 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2046 editor.inline_blame_popover.take();
2047 }
2048 }
2049 EditorEvent::Edited { .. } => {
2050 if !vim_enabled(cx) {
2051 let (map, selections) = editor.selections.all_adjusted_display(cx);
2052 let pop_state = editor
2053 .change_list
2054 .last()
2055 .map(|previous| {
2056 previous.len() == selections.len()
2057 && previous.iter().enumerate().all(|(ix, p)| {
2058 p.to_display_point(&map).row()
2059 == selections[ix].head().row()
2060 })
2061 })
2062 .unwrap_or(false);
2063 let new_positions = selections
2064 .into_iter()
2065 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2066 .collect();
2067 editor
2068 .change_list
2069 .push_to_change_list(pop_state, new_positions);
2070 }
2071 }
2072 _ => (),
2073 },
2074 ));
2075
2076 if let Some(dap_store) = editor
2077 .project
2078 .as_ref()
2079 .map(|project| project.read(cx).dap_store())
2080 {
2081 let weak_editor = cx.weak_entity();
2082
2083 editor
2084 ._subscriptions
2085 .push(
2086 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2087 let session_entity = cx.entity();
2088 weak_editor
2089 .update(cx, |editor, cx| {
2090 editor._subscriptions.push(
2091 cx.subscribe(&session_entity, Self::on_debug_session_event),
2092 );
2093 })
2094 .ok();
2095 }),
2096 );
2097
2098 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2099 editor
2100 ._subscriptions
2101 .push(cx.subscribe(&session, Self::on_debug_session_event));
2102 }
2103 }
2104
2105 // skip adding the initial selection to selection history
2106 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2107 editor.end_selection(window, cx);
2108 editor.selection_history.mode = SelectionHistoryMode::Normal;
2109
2110 editor.scroll_manager.show_scrollbars(window, cx);
2111 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2112
2113 if full_mode {
2114 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2115 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2116
2117 if editor.git_blame_inline_enabled {
2118 editor.start_git_blame_inline(false, window, cx);
2119 }
2120
2121 editor.go_to_active_debug_line(window, cx);
2122
2123 if let Some(buffer) = buffer.read(cx).as_singleton() {
2124 if let Some(project) = editor.project.as_ref() {
2125 let handle = project.update(cx, |project, cx| {
2126 project.register_buffer_with_language_servers(&buffer, cx)
2127 });
2128 editor
2129 .registered_buffers
2130 .insert(buffer.read(cx).remote_id(), handle);
2131 }
2132 }
2133
2134 editor.minimap =
2135 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2136 editor.pull_diagnostics(None, window, cx);
2137 }
2138
2139 editor.report_editor_event("Editor Opened", None, cx);
2140 editor
2141 }
2142
2143 pub fn deploy_mouse_context_menu(
2144 &mut self,
2145 position: gpui::Point<Pixels>,
2146 context_menu: Entity<ContextMenu>,
2147 window: &mut Window,
2148 cx: &mut Context<Self>,
2149 ) {
2150 self.mouse_context_menu = Some(MouseContextMenu::new(
2151 self,
2152 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2153 context_menu,
2154 window,
2155 cx,
2156 ));
2157 }
2158
2159 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2160 self.mouse_context_menu
2161 .as_ref()
2162 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2163 }
2164
2165 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2166 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2167 }
2168
2169 fn key_context_internal(
2170 &self,
2171 has_active_edit_prediction: bool,
2172 window: &Window,
2173 cx: &App,
2174 ) -> KeyContext {
2175 let mut key_context = KeyContext::new_with_defaults();
2176 key_context.add("Editor");
2177 let mode = match self.mode {
2178 EditorMode::SingleLine { .. } => "single_line",
2179 EditorMode::AutoHeight { .. } => "auto_height",
2180 EditorMode::Minimap { .. } => "minimap",
2181 EditorMode::Full { .. } => "full",
2182 };
2183
2184 if EditorSettings::jupyter_enabled(cx) {
2185 key_context.add("jupyter");
2186 }
2187
2188 key_context.set("mode", mode);
2189 if self.pending_rename.is_some() {
2190 key_context.add("renaming");
2191 }
2192
2193 match self.context_menu.borrow().as_ref() {
2194 Some(CodeContextMenu::Completions(_)) => {
2195 key_context.add("menu");
2196 key_context.add("showing_completions");
2197 }
2198 Some(CodeContextMenu::CodeActions(_)) => {
2199 key_context.add("menu");
2200 key_context.add("showing_code_actions")
2201 }
2202 None => {}
2203 }
2204
2205 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2206 if !self.focus_handle(cx).contains_focused(window, cx)
2207 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2208 {
2209 for addon in self.addons.values() {
2210 addon.extend_key_context(&mut key_context, cx)
2211 }
2212 }
2213
2214 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2215 if let Some(extension) = singleton_buffer
2216 .read(cx)
2217 .file()
2218 .and_then(|file| file.path().extension()?.to_str())
2219 {
2220 key_context.set("extension", extension.to_string());
2221 }
2222 } else {
2223 key_context.add("multibuffer");
2224 }
2225
2226 if has_active_edit_prediction {
2227 if self.edit_prediction_in_conflict() {
2228 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2229 } else {
2230 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2231 key_context.add("copilot_suggestion");
2232 }
2233 }
2234
2235 if self.selection_mark_mode {
2236 key_context.add("selection_mode");
2237 }
2238
2239 key_context
2240 }
2241
2242 fn show_mouse_cursor(&mut self) {
2243 self.mouse_cursor_hidden = false;
2244 }
2245
2246 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2247 self.mouse_cursor_hidden = match origin {
2248 HideMouseCursorOrigin::TypingAction => {
2249 matches!(
2250 self.hide_mouse_mode,
2251 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2252 )
2253 }
2254 HideMouseCursorOrigin::MovementAction => {
2255 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2256 }
2257 };
2258 }
2259
2260 pub fn edit_prediction_in_conflict(&self) -> bool {
2261 if !self.show_edit_predictions_in_menu() {
2262 return false;
2263 }
2264
2265 let showing_completions = self
2266 .context_menu
2267 .borrow()
2268 .as_ref()
2269 .map_or(false, |context| {
2270 matches!(context, CodeContextMenu::Completions(_))
2271 });
2272
2273 showing_completions
2274 || self.edit_prediction_requires_modifier()
2275 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2276 // bindings to insert tab characters.
2277 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2278 }
2279
2280 pub fn accept_edit_prediction_keybind(
2281 &self,
2282 accept_partial: bool,
2283 window: &Window,
2284 cx: &App,
2285 ) -> AcceptEditPredictionBinding {
2286 let key_context = self.key_context_internal(true, window, cx);
2287 let in_conflict = self.edit_prediction_in_conflict();
2288
2289 let bindings = if accept_partial {
2290 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2291 } else {
2292 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2293 };
2294
2295 // TODO: if the binding contains multiple keystrokes, display all of them, not
2296 // just the first one.
2297 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2298 !in_conflict
2299 || binding
2300 .keystrokes()
2301 .first()
2302 .map_or(false, |keystroke| keystroke.modifiers.modified())
2303 }))
2304 }
2305
2306 pub fn new_file(
2307 workspace: &mut Workspace,
2308 _: &workspace::NewFile,
2309 window: &mut Window,
2310 cx: &mut Context<Workspace>,
2311 ) {
2312 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2313 "Failed to create buffer",
2314 window,
2315 cx,
2316 |e, _, _| match e.error_code() {
2317 ErrorCode::RemoteUpgradeRequired => Some(format!(
2318 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2319 e.error_tag("required").unwrap_or("the latest version")
2320 )),
2321 _ => None,
2322 },
2323 );
2324 }
2325
2326 pub fn new_in_workspace(
2327 workspace: &mut Workspace,
2328 window: &mut Window,
2329 cx: &mut Context<Workspace>,
2330 ) -> Task<Result<Entity<Editor>>> {
2331 let project = workspace.project().clone();
2332 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2333
2334 cx.spawn_in(window, async move |workspace, cx| {
2335 let buffer = create.await?;
2336 workspace.update_in(cx, |workspace, window, cx| {
2337 let editor =
2338 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2339 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2340 editor
2341 })
2342 })
2343 }
2344
2345 fn new_file_vertical(
2346 workspace: &mut Workspace,
2347 _: &workspace::NewFileSplitVertical,
2348 window: &mut Window,
2349 cx: &mut Context<Workspace>,
2350 ) {
2351 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2352 }
2353
2354 fn new_file_horizontal(
2355 workspace: &mut Workspace,
2356 _: &workspace::NewFileSplitHorizontal,
2357 window: &mut Window,
2358 cx: &mut Context<Workspace>,
2359 ) {
2360 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2361 }
2362
2363 fn new_file_in_direction(
2364 workspace: &mut Workspace,
2365 direction: SplitDirection,
2366 window: &mut Window,
2367 cx: &mut Context<Workspace>,
2368 ) {
2369 let project = workspace.project().clone();
2370 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2371
2372 cx.spawn_in(window, async move |workspace, cx| {
2373 let buffer = create.await?;
2374 workspace.update_in(cx, move |workspace, window, cx| {
2375 workspace.split_item(
2376 direction,
2377 Box::new(
2378 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2379 ),
2380 window,
2381 cx,
2382 )
2383 })?;
2384 anyhow::Ok(())
2385 })
2386 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2387 match e.error_code() {
2388 ErrorCode::RemoteUpgradeRequired => Some(format!(
2389 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2390 e.error_tag("required").unwrap_or("the latest version")
2391 )),
2392 _ => None,
2393 }
2394 });
2395 }
2396
2397 pub fn leader_id(&self) -> Option<CollaboratorId> {
2398 self.leader_id
2399 }
2400
2401 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2402 &self.buffer
2403 }
2404
2405 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2406 self.workspace.as_ref()?.0.upgrade()
2407 }
2408
2409 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2410 self.buffer().read(cx).title(cx)
2411 }
2412
2413 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2414 let git_blame_gutter_max_author_length = self
2415 .render_git_blame_gutter(cx)
2416 .then(|| {
2417 if let Some(blame) = self.blame.as_ref() {
2418 let max_author_length =
2419 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2420 Some(max_author_length)
2421 } else {
2422 None
2423 }
2424 })
2425 .flatten();
2426
2427 EditorSnapshot {
2428 mode: self.mode.clone(),
2429 show_gutter: self.show_gutter,
2430 show_line_numbers: self.show_line_numbers,
2431 show_git_diff_gutter: self.show_git_diff_gutter,
2432 show_code_actions: self.show_code_actions,
2433 show_runnables: self.show_runnables,
2434 show_breakpoints: self.show_breakpoints,
2435 git_blame_gutter_max_author_length,
2436 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2437 scroll_anchor: self.scroll_manager.anchor(),
2438 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2439 placeholder_text: self.placeholder_text.clone(),
2440 is_focused: self.focus_handle.is_focused(window),
2441 current_line_highlight: self
2442 .current_line_highlight
2443 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2444 gutter_hovered: self.gutter_hovered,
2445 }
2446 }
2447
2448 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2449 self.buffer.read(cx).language_at(point, cx)
2450 }
2451
2452 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2453 self.buffer.read(cx).read(cx).file_at(point).cloned()
2454 }
2455
2456 pub fn active_excerpt(
2457 &self,
2458 cx: &App,
2459 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2460 self.buffer
2461 .read(cx)
2462 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2463 }
2464
2465 pub fn mode(&self) -> &EditorMode {
2466 &self.mode
2467 }
2468
2469 pub fn set_mode(&mut self, mode: EditorMode) {
2470 self.mode = mode;
2471 }
2472
2473 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2474 self.collaboration_hub.as_deref()
2475 }
2476
2477 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2478 self.collaboration_hub = Some(hub);
2479 }
2480
2481 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2482 self.in_project_search = in_project_search;
2483 }
2484
2485 pub fn set_custom_context_menu(
2486 &mut self,
2487 f: impl 'static
2488 + Fn(
2489 &mut Self,
2490 DisplayPoint,
2491 &mut Window,
2492 &mut Context<Self>,
2493 ) -> Option<Entity<ui::ContextMenu>>,
2494 ) {
2495 self.custom_context_menu = Some(Box::new(f))
2496 }
2497
2498 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2499 self.completion_provider = provider;
2500 }
2501
2502 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2503 self.semantics_provider.clone()
2504 }
2505
2506 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2507 self.semantics_provider = provider;
2508 }
2509
2510 pub fn set_edit_prediction_provider<T>(
2511 &mut self,
2512 provider: Option<Entity<T>>,
2513 window: &mut Window,
2514 cx: &mut Context<Self>,
2515 ) where
2516 T: EditPredictionProvider,
2517 {
2518 self.edit_prediction_provider =
2519 provider.map(|provider| RegisteredInlineCompletionProvider {
2520 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2521 if this.focus_handle.is_focused(window) {
2522 this.update_visible_inline_completion(window, cx);
2523 }
2524 }),
2525 provider: Arc::new(provider),
2526 });
2527 self.update_edit_prediction_settings(cx);
2528 self.refresh_inline_completion(false, false, window, cx);
2529 }
2530
2531 pub fn placeholder_text(&self) -> Option<&str> {
2532 self.placeholder_text.as_deref()
2533 }
2534
2535 pub fn set_placeholder_text(
2536 &mut self,
2537 placeholder_text: impl Into<Arc<str>>,
2538 cx: &mut Context<Self>,
2539 ) {
2540 let placeholder_text = Some(placeholder_text.into());
2541 if self.placeholder_text != placeholder_text {
2542 self.placeholder_text = placeholder_text;
2543 cx.notify();
2544 }
2545 }
2546
2547 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2548 self.cursor_shape = cursor_shape;
2549
2550 // Disrupt blink for immediate user feedback that the cursor shape has changed
2551 self.blink_manager.update(cx, BlinkManager::show_cursor);
2552
2553 cx.notify();
2554 }
2555
2556 pub fn set_current_line_highlight(
2557 &mut self,
2558 current_line_highlight: Option<CurrentLineHighlight>,
2559 ) {
2560 self.current_line_highlight = current_line_highlight;
2561 }
2562
2563 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2564 self.collapse_matches = collapse_matches;
2565 }
2566
2567 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2568 let buffers = self.buffer.read(cx).all_buffers();
2569 let Some(project) = self.project.as_ref() else {
2570 return;
2571 };
2572 project.update(cx, |project, cx| {
2573 for buffer in buffers {
2574 self.registered_buffers
2575 .entry(buffer.read(cx).remote_id())
2576 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2577 }
2578 })
2579 }
2580
2581 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2582 if self.collapse_matches {
2583 return range.start..range.start;
2584 }
2585 range.clone()
2586 }
2587
2588 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2589 if self.display_map.read(cx).clip_at_line_ends != clip {
2590 self.display_map
2591 .update(cx, |map, _| map.clip_at_line_ends = clip);
2592 }
2593 }
2594
2595 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2596 self.input_enabled = input_enabled;
2597 }
2598
2599 pub fn set_inline_completions_hidden_for_vim_mode(
2600 &mut self,
2601 hidden: bool,
2602 window: &mut Window,
2603 cx: &mut Context<Self>,
2604 ) {
2605 if hidden != self.inline_completions_hidden_for_vim_mode {
2606 self.inline_completions_hidden_for_vim_mode = hidden;
2607 if hidden {
2608 self.update_visible_inline_completion(window, cx);
2609 } else {
2610 self.refresh_inline_completion(true, false, window, cx);
2611 }
2612 }
2613 }
2614
2615 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2616 self.menu_inline_completions_policy = value;
2617 }
2618
2619 pub fn set_autoindent(&mut self, autoindent: bool) {
2620 if autoindent {
2621 self.autoindent_mode = Some(AutoindentMode::EachLine);
2622 } else {
2623 self.autoindent_mode = None;
2624 }
2625 }
2626
2627 pub fn read_only(&self, cx: &App) -> bool {
2628 self.read_only || self.buffer.read(cx).read_only()
2629 }
2630
2631 pub fn set_read_only(&mut self, read_only: bool) {
2632 self.read_only = read_only;
2633 }
2634
2635 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2636 self.use_autoclose = autoclose;
2637 }
2638
2639 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2640 self.use_auto_surround = auto_surround;
2641 }
2642
2643 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2644 self.auto_replace_emoji_shortcode = auto_replace;
2645 }
2646
2647 pub fn toggle_edit_predictions(
2648 &mut self,
2649 _: &ToggleEditPrediction,
2650 window: &mut Window,
2651 cx: &mut Context<Self>,
2652 ) {
2653 if self.show_inline_completions_override.is_some() {
2654 self.set_show_edit_predictions(None, window, cx);
2655 } else {
2656 let show_edit_predictions = !self.edit_predictions_enabled();
2657 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2658 }
2659 }
2660
2661 pub fn set_show_edit_predictions(
2662 &mut self,
2663 show_edit_predictions: Option<bool>,
2664 window: &mut Window,
2665 cx: &mut Context<Self>,
2666 ) {
2667 self.show_inline_completions_override = show_edit_predictions;
2668 self.update_edit_prediction_settings(cx);
2669
2670 if let Some(false) = show_edit_predictions {
2671 self.discard_inline_completion(false, cx);
2672 } else {
2673 self.refresh_inline_completion(false, true, window, cx);
2674 }
2675 }
2676
2677 fn inline_completions_disabled_in_scope(
2678 &self,
2679 buffer: &Entity<Buffer>,
2680 buffer_position: language::Anchor,
2681 cx: &App,
2682 ) -> bool {
2683 let snapshot = buffer.read(cx).snapshot();
2684 let settings = snapshot.settings_at(buffer_position, cx);
2685
2686 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2687 return false;
2688 };
2689
2690 scope.override_name().map_or(false, |scope_name| {
2691 settings
2692 .edit_predictions_disabled_in
2693 .iter()
2694 .any(|s| s == scope_name)
2695 })
2696 }
2697
2698 pub fn set_use_modal_editing(&mut self, to: bool) {
2699 self.use_modal_editing = to;
2700 }
2701
2702 pub fn use_modal_editing(&self) -> bool {
2703 self.use_modal_editing
2704 }
2705
2706 fn selections_did_change(
2707 &mut self,
2708 local: bool,
2709 old_cursor_position: &Anchor,
2710 should_update_completions: bool,
2711 window: &mut Window,
2712 cx: &mut Context<Self>,
2713 ) {
2714 window.invalidate_character_coordinates();
2715
2716 // Copy selections to primary selection buffer
2717 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2718 if local {
2719 let selections = self.selections.all::<usize>(cx);
2720 let buffer_handle = self.buffer.read(cx).read(cx);
2721
2722 let mut text = String::new();
2723 for (index, selection) in selections.iter().enumerate() {
2724 let text_for_selection = buffer_handle
2725 .text_for_range(selection.start..selection.end)
2726 .collect::<String>();
2727
2728 text.push_str(&text_for_selection);
2729 if index != selections.len() - 1 {
2730 text.push('\n');
2731 }
2732 }
2733
2734 if !text.is_empty() {
2735 cx.write_to_primary(ClipboardItem::new_string(text));
2736 }
2737 }
2738
2739 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2740 self.buffer.update(cx, |buffer, cx| {
2741 buffer.set_active_selections(
2742 &self.selections.disjoint_anchors(),
2743 self.selections.line_mode,
2744 self.cursor_shape,
2745 cx,
2746 )
2747 });
2748 }
2749 let display_map = self
2750 .display_map
2751 .update(cx, |display_map, cx| display_map.snapshot(cx));
2752 let buffer = &display_map.buffer_snapshot;
2753 if self.selections.count() == 1 {
2754 self.add_selections_state = None;
2755 }
2756 self.select_next_state = None;
2757 self.select_prev_state = None;
2758 self.select_syntax_node_history.try_clear();
2759 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2760 self.snippet_stack
2761 .invalidate(&self.selections.disjoint_anchors(), buffer);
2762 self.take_rename(false, window, cx);
2763
2764 let newest_selection = self.selections.newest_anchor();
2765 let new_cursor_position = newest_selection.head();
2766 let selection_start = newest_selection.start;
2767
2768 self.push_to_nav_history(
2769 *old_cursor_position,
2770 Some(new_cursor_position.to_point(buffer)),
2771 false,
2772 cx,
2773 );
2774
2775 if local {
2776 if let Some(buffer_id) = new_cursor_position.buffer_id {
2777 if !self.registered_buffers.contains_key(&buffer_id) {
2778 if let Some(project) = self.project.as_ref() {
2779 project.update(cx, |project, cx| {
2780 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2781 return;
2782 };
2783 self.registered_buffers.insert(
2784 buffer_id,
2785 project.register_buffer_with_language_servers(&buffer, cx),
2786 );
2787 })
2788 }
2789 }
2790 }
2791
2792 let mut context_menu = self.context_menu.borrow_mut();
2793 let completion_menu = match context_menu.as_ref() {
2794 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2795 Some(CodeContextMenu::CodeActions(_)) => {
2796 *context_menu = None;
2797 None
2798 }
2799 None => None,
2800 };
2801 let completion_position = completion_menu.map(|menu| menu.initial_position);
2802 drop(context_menu);
2803
2804 if should_update_completions {
2805 if let Some(completion_position) = completion_position {
2806 let start_offset = selection_start.to_offset(buffer);
2807 let position_matches = start_offset == completion_position.to_offset(buffer);
2808 let continue_showing = if position_matches {
2809 if self.snippet_stack.is_empty() {
2810 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
2811 } else {
2812 // Snippet choices can be shown even when the cursor is in whitespace.
2813 // Dismissing the menu when actions like backspace
2814 true
2815 }
2816 } else {
2817 false
2818 };
2819
2820 if continue_showing {
2821 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2822 } else {
2823 self.hide_context_menu(window, cx);
2824 }
2825 }
2826 }
2827
2828 hide_hover(self, cx);
2829
2830 if old_cursor_position.to_display_point(&display_map).row()
2831 != new_cursor_position.to_display_point(&display_map).row()
2832 {
2833 self.available_code_actions.take();
2834 }
2835 self.refresh_code_actions(window, cx);
2836 self.refresh_document_highlights(cx);
2837 self.refresh_selected_text_highlights(false, window, cx);
2838 refresh_matching_bracket_highlights(self, window, cx);
2839 self.update_visible_inline_completion(window, cx);
2840 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2841 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2842 self.inline_blame_popover.take();
2843 if self.git_blame_inline_enabled {
2844 self.start_inline_blame_timer(window, cx);
2845 }
2846 }
2847
2848 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2849 cx.emit(EditorEvent::SelectionsChanged { local });
2850
2851 let selections = &self.selections.disjoint;
2852 if selections.len() == 1 {
2853 cx.emit(SearchEvent::ActiveMatchChanged)
2854 }
2855 if local {
2856 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2857 let inmemory_selections = selections
2858 .iter()
2859 .map(|s| {
2860 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2861 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2862 })
2863 .collect();
2864 self.update_restoration_data(cx, |data| {
2865 data.selections = inmemory_selections;
2866 });
2867
2868 if WorkspaceSettings::get(None, cx).restore_on_startup
2869 != RestoreOnStartupBehavior::None
2870 {
2871 if let Some(workspace_id) =
2872 self.workspace.as_ref().and_then(|workspace| workspace.1)
2873 {
2874 let snapshot = self.buffer().read(cx).snapshot(cx);
2875 let selections = selections.clone();
2876 let background_executor = cx.background_executor().clone();
2877 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2878 self.serialize_selections = cx.background_spawn(async move {
2879 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2880 let db_selections = selections
2881 .iter()
2882 .map(|selection| {
2883 (
2884 selection.start.to_offset(&snapshot),
2885 selection.end.to_offset(&snapshot),
2886 )
2887 })
2888 .collect();
2889
2890 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2891 .await
2892 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2893 .log_err();
2894 });
2895 }
2896 }
2897 }
2898 }
2899
2900 cx.notify();
2901 }
2902
2903 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2904 use text::ToOffset as _;
2905 use text::ToPoint as _;
2906
2907 if self.mode.is_minimap()
2908 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2909 {
2910 return;
2911 }
2912
2913 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2914 return;
2915 };
2916
2917 let snapshot = singleton.read(cx).snapshot();
2918 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2919 let display_snapshot = display_map.snapshot(cx);
2920
2921 display_snapshot
2922 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2923 .map(|fold| {
2924 fold.range.start.text_anchor.to_point(&snapshot)
2925 ..fold.range.end.text_anchor.to_point(&snapshot)
2926 })
2927 .collect()
2928 });
2929 self.update_restoration_data(cx, |data| {
2930 data.folds = inmemory_folds;
2931 });
2932
2933 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2934 return;
2935 };
2936 let background_executor = cx.background_executor().clone();
2937 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2938 let db_folds = self.display_map.update(cx, |display_map, cx| {
2939 display_map
2940 .snapshot(cx)
2941 .folds_in_range(0..snapshot.len())
2942 .map(|fold| {
2943 (
2944 fold.range.start.text_anchor.to_offset(&snapshot),
2945 fold.range.end.text_anchor.to_offset(&snapshot),
2946 )
2947 })
2948 .collect()
2949 });
2950 self.serialize_folds = cx.background_spawn(async move {
2951 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2952 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2953 .await
2954 .with_context(|| {
2955 format!(
2956 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2957 )
2958 })
2959 .log_err();
2960 });
2961 }
2962
2963 pub fn sync_selections(
2964 &mut self,
2965 other: Entity<Editor>,
2966 cx: &mut Context<Self>,
2967 ) -> gpui::Subscription {
2968 let other_selections = other.read(cx).selections.disjoint.to_vec();
2969 self.selections.change_with(cx, |selections| {
2970 selections.select_anchors(other_selections);
2971 });
2972
2973 let other_subscription =
2974 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2975 EditorEvent::SelectionsChanged { local: true } => {
2976 let other_selections = other.read(cx).selections.disjoint.to_vec();
2977 if other_selections.is_empty() {
2978 return;
2979 }
2980 this.selections.change_with(cx, |selections| {
2981 selections.select_anchors(other_selections);
2982 });
2983 }
2984 _ => {}
2985 });
2986
2987 let this_subscription =
2988 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2989 EditorEvent::SelectionsChanged { local: true } => {
2990 let these_selections = this.selections.disjoint.to_vec();
2991 if these_selections.is_empty() {
2992 return;
2993 }
2994 other.update(cx, |other_editor, cx| {
2995 other_editor.selections.change_with(cx, |selections| {
2996 selections.select_anchors(these_selections);
2997 })
2998 });
2999 }
3000 _ => {}
3001 });
3002
3003 Subscription::join(other_subscription, this_subscription)
3004 }
3005
3006 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3007 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3008 /// effects of selection change occur at the end of the transaction.
3009 pub fn change_selections<R>(
3010 &mut self,
3011 autoscroll: Option<Autoscroll>,
3012 window: &mut Window,
3013 cx: &mut Context<Self>,
3014 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3015 ) -> R {
3016 self.change_selections_inner(true, autoscroll, window, cx, change)
3017 }
3018
3019 pub(crate) fn change_selections_without_updating_completions<R>(
3020 &mut self,
3021 autoscroll: Option<Autoscroll>,
3022 window: &mut Window,
3023 cx: &mut Context<Self>,
3024 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3025 ) -> R {
3026 self.change_selections_inner(false, autoscroll, window, cx, change)
3027 }
3028
3029 fn change_selections_inner<R>(
3030 &mut self,
3031 should_update_completions: bool,
3032 autoscroll: Option<Autoscroll>,
3033 window: &mut Window,
3034 cx: &mut Context<Self>,
3035 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3036 ) -> R {
3037 if let Some(state) = &mut self.deferred_selection_effects_state {
3038 state.autoscroll = autoscroll.or(state.autoscroll);
3039 state.should_update_completions = should_update_completions;
3040 let (changed, result) = self.selections.change_with(cx, change);
3041 state.changed |= changed;
3042 return result;
3043 }
3044 let mut state = DeferredSelectionEffectsState {
3045 changed: false,
3046 should_update_completions,
3047 autoscroll,
3048 old_cursor_position: self.selections.newest_anchor().head(),
3049 history_entry: SelectionHistoryEntry {
3050 selections: self.selections.disjoint_anchors(),
3051 select_next_state: self.select_next_state.clone(),
3052 select_prev_state: self.select_prev_state.clone(),
3053 add_selections_state: self.add_selections_state.clone(),
3054 },
3055 };
3056 let (changed, result) = self.selections.change_with(cx, change);
3057 state.changed = state.changed || changed;
3058 if self.defer_selection_effects {
3059 self.deferred_selection_effects_state = Some(state);
3060 } else {
3061 self.apply_selection_effects(state, window, cx);
3062 }
3063 result
3064 }
3065
3066 /// Defers the effects of selection change, so that the effects of multiple calls to
3067 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3068 /// to selection history and the state of popovers based on selection position aren't
3069 /// erroneously updated.
3070 pub fn with_selection_effects_deferred<R>(
3071 &mut self,
3072 window: &mut Window,
3073 cx: &mut Context<Self>,
3074 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3075 ) -> R {
3076 let already_deferred = self.defer_selection_effects;
3077 self.defer_selection_effects = true;
3078 let result = update(self, window, cx);
3079 if !already_deferred {
3080 self.defer_selection_effects = false;
3081 if let Some(state) = self.deferred_selection_effects_state.take() {
3082 self.apply_selection_effects(state, window, cx);
3083 }
3084 }
3085 result
3086 }
3087
3088 fn apply_selection_effects(
3089 &mut self,
3090 state: DeferredSelectionEffectsState,
3091 window: &mut Window,
3092 cx: &mut Context<Self>,
3093 ) {
3094 if state.changed {
3095 self.selection_history.push(state.history_entry);
3096
3097 if let Some(autoscroll) = state.autoscroll {
3098 self.request_autoscroll(autoscroll, cx);
3099 }
3100
3101 let old_cursor_position = &state.old_cursor_position;
3102
3103 self.selections_did_change(
3104 true,
3105 &old_cursor_position,
3106 state.should_update_completions,
3107 window,
3108 cx,
3109 );
3110
3111 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3112 self.show_signature_help(&ShowSignatureHelp, window, cx);
3113 }
3114 }
3115 }
3116
3117 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3118 where
3119 I: IntoIterator<Item = (Range<S>, T)>,
3120 S: ToOffset,
3121 T: Into<Arc<str>>,
3122 {
3123 if self.read_only(cx) {
3124 return;
3125 }
3126
3127 self.buffer
3128 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3129 }
3130
3131 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3132 where
3133 I: IntoIterator<Item = (Range<S>, T)>,
3134 S: ToOffset,
3135 T: Into<Arc<str>>,
3136 {
3137 if self.read_only(cx) {
3138 return;
3139 }
3140
3141 self.buffer.update(cx, |buffer, cx| {
3142 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3143 });
3144 }
3145
3146 pub fn edit_with_block_indent<I, S, T>(
3147 &mut self,
3148 edits: I,
3149 original_indent_columns: Vec<Option<u32>>,
3150 cx: &mut Context<Self>,
3151 ) where
3152 I: IntoIterator<Item = (Range<S>, T)>,
3153 S: ToOffset,
3154 T: Into<Arc<str>>,
3155 {
3156 if self.read_only(cx) {
3157 return;
3158 }
3159
3160 self.buffer.update(cx, |buffer, cx| {
3161 buffer.edit(
3162 edits,
3163 Some(AutoindentMode::Block {
3164 original_indent_columns,
3165 }),
3166 cx,
3167 )
3168 });
3169 }
3170
3171 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3172 self.hide_context_menu(window, cx);
3173
3174 match phase {
3175 SelectPhase::Begin {
3176 position,
3177 add,
3178 click_count,
3179 } => self.begin_selection(position, add, click_count, window, cx),
3180 SelectPhase::BeginColumnar {
3181 position,
3182 goal_column,
3183 reset,
3184 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
3185 SelectPhase::Extend {
3186 position,
3187 click_count,
3188 } => self.extend_selection(position, click_count, window, cx),
3189 SelectPhase::Update {
3190 position,
3191 goal_column,
3192 scroll_delta,
3193 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3194 SelectPhase::End => self.end_selection(window, cx),
3195 }
3196 }
3197
3198 fn extend_selection(
3199 &mut self,
3200 position: DisplayPoint,
3201 click_count: usize,
3202 window: &mut Window,
3203 cx: &mut Context<Self>,
3204 ) {
3205 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3206 let tail = self.selections.newest::<usize>(cx).tail();
3207 self.begin_selection(position, false, click_count, window, cx);
3208
3209 let position = position.to_offset(&display_map, Bias::Left);
3210 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3211
3212 let mut pending_selection = self
3213 .selections
3214 .pending_anchor()
3215 .expect("extend_selection not called with pending selection");
3216 if position >= tail {
3217 pending_selection.start = tail_anchor;
3218 } else {
3219 pending_selection.end = tail_anchor;
3220 pending_selection.reversed = true;
3221 }
3222
3223 let mut pending_mode = self.selections.pending_mode().unwrap();
3224 match &mut pending_mode {
3225 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3226 _ => {}
3227 }
3228
3229 let auto_scroll = EditorSettings::get_global(cx).autoscroll_on_clicks;
3230
3231 self.change_selections(auto_scroll.then(Autoscroll::fit), window, cx, |s| {
3232 s.set_pending(pending_selection, pending_mode)
3233 });
3234 }
3235
3236 fn begin_selection(
3237 &mut self,
3238 position: DisplayPoint,
3239 add: bool,
3240 click_count: usize,
3241 window: &mut Window,
3242 cx: &mut Context<Self>,
3243 ) {
3244 if !self.focus_handle.is_focused(window) {
3245 self.last_focused_descendant = None;
3246 window.focus(&self.focus_handle);
3247 }
3248
3249 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3250 let buffer = &display_map.buffer_snapshot;
3251 let position = display_map.clip_point(position, Bias::Left);
3252
3253 let start;
3254 let end;
3255 let mode;
3256 let mut auto_scroll;
3257 match click_count {
3258 1 => {
3259 start = buffer.anchor_before(position.to_point(&display_map));
3260 end = start;
3261 mode = SelectMode::Character;
3262 auto_scroll = true;
3263 }
3264 2 => {
3265 let range = movement::surrounding_word(&display_map, position);
3266 start = buffer.anchor_before(range.start.to_point(&display_map));
3267 end = buffer.anchor_before(range.end.to_point(&display_map));
3268 mode = SelectMode::Word(start..end);
3269 auto_scroll = true;
3270 }
3271 3 => {
3272 let position = display_map
3273 .clip_point(position, Bias::Left)
3274 .to_point(&display_map);
3275 let line_start = display_map.prev_line_boundary(position).0;
3276 let next_line_start = buffer.clip_point(
3277 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3278 Bias::Left,
3279 );
3280 start = buffer.anchor_before(line_start);
3281 end = buffer.anchor_before(next_line_start);
3282 mode = SelectMode::Line(start..end);
3283 auto_scroll = true;
3284 }
3285 _ => {
3286 start = buffer.anchor_before(0);
3287 end = buffer.anchor_before(buffer.len());
3288 mode = SelectMode::All;
3289 auto_scroll = false;
3290 }
3291 }
3292 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3293
3294 let point_to_delete: Option<usize> = {
3295 let selected_points: Vec<Selection<Point>> =
3296 self.selections.disjoint_in_range(start..end, cx);
3297
3298 if !add || click_count > 1 {
3299 None
3300 } else if !selected_points.is_empty() {
3301 Some(selected_points[0].id)
3302 } else {
3303 let clicked_point_already_selected =
3304 self.selections.disjoint.iter().find(|selection| {
3305 selection.start.to_point(buffer) == start.to_point(buffer)
3306 || selection.end.to_point(buffer) == end.to_point(buffer)
3307 });
3308
3309 clicked_point_already_selected.map(|selection| selection.id)
3310 }
3311 };
3312
3313 let selections_count = self.selections.count();
3314
3315 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3316 if let Some(point_to_delete) = point_to_delete {
3317 s.delete(point_to_delete);
3318
3319 if selections_count == 1 {
3320 s.set_pending_anchor_range(start..end, mode);
3321 }
3322 } else {
3323 if !add {
3324 s.clear_disjoint();
3325 }
3326
3327 s.set_pending_anchor_range(start..end, mode);
3328 }
3329 });
3330 }
3331
3332 fn begin_columnar_selection(
3333 &mut self,
3334 position: DisplayPoint,
3335 goal_column: u32,
3336 reset: bool,
3337 window: &mut Window,
3338 cx: &mut Context<Self>,
3339 ) {
3340 if !self.focus_handle.is_focused(window) {
3341 self.last_focused_descendant = None;
3342 window.focus(&self.focus_handle);
3343 }
3344
3345 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3346
3347 if reset {
3348 let pointer_position = display_map
3349 .buffer_snapshot
3350 .anchor_before(position.to_point(&display_map));
3351
3352 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3353 s.clear_disjoint();
3354 s.set_pending_anchor_range(
3355 pointer_position..pointer_position,
3356 SelectMode::Character,
3357 );
3358 });
3359 if position.column() != goal_column {
3360 self.columnar_display_point = Some(DisplayPoint::new(position.row(), goal_column));
3361 } else {
3362 self.columnar_display_point = None;
3363 }
3364 }
3365
3366 let tail = self.selections.newest::<Point>(cx).tail();
3367 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3368
3369 if !reset {
3370 self.columnar_display_point = None;
3371 self.select_columns(
3372 tail.to_display_point(&display_map),
3373 position,
3374 goal_column,
3375 &display_map,
3376 window,
3377 cx,
3378 );
3379 }
3380 }
3381
3382 fn update_selection(
3383 &mut self,
3384 position: DisplayPoint,
3385 goal_column: u32,
3386 scroll_delta: gpui::Point<f32>,
3387 window: &mut Window,
3388 cx: &mut Context<Self>,
3389 ) {
3390 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3391
3392 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3393 let tail = self
3394 .columnar_display_point
3395 .unwrap_or_else(|| tail.to_display_point(&display_map));
3396 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3397 } else if let Some(mut pending) = self.selections.pending_anchor() {
3398 let buffer = self.buffer.read(cx).snapshot(cx);
3399 let head;
3400 let tail;
3401 let mode = self.selections.pending_mode().unwrap();
3402 match &mode {
3403 SelectMode::Character => {
3404 head = position.to_point(&display_map);
3405 tail = pending.tail().to_point(&buffer);
3406 }
3407 SelectMode::Word(original_range) => {
3408 let original_display_range = original_range.start.to_display_point(&display_map)
3409 ..original_range.end.to_display_point(&display_map);
3410 let original_buffer_range = original_display_range.start.to_point(&display_map)
3411 ..original_display_range.end.to_point(&display_map);
3412 if movement::is_inside_word(&display_map, position)
3413 || original_display_range.contains(&position)
3414 {
3415 let word_range = movement::surrounding_word(&display_map, position);
3416 if word_range.start < original_display_range.start {
3417 head = word_range.start.to_point(&display_map);
3418 } else {
3419 head = word_range.end.to_point(&display_map);
3420 }
3421 } else {
3422 head = position.to_point(&display_map);
3423 }
3424
3425 if head <= original_buffer_range.start {
3426 tail = original_buffer_range.end;
3427 } else {
3428 tail = original_buffer_range.start;
3429 }
3430 }
3431 SelectMode::Line(original_range) => {
3432 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3433
3434 let position = display_map
3435 .clip_point(position, Bias::Left)
3436 .to_point(&display_map);
3437 let line_start = display_map.prev_line_boundary(position).0;
3438 let next_line_start = buffer.clip_point(
3439 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3440 Bias::Left,
3441 );
3442
3443 if line_start < original_range.start {
3444 head = line_start
3445 } else {
3446 head = next_line_start
3447 }
3448
3449 if head <= original_range.start {
3450 tail = original_range.end;
3451 } else {
3452 tail = original_range.start;
3453 }
3454 }
3455 SelectMode::All => {
3456 return;
3457 }
3458 };
3459
3460 if head < tail {
3461 pending.start = buffer.anchor_before(head);
3462 pending.end = buffer.anchor_before(tail);
3463 pending.reversed = true;
3464 } else {
3465 pending.start = buffer.anchor_before(tail);
3466 pending.end = buffer.anchor_before(head);
3467 pending.reversed = false;
3468 }
3469
3470 self.change_selections(None, window, cx, |s| {
3471 s.set_pending(pending, mode);
3472 });
3473 } else {
3474 log::error!("update_selection dispatched with no pending selection");
3475 return;
3476 }
3477
3478 self.apply_scroll_delta(scroll_delta, window, cx);
3479 cx.notify();
3480 }
3481
3482 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3483 self.columnar_selection_tail.take();
3484 if self.selections.pending_anchor().is_some() {
3485 let selections = self.selections.all::<usize>(cx);
3486 self.change_selections(None, window, cx, |s| {
3487 s.select(selections);
3488 s.clear_pending();
3489 });
3490 }
3491 }
3492
3493 fn select_columns(
3494 &mut self,
3495 tail: DisplayPoint,
3496 head: DisplayPoint,
3497 goal_column: u32,
3498 display_map: &DisplaySnapshot,
3499 window: &mut Window,
3500 cx: &mut Context<Self>,
3501 ) {
3502 let start_row = cmp::min(tail.row(), head.row());
3503 let end_row = cmp::max(tail.row(), head.row());
3504 let start_column = cmp::min(tail.column(), goal_column);
3505 let end_column = cmp::max(tail.column(), goal_column);
3506 let reversed = start_column < tail.column();
3507
3508 let selection_ranges = (start_row.0..=end_row.0)
3509 .map(DisplayRow)
3510 .filter_map(|row| {
3511 if !display_map.is_block_line(row) {
3512 let start = display_map
3513 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3514 .to_point(display_map);
3515 let end = display_map
3516 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3517 .to_point(display_map);
3518 if reversed {
3519 Some(end..start)
3520 } else {
3521 Some(start..end)
3522 }
3523 } else {
3524 None
3525 }
3526 })
3527 .collect::<Vec<_>>();
3528
3529 let mut non_empty_ranges = selection_ranges
3530 .iter()
3531 .filter(|selection_range| selection_range.start != selection_range.end)
3532 .peekable();
3533
3534 let ranges = if non_empty_ranges.peek().is_some() {
3535 non_empty_ranges.cloned().collect()
3536 } else {
3537 selection_ranges
3538 };
3539
3540 self.change_selections(None, window, cx, |s| {
3541 s.select_ranges(ranges);
3542 });
3543 cx.notify();
3544 }
3545
3546 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3547 self.selections
3548 .all_adjusted(cx)
3549 .iter()
3550 .any(|selection| !selection.is_empty())
3551 }
3552
3553 pub fn has_pending_nonempty_selection(&self) -> bool {
3554 let pending_nonempty_selection = match self.selections.pending_anchor() {
3555 Some(Selection { start, end, .. }) => start != end,
3556 None => false,
3557 };
3558
3559 pending_nonempty_selection
3560 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3561 }
3562
3563 pub fn has_pending_selection(&self) -> bool {
3564 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3565 }
3566
3567 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3568 self.selection_mark_mode = false;
3569 self.selection_drag_state = SelectionDragState::None;
3570
3571 if self.clear_expanded_diff_hunks(cx) {
3572 cx.notify();
3573 return;
3574 }
3575 if self.dismiss_menus_and_popups(true, window, cx) {
3576 return;
3577 }
3578
3579 if self.mode.is_full()
3580 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3581 {
3582 return;
3583 }
3584
3585 cx.propagate();
3586 }
3587
3588 pub fn dismiss_menus_and_popups(
3589 &mut self,
3590 is_user_requested: bool,
3591 window: &mut Window,
3592 cx: &mut Context<Self>,
3593 ) -> bool {
3594 if self.take_rename(false, window, cx).is_some() {
3595 return true;
3596 }
3597
3598 if hide_hover(self, cx) {
3599 return true;
3600 }
3601
3602 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3603 return true;
3604 }
3605
3606 if self.hide_context_menu(window, cx).is_some() {
3607 return true;
3608 }
3609
3610 if self.mouse_context_menu.take().is_some() {
3611 return true;
3612 }
3613
3614 if is_user_requested && self.discard_inline_completion(true, cx) {
3615 return true;
3616 }
3617
3618 if self.snippet_stack.pop().is_some() {
3619 return true;
3620 }
3621
3622 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3623 self.dismiss_diagnostics(cx);
3624 return true;
3625 }
3626
3627 false
3628 }
3629
3630 fn linked_editing_ranges_for(
3631 &self,
3632 selection: Range<text::Anchor>,
3633 cx: &App,
3634 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3635 if self.linked_edit_ranges.is_empty() {
3636 return None;
3637 }
3638 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3639 selection.end.buffer_id.and_then(|end_buffer_id| {
3640 if selection.start.buffer_id != Some(end_buffer_id) {
3641 return None;
3642 }
3643 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3644 let snapshot = buffer.read(cx).snapshot();
3645 self.linked_edit_ranges
3646 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3647 .map(|ranges| (ranges, snapshot, buffer))
3648 })?;
3649 use text::ToOffset as TO;
3650 // find offset from the start of current range to current cursor position
3651 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3652
3653 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3654 let start_difference = start_offset - start_byte_offset;
3655 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3656 let end_difference = end_offset - start_byte_offset;
3657 // Current range has associated linked ranges.
3658 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3659 for range in linked_ranges.iter() {
3660 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3661 let end_offset = start_offset + end_difference;
3662 let start_offset = start_offset + start_difference;
3663 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3664 continue;
3665 }
3666 if self.selections.disjoint_anchor_ranges().any(|s| {
3667 if s.start.buffer_id != selection.start.buffer_id
3668 || s.end.buffer_id != selection.end.buffer_id
3669 {
3670 return false;
3671 }
3672 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3673 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3674 }) {
3675 continue;
3676 }
3677 let start = buffer_snapshot.anchor_after(start_offset);
3678 let end = buffer_snapshot.anchor_after(end_offset);
3679 linked_edits
3680 .entry(buffer.clone())
3681 .or_default()
3682 .push(start..end);
3683 }
3684 Some(linked_edits)
3685 }
3686
3687 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3688 let text: Arc<str> = text.into();
3689
3690 if self.read_only(cx) {
3691 return;
3692 }
3693
3694 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3695
3696 let selections = self.selections.all_adjusted(cx);
3697 let mut bracket_inserted = false;
3698 let mut edits = Vec::new();
3699 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3700 let mut new_selections = Vec::with_capacity(selections.len());
3701 let mut new_autoclose_regions = Vec::new();
3702 let snapshot = self.buffer.read(cx).read(cx);
3703 let mut clear_linked_edit_ranges = false;
3704
3705 for (selection, autoclose_region) in
3706 self.selections_with_autoclose_regions(selections, &snapshot)
3707 {
3708 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3709 // Determine if the inserted text matches the opening or closing
3710 // bracket of any of this language's bracket pairs.
3711 let mut bracket_pair = None;
3712 let mut is_bracket_pair_start = false;
3713 let mut is_bracket_pair_end = false;
3714 if !text.is_empty() {
3715 let mut bracket_pair_matching_end = None;
3716 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3717 // and they are removing the character that triggered IME popup.
3718 for (pair, enabled) in scope.brackets() {
3719 if !pair.close && !pair.surround {
3720 continue;
3721 }
3722
3723 if enabled && pair.start.ends_with(text.as_ref()) {
3724 let prefix_len = pair.start.len() - text.len();
3725 let preceding_text_matches_prefix = prefix_len == 0
3726 || (selection.start.column >= (prefix_len as u32)
3727 && snapshot.contains_str_at(
3728 Point::new(
3729 selection.start.row,
3730 selection.start.column - (prefix_len as u32),
3731 ),
3732 &pair.start[..prefix_len],
3733 ));
3734 if preceding_text_matches_prefix {
3735 bracket_pair = Some(pair.clone());
3736 is_bracket_pair_start = true;
3737 break;
3738 }
3739 }
3740 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3741 {
3742 // take first bracket pair matching end, but don't break in case a later bracket
3743 // pair matches start
3744 bracket_pair_matching_end = Some(pair.clone());
3745 }
3746 }
3747 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3748 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3749 is_bracket_pair_end = true;
3750 }
3751 }
3752
3753 if let Some(bracket_pair) = bracket_pair {
3754 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3755 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3756 let auto_surround =
3757 self.use_auto_surround && snapshot_settings.use_auto_surround;
3758 if selection.is_empty() {
3759 if is_bracket_pair_start {
3760 // If the inserted text is a suffix of an opening bracket and the
3761 // selection is preceded by the rest of the opening bracket, then
3762 // insert the closing bracket.
3763 let following_text_allows_autoclose = snapshot
3764 .chars_at(selection.start)
3765 .next()
3766 .map_or(true, |c| scope.should_autoclose_before(c));
3767
3768 let preceding_text_allows_autoclose = selection.start.column == 0
3769 || snapshot.reversed_chars_at(selection.start).next().map_or(
3770 true,
3771 |c| {
3772 bracket_pair.start != bracket_pair.end
3773 || !snapshot
3774 .char_classifier_at(selection.start)
3775 .is_word(c)
3776 },
3777 );
3778
3779 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3780 && bracket_pair.start.len() == 1
3781 {
3782 let target = bracket_pair.start.chars().next().unwrap();
3783 let current_line_count = snapshot
3784 .reversed_chars_at(selection.start)
3785 .take_while(|&c| c != '\n')
3786 .filter(|&c| c == target)
3787 .count();
3788 current_line_count % 2 == 1
3789 } else {
3790 false
3791 };
3792
3793 if autoclose
3794 && bracket_pair.close
3795 && following_text_allows_autoclose
3796 && preceding_text_allows_autoclose
3797 && !is_closing_quote
3798 {
3799 let anchor = snapshot.anchor_before(selection.end);
3800 new_selections.push((selection.map(|_| anchor), text.len()));
3801 new_autoclose_regions.push((
3802 anchor,
3803 text.len(),
3804 selection.id,
3805 bracket_pair.clone(),
3806 ));
3807 edits.push((
3808 selection.range(),
3809 format!("{}{}", text, bracket_pair.end).into(),
3810 ));
3811 bracket_inserted = true;
3812 continue;
3813 }
3814 }
3815
3816 if let Some(region) = autoclose_region {
3817 // If the selection is followed by an auto-inserted closing bracket,
3818 // then don't insert that closing bracket again; just move the selection
3819 // past the closing bracket.
3820 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3821 && text.as_ref() == region.pair.end.as_str();
3822 if should_skip {
3823 let anchor = snapshot.anchor_after(selection.end);
3824 new_selections
3825 .push((selection.map(|_| anchor), region.pair.end.len()));
3826 continue;
3827 }
3828 }
3829
3830 let always_treat_brackets_as_autoclosed = snapshot
3831 .language_settings_at(selection.start, cx)
3832 .always_treat_brackets_as_autoclosed;
3833 if always_treat_brackets_as_autoclosed
3834 && is_bracket_pair_end
3835 && snapshot.contains_str_at(selection.end, text.as_ref())
3836 {
3837 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3838 // and the inserted text is a closing bracket and the selection is followed
3839 // by the closing bracket then move the selection past the closing bracket.
3840 let anchor = snapshot.anchor_after(selection.end);
3841 new_selections.push((selection.map(|_| anchor), text.len()));
3842 continue;
3843 }
3844 }
3845 // If an opening bracket is 1 character long and is typed while
3846 // text is selected, then surround that text with the bracket pair.
3847 else if auto_surround
3848 && bracket_pair.surround
3849 && is_bracket_pair_start
3850 && bracket_pair.start.chars().count() == 1
3851 {
3852 edits.push((selection.start..selection.start, text.clone()));
3853 edits.push((
3854 selection.end..selection.end,
3855 bracket_pair.end.as_str().into(),
3856 ));
3857 bracket_inserted = true;
3858 new_selections.push((
3859 Selection {
3860 id: selection.id,
3861 start: snapshot.anchor_after(selection.start),
3862 end: snapshot.anchor_before(selection.end),
3863 reversed: selection.reversed,
3864 goal: selection.goal,
3865 },
3866 0,
3867 ));
3868 continue;
3869 }
3870 }
3871 }
3872
3873 if self.auto_replace_emoji_shortcode
3874 && selection.is_empty()
3875 && text.as_ref().ends_with(':')
3876 {
3877 if let Some(possible_emoji_short_code) =
3878 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3879 {
3880 if !possible_emoji_short_code.is_empty() {
3881 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3882 let emoji_shortcode_start = Point::new(
3883 selection.start.row,
3884 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3885 );
3886
3887 // Remove shortcode from buffer
3888 edits.push((
3889 emoji_shortcode_start..selection.start,
3890 "".to_string().into(),
3891 ));
3892 new_selections.push((
3893 Selection {
3894 id: selection.id,
3895 start: snapshot.anchor_after(emoji_shortcode_start),
3896 end: snapshot.anchor_before(selection.start),
3897 reversed: selection.reversed,
3898 goal: selection.goal,
3899 },
3900 0,
3901 ));
3902
3903 // Insert emoji
3904 let selection_start_anchor = snapshot.anchor_after(selection.start);
3905 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3906 edits.push((selection.start..selection.end, emoji.to_string().into()));
3907
3908 continue;
3909 }
3910 }
3911 }
3912 }
3913
3914 // If not handling any auto-close operation, then just replace the selected
3915 // text with the given input and move the selection to the end of the
3916 // newly inserted text.
3917 let anchor = snapshot.anchor_after(selection.end);
3918 if !self.linked_edit_ranges.is_empty() {
3919 let start_anchor = snapshot.anchor_before(selection.start);
3920
3921 let is_word_char = text.chars().next().map_or(true, |char| {
3922 let classifier = snapshot
3923 .char_classifier_at(start_anchor.to_offset(&snapshot))
3924 .ignore_punctuation(true);
3925 classifier.is_word(char)
3926 });
3927
3928 if is_word_char {
3929 if let Some(ranges) = self
3930 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3931 {
3932 for (buffer, edits) in ranges {
3933 linked_edits
3934 .entry(buffer.clone())
3935 .or_default()
3936 .extend(edits.into_iter().map(|range| (range, text.clone())));
3937 }
3938 }
3939 } else {
3940 clear_linked_edit_ranges = true;
3941 }
3942 }
3943
3944 new_selections.push((selection.map(|_| anchor), 0));
3945 edits.push((selection.start..selection.end, text.clone()));
3946 }
3947
3948 drop(snapshot);
3949
3950 self.transact(window, cx, |this, window, cx| {
3951 if clear_linked_edit_ranges {
3952 this.linked_edit_ranges.clear();
3953 }
3954 let initial_buffer_versions =
3955 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3956
3957 this.buffer.update(cx, |buffer, cx| {
3958 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3959 });
3960 for (buffer, edits) in linked_edits {
3961 buffer.update(cx, |buffer, cx| {
3962 let snapshot = buffer.snapshot();
3963 let edits = edits
3964 .into_iter()
3965 .map(|(range, text)| {
3966 use text::ToPoint as TP;
3967 let end_point = TP::to_point(&range.end, &snapshot);
3968 let start_point = TP::to_point(&range.start, &snapshot);
3969 (start_point..end_point, text)
3970 })
3971 .sorted_by_key(|(range, _)| range.start);
3972 buffer.edit(edits, None, cx);
3973 })
3974 }
3975 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3976 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3977 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3978 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3979 .zip(new_selection_deltas)
3980 .map(|(selection, delta)| Selection {
3981 id: selection.id,
3982 start: selection.start + delta,
3983 end: selection.end + delta,
3984 reversed: selection.reversed,
3985 goal: SelectionGoal::None,
3986 })
3987 .collect::<Vec<_>>();
3988
3989 let mut i = 0;
3990 for (position, delta, selection_id, pair) in new_autoclose_regions {
3991 let position = position.to_offset(&map.buffer_snapshot) + delta;
3992 let start = map.buffer_snapshot.anchor_before(position);
3993 let end = map.buffer_snapshot.anchor_after(position);
3994 while let Some(existing_state) = this.autoclose_regions.get(i) {
3995 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3996 Ordering::Less => i += 1,
3997 Ordering::Greater => break,
3998 Ordering::Equal => {
3999 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4000 Ordering::Less => i += 1,
4001 Ordering::Equal => break,
4002 Ordering::Greater => break,
4003 }
4004 }
4005 }
4006 }
4007 this.autoclose_regions.insert(
4008 i,
4009 AutocloseRegion {
4010 selection_id,
4011 range: start..end,
4012 pair,
4013 },
4014 );
4015 }
4016
4017 let had_active_inline_completion = this.has_active_inline_completion();
4018 this.change_selections_without_updating_completions(
4019 Some(Autoscroll::fit()),
4020 window,
4021 cx,
4022 |s| s.select(new_selections),
4023 );
4024
4025 if !bracket_inserted {
4026 if let Some(on_type_format_task) =
4027 this.trigger_on_type_formatting(text.to_string(), window, cx)
4028 {
4029 on_type_format_task.detach_and_log_err(cx);
4030 }
4031 }
4032
4033 let editor_settings = EditorSettings::get_global(cx);
4034 if bracket_inserted
4035 && (editor_settings.auto_signature_help
4036 || editor_settings.show_signature_help_after_edits)
4037 {
4038 this.show_signature_help(&ShowSignatureHelp, window, cx);
4039 }
4040
4041 let trigger_in_words =
4042 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4043 if this.hard_wrap.is_some() {
4044 let latest: Range<Point> = this.selections.newest(cx).range();
4045 if latest.is_empty()
4046 && this
4047 .buffer()
4048 .read(cx)
4049 .snapshot(cx)
4050 .line_len(MultiBufferRow(latest.start.row))
4051 == latest.start.column
4052 {
4053 this.rewrap_impl(
4054 RewrapOptions {
4055 override_language_settings: true,
4056 preserve_existing_whitespace: true,
4057 },
4058 cx,
4059 )
4060 }
4061 }
4062 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4063 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4064 this.refresh_inline_completion(true, false, window, cx);
4065 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4066 });
4067 }
4068
4069 fn find_possible_emoji_shortcode_at_position(
4070 snapshot: &MultiBufferSnapshot,
4071 position: Point,
4072 ) -> Option<String> {
4073 let mut chars = Vec::new();
4074 let mut found_colon = false;
4075 for char in snapshot.reversed_chars_at(position).take(100) {
4076 // Found a possible emoji shortcode in the middle of the buffer
4077 if found_colon {
4078 if char.is_whitespace() {
4079 chars.reverse();
4080 return Some(chars.iter().collect());
4081 }
4082 // If the previous character is not a whitespace, we are in the middle of a word
4083 // and we only want to complete the shortcode if the word is made up of other emojis
4084 let mut containing_word = String::new();
4085 for ch in snapshot
4086 .reversed_chars_at(position)
4087 .skip(chars.len() + 1)
4088 .take(100)
4089 {
4090 if ch.is_whitespace() {
4091 break;
4092 }
4093 containing_word.push(ch);
4094 }
4095 let containing_word = containing_word.chars().rev().collect::<String>();
4096 if util::word_consists_of_emojis(containing_word.as_str()) {
4097 chars.reverse();
4098 return Some(chars.iter().collect());
4099 }
4100 }
4101
4102 if char.is_whitespace() || !char.is_ascii() {
4103 return None;
4104 }
4105 if char == ':' {
4106 found_colon = true;
4107 } else {
4108 chars.push(char);
4109 }
4110 }
4111 // Found a possible emoji shortcode at the beginning of the buffer
4112 chars.reverse();
4113 Some(chars.iter().collect())
4114 }
4115
4116 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4117 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4118 self.transact(window, cx, |this, window, cx| {
4119 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4120 let selections = this.selections.all::<usize>(cx);
4121 let multi_buffer = this.buffer.read(cx);
4122 let buffer = multi_buffer.snapshot(cx);
4123 selections
4124 .iter()
4125 .map(|selection| {
4126 let start_point = selection.start.to_point(&buffer);
4127 let mut existing_indent =
4128 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4129 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4130 let start = selection.start;
4131 let end = selection.end;
4132 let selection_is_empty = start == end;
4133 let language_scope = buffer.language_scope_at(start);
4134 let (
4135 comment_delimiter,
4136 doc_delimiter,
4137 insert_extra_newline,
4138 indent_on_newline,
4139 indent_on_extra_newline,
4140 ) = if let Some(language) = &language_scope {
4141 let mut insert_extra_newline =
4142 insert_extra_newline_brackets(&buffer, start..end, language)
4143 || insert_extra_newline_tree_sitter(&buffer, start..end);
4144
4145 // Comment extension on newline is allowed only for cursor selections
4146 let comment_delimiter = maybe!({
4147 if !selection_is_empty {
4148 return None;
4149 }
4150
4151 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4152 return None;
4153 }
4154
4155 let delimiters = language.line_comment_prefixes();
4156 let max_len_of_delimiter =
4157 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4158 let (snapshot, range) =
4159 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4160
4161 let num_of_whitespaces = snapshot
4162 .chars_for_range(range.clone())
4163 .take_while(|c| c.is_whitespace())
4164 .count();
4165 let comment_candidate = snapshot
4166 .chars_for_range(range)
4167 .skip(num_of_whitespaces)
4168 .take(max_len_of_delimiter)
4169 .collect::<String>();
4170 let (delimiter, trimmed_len) = delimiters
4171 .iter()
4172 .filter_map(|delimiter| {
4173 let prefix = delimiter.trim_end();
4174 if comment_candidate.starts_with(prefix) {
4175 Some((delimiter, prefix.len()))
4176 } else {
4177 None
4178 }
4179 })
4180 .max_by_key(|(_, len)| *len)?;
4181
4182 let cursor_is_placed_after_comment_marker =
4183 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4184 if cursor_is_placed_after_comment_marker {
4185 Some(delimiter.clone())
4186 } else {
4187 None
4188 }
4189 });
4190
4191 let mut indent_on_newline = IndentSize::spaces(0);
4192 let mut indent_on_extra_newline = IndentSize::spaces(0);
4193
4194 let doc_delimiter = maybe!({
4195 if !selection_is_empty {
4196 return None;
4197 }
4198
4199 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4200 return None;
4201 }
4202
4203 let DocumentationConfig {
4204 start: start_tag,
4205 end: end_tag,
4206 prefix: delimiter,
4207 tab_size: len,
4208 } = language.documentation()?;
4209
4210 let is_within_block_comment = buffer
4211 .language_scope_at(start_point)
4212 .is_some_and(|scope| scope.override_name() == Some("comment"));
4213 if !is_within_block_comment {
4214 return None;
4215 }
4216
4217 let (snapshot, range) =
4218 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4219
4220 let num_of_whitespaces = snapshot
4221 .chars_for_range(range.clone())
4222 .take_while(|c| c.is_whitespace())
4223 .count();
4224
4225 // 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.
4226 let column = start_point.column;
4227 let cursor_is_after_start_tag = {
4228 let start_tag_len = start_tag.len();
4229 let start_tag_line = snapshot
4230 .chars_for_range(range.clone())
4231 .skip(num_of_whitespaces)
4232 .take(start_tag_len)
4233 .collect::<String>();
4234 if start_tag_line.starts_with(start_tag.as_ref()) {
4235 num_of_whitespaces + start_tag_len <= column as usize
4236 } else {
4237 false
4238 }
4239 };
4240
4241 let cursor_is_after_delimiter = {
4242 let delimiter_trim = delimiter.trim_end();
4243 let delimiter_line = snapshot
4244 .chars_for_range(range.clone())
4245 .skip(num_of_whitespaces)
4246 .take(delimiter_trim.len())
4247 .collect::<String>();
4248 if delimiter_line.starts_with(delimiter_trim) {
4249 num_of_whitespaces + delimiter_trim.len() <= column as usize
4250 } else {
4251 false
4252 }
4253 };
4254
4255 let cursor_is_before_end_tag_if_exists = {
4256 let mut char_position = 0u32;
4257 let mut end_tag_offset = None;
4258
4259 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4260 if let Some(byte_pos) = chunk.find(&**end_tag) {
4261 let chars_before_match =
4262 chunk[..byte_pos].chars().count() as u32;
4263 end_tag_offset =
4264 Some(char_position + chars_before_match);
4265 break 'outer;
4266 }
4267 char_position += chunk.chars().count() as u32;
4268 }
4269
4270 if let Some(end_tag_offset) = end_tag_offset {
4271 let cursor_is_before_end_tag = column <= end_tag_offset;
4272 if cursor_is_after_start_tag {
4273 if cursor_is_before_end_tag {
4274 insert_extra_newline = true;
4275 }
4276 let cursor_is_at_start_of_end_tag =
4277 column == end_tag_offset;
4278 if cursor_is_at_start_of_end_tag {
4279 indent_on_extra_newline.len = (*len).into();
4280 }
4281 }
4282 cursor_is_before_end_tag
4283 } else {
4284 true
4285 }
4286 };
4287
4288 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4289 && cursor_is_before_end_tag_if_exists
4290 {
4291 if cursor_is_after_start_tag {
4292 indent_on_newline.len = (*len).into();
4293 }
4294 Some(delimiter.clone())
4295 } else {
4296 None
4297 }
4298 });
4299
4300 (
4301 comment_delimiter,
4302 doc_delimiter,
4303 insert_extra_newline,
4304 indent_on_newline,
4305 indent_on_extra_newline,
4306 )
4307 } else {
4308 (
4309 None,
4310 None,
4311 false,
4312 IndentSize::default(),
4313 IndentSize::default(),
4314 )
4315 };
4316
4317 let prevent_auto_indent = doc_delimiter.is_some();
4318 let delimiter = comment_delimiter.or(doc_delimiter);
4319
4320 let capacity_for_delimiter =
4321 delimiter.as_deref().map(str::len).unwrap_or_default();
4322 let mut new_text = String::with_capacity(
4323 1 + capacity_for_delimiter
4324 + existing_indent.len as usize
4325 + indent_on_newline.len as usize
4326 + indent_on_extra_newline.len as usize,
4327 );
4328 new_text.push('\n');
4329 new_text.extend(existing_indent.chars());
4330 new_text.extend(indent_on_newline.chars());
4331
4332 if let Some(delimiter) = &delimiter {
4333 new_text.push_str(delimiter);
4334 }
4335
4336 if insert_extra_newline {
4337 new_text.push('\n');
4338 new_text.extend(existing_indent.chars());
4339 new_text.extend(indent_on_extra_newline.chars());
4340 }
4341
4342 let anchor = buffer.anchor_after(end);
4343 let new_selection = selection.map(|_| anchor);
4344 (
4345 ((start..end, new_text), prevent_auto_indent),
4346 (insert_extra_newline, new_selection),
4347 )
4348 })
4349 .unzip()
4350 };
4351
4352 let mut auto_indent_edits = Vec::new();
4353 let mut edits = Vec::new();
4354 for (edit, prevent_auto_indent) in edits_with_flags {
4355 if prevent_auto_indent {
4356 edits.push(edit);
4357 } else {
4358 auto_indent_edits.push(edit);
4359 }
4360 }
4361 if !edits.is_empty() {
4362 this.edit(edits, cx);
4363 }
4364 if !auto_indent_edits.is_empty() {
4365 this.edit_with_autoindent(auto_indent_edits, cx);
4366 }
4367
4368 let buffer = this.buffer.read(cx).snapshot(cx);
4369 let new_selections = selection_info
4370 .into_iter()
4371 .map(|(extra_newline_inserted, new_selection)| {
4372 let mut cursor = new_selection.end.to_point(&buffer);
4373 if extra_newline_inserted {
4374 cursor.row -= 1;
4375 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4376 }
4377 new_selection.map(|_| cursor)
4378 })
4379 .collect();
4380
4381 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4382 s.select(new_selections)
4383 });
4384 this.refresh_inline_completion(true, false, window, cx);
4385 });
4386 }
4387
4388 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4389 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4390
4391 let buffer = self.buffer.read(cx);
4392 let snapshot = buffer.snapshot(cx);
4393
4394 let mut edits = Vec::new();
4395 let mut rows = Vec::new();
4396
4397 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4398 let cursor = selection.head();
4399 let row = cursor.row;
4400
4401 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4402
4403 let newline = "\n".to_string();
4404 edits.push((start_of_line..start_of_line, newline));
4405
4406 rows.push(row + rows_inserted as u32);
4407 }
4408
4409 self.transact(window, cx, |editor, window, cx| {
4410 editor.edit(edits, cx);
4411
4412 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4413 let mut index = 0;
4414 s.move_cursors_with(|map, _, _| {
4415 let row = rows[index];
4416 index += 1;
4417
4418 let point = Point::new(row, 0);
4419 let boundary = map.next_line_boundary(point).1;
4420 let clipped = map.clip_point(boundary, Bias::Left);
4421
4422 (clipped, SelectionGoal::None)
4423 });
4424 });
4425
4426 let mut indent_edits = Vec::new();
4427 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4428 for row in rows {
4429 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4430 for (row, indent) in indents {
4431 if indent.len == 0 {
4432 continue;
4433 }
4434
4435 let text = match indent.kind {
4436 IndentKind::Space => " ".repeat(indent.len as usize),
4437 IndentKind::Tab => "\t".repeat(indent.len as usize),
4438 };
4439 let point = Point::new(row.0, 0);
4440 indent_edits.push((point..point, text));
4441 }
4442 }
4443 editor.edit(indent_edits, cx);
4444 });
4445 }
4446
4447 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4448 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4449
4450 let buffer = self.buffer.read(cx);
4451 let snapshot = buffer.snapshot(cx);
4452
4453 let mut edits = Vec::new();
4454 let mut rows = Vec::new();
4455 let mut rows_inserted = 0;
4456
4457 for selection in self.selections.all_adjusted(cx) {
4458 let cursor = selection.head();
4459 let row = cursor.row;
4460
4461 let point = Point::new(row + 1, 0);
4462 let start_of_line = snapshot.clip_point(point, Bias::Left);
4463
4464 let newline = "\n".to_string();
4465 edits.push((start_of_line..start_of_line, newline));
4466
4467 rows_inserted += 1;
4468 rows.push(row + rows_inserted);
4469 }
4470
4471 self.transact(window, cx, |editor, window, cx| {
4472 editor.edit(edits, cx);
4473
4474 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4475 let mut index = 0;
4476 s.move_cursors_with(|map, _, _| {
4477 let row = rows[index];
4478 index += 1;
4479
4480 let point = Point::new(row, 0);
4481 let boundary = map.next_line_boundary(point).1;
4482 let clipped = map.clip_point(boundary, Bias::Left);
4483
4484 (clipped, SelectionGoal::None)
4485 });
4486 });
4487
4488 let mut indent_edits = Vec::new();
4489 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4490 for row in rows {
4491 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4492 for (row, indent) in indents {
4493 if indent.len == 0 {
4494 continue;
4495 }
4496
4497 let text = match indent.kind {
4498 IndentKind::Space => " ".repeat(indent.len as usize),
4499 IndentKind::Tab => "\t".repeat(indent.len as usize),
4500 };
4501 let point = Point::new(row.0, 0);
4502 indent_edits.push((point..point, text));
4503 }
4504 }
4505 editor.edit(indent_edits, cx);
4506 });
4507 }
4508
4509 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4510 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4511 original_indent_columns: Vec::new(),
4512 });
4513 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4514 }
4515
4516 fn insert_with_autoindent_mode(
4517 &mut self,
4518 text: &str,
4519 autoindent_mode: Option<AutoindentMode>,
4520 window: &mut Window,
4521 cx: &mut Context<Self>,
4522 ) {
4523 if self.read_only(cx) {
4524 return;
4525 }
4526
4527 let text: Arc<str> = text.into();
4528 self.transact(window, cx, |this, window, cx| {
4529 let old_selections = this.selections.all_adjusted(cx);
4530 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4531 let anchors = {
4532 let snapshot = buffer.read(cx);
4533 old_selections
4534 .iter()
4535 .map(|s| {
4536 let anchor = snapshot.anchor_after(s.head());
4537 s.map(|_| anchor)
4538 })
4539 .collect::<Vec<_>>()
4540 };
4541 buffer.edit(
4542 old_selections
4543 .iter()
4544 .map(|s| (s.start..s.end, text.clone())),
4545 autoindent_mode,
4546 cx,
4547 );
4548 anchors
4549 });
4550
4551 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4552 s.select_anchors(selection_anchors);
4553 });
4554
4555 cx.notify();
4556 });
4557 }
4558
4559 fn trigger_completion_on_input(
4560 &mut self,
4561 text: &str,
4562 trigger_in_words: bool,
4563 window: &mut Window,
4564 cx: &mut Context<Self>,
4565 ) {
4566 let completions_source = self
4567 .context_menu
4568 .borrow()
4569 .as_ref()
4570 .and_then(|menu| match menu {
4571 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4572 CodeContextMenu::CodeActions(_) => None,
4573 });
4574
4575 match completions_source {
4576 Some(CompletionsMenuSource::Words) => {
4577 self.show_word_completions(&ShowWordCompletions, window, cx)
4578 }
4579 Some(CompletionsMenuSource::Normal)
4580 | Some(CompletionsMenuSource::SnippetChoices)
4581 | None
4582 if self.is_completion_trigger(
4583 text,
4584 trigger_in_words,
4585 completions_source.is_some(),
4586 cx,
4587 ) =>
4588 {
4589 self.show_completions(
4590 &ShowCompletions {
4591 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4592 },
4593 window,
4594 cx,
4595 )
4596 }
4597 _ => {
4598 self.hide_context_menu(window, cx);
4599 }
4600 }
4601 }
4602
4603 fn is_completion_trigger(
4604 &self,
4605 text: &str,
4606 trigger_in_words: bool,
4607 menu_is_open: bool,
4608 cx: &mut Context<Self>,
4609 ) -> bool {
4610 let position = self.selections.newest_anchor().head();
4611 let multibuffer = self.buffer.read(cx);
4612 let Some(buffer) = position
4613 .buffer_id
4614 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4615 else {
4616 return false;
4617 };
4618
4619 if let Some(completion_provider) = &self.completion_provider {
4620 completion_provider.is_completion_trigger(
4621 &buffer,
4622 position.text_anchor,
4623 text,
4624 trigger_in_words,
4625 menu_is_open,
4626 cx,
4627 )
4628 } else {
4629 false
4630 }
4631 }
4632
4633 /// If any empty selections is touching the start of its innermost containing autoclose
4634 /// region, expand it to select the brackets.
4635 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4636 let selections = self.selections.all::<usize>(cx);
4637 let buffer = self.buffer.read(cx).read(cx);
4638 let new_selections = self
4639 .selections_with_autoclose_regions(selections, &buffer)
4640 .map(|(mut selection, region)| {
4641 if !selection.is_empty() {
4642 return selection;
4643 }
4644
4645 if let Some(region) = region {
4646 let mut range = region.range.to_offset(&buffer);
4647 if selection.start == range.start && range.start >= region.pair.start.len() {
4648 range.start -= region.pair.start.len();
4649 if buffer.contains_str_at(range.start, ®ion.pair.start)
4650 && buffer.contains_str_at(range.end, ®ion.pair.end)
4651 {
4652 range.end += region.pair.end.len();
4653 selection.start = range.start;
4654 selection.end = range.end;
4655
4656 return selection;
4657 }
4658 }
4659 }
4660
4661 let always_treat_brackets_as_autoclosed = buffer
4662 .language_settings_at(selection.start, cx)
4663 .always_treat_brackets_as_autoclosed;
4664
4665 if !always_treat_brackets_as_autoclosed {
4666 return selection;
4667 }
4668
4669 if let Some(scope) = buffer.language_scope_at(selection.start) {
4670 for (pair, enabled) in scope.brackets() {
4671 if !enabled || !pair.close {
4672 continue;
4673 }
4674
4675 if buffer.contains_str_at(selection.start, &pair.end) {
4676 let pair_start_len = pair.start.len();
4677 if buffer.contains_str_at(
4678 selection.start.saturating_sub(pair_start_len),
4679 &pair.start,
4680 ) {
4681 selection.start -= pair_start_len;
4682 selection.end += pair.end.len();
4683
4684 return selection;
4685 }
4686 }
4687 }
4688 }
4689
4690 selection
4691 })
4692 .collect();
4693
4694 drop(buffer);
4695 self.change_selections(None, window, cx, |selections| {
4696 selections.select(new_selections)
4697 });
4698 }
4699
4700 /// Iterate the given selections, and for each one, find the smallest surrounding
4701 /// autoclose region. This uses the ordering of the selections and the autoclose
4702 /// regions to avoid repeated comparisons.
4703 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4704 &'a self,
4705 selections: impl IntoIterator<Item = Selection<D>>,
4706 buffer: &'a MultiBufferSnapshot,
4707 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4708 let mut i = 0;
4709 let mut regions = self.autoclose_regions.as_slice();
4710 selections.into_iter().map(move |selection| {
4711 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4712
4713 let mut enclosing = None;
4714 while let Some(pair_state) = regions.get(i) {
4715 if pair_state.range.end.to_offset(buffer) < range.start {
4716 regions = ®ions[i + 1..];
4717 i = 0;
4718 } else if pair_state.range.start.to_offset(buffer) > range.end {
4719 break;
4720 } else {
4721 if pair_state.selection_id == selection.id {
4722 enclosing = Some(pair_state);
4723 }
4724 i += 1;
4725 }
4726 }
4727
4728 (selection, enclosing)
4729 })
4730 }
4731
4732 /// Remove any autoclose regions that no longer contain their selection.
4733 fn invalidate_autoclose_regions(
4734 &mut self,
4735 mut selections: &[Selection<Anchor>],
4736 buffer: &MultiBufferSnapshot,
4737 ) {
4738 self.autoclose_regions.retain(|state| {
4739 let mut i = 0;
4740 while let Some(selection) = selections.get(i) {
4741 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4742 selections = &selections[1..];
4743 continue;
4744 }
4745 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4746 break;
4747 }
4748 if selection.id == state.selection_id {
4749 return true;
4750 } else {
4751 i += 1;
4752 }
4753 }
4754 false
4755 });
4756 }
4757
4758 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4759 let offset = position.to_offset(buffer);
4760 let (word_range, kind) = buffer.surrounding_word(offset, true);
4761 if offset > word_range.start && kind == Some(CharKind::Word) {
4762 Some(
4763 buffer
4764 .text_for_range(word_range.start..offset)
4765 .collect::<String>(),
4766 )
4767 } else {
4768 None
4769 }
4770 }
4771
4772 pub fn toggle_inline_values(
4773 &mut self,
4774 _: &ToggleInlineValues,
4775 _: &mut Window,
4776 cx: &mut Context<Self>,
4777 ) {
4778 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4779
4780 self.refresh_inline_values(cx);
4781 }
4782
4783 pub fn toggle_inlay_hints(
4784 &mut self,
4785 _: &ToggleInlayHints,
4786 _: &mut Window,
4787 cx: &mut Context<Self>,
4788 ) {
4789 self.refresh_inlay_hints(
4790 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4791 cx,
4792 );
4793 }
4794
4795 pub fn inlay_hints_enabled(&self) -> bool {
4796 self.inlay_hint_cache.enabled
4797 }
4798
4799 pub fn inline_values_enabled(&self) -> bool {
4800 self.inline_value_cache.enabled
4801 }
4802
4803 #[cfg(any(test, feature = "test-support"))]
4804 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4805 self.display_map
4806 .read(cx)
4807 .current_inlays()
4808 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4809 .cloned()
4810 .collect()
4811 }
4812
4813 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4814 if self.semantics_provider.is_none() || !self.mode.is_full() {
4815 return;
4816 }
4817
4818 let reason_description = reason.description();
4819 let ignore_debounce = matches!(
4820 reason,
4821 InlayHintRefreshReason::SettingsChange(_)
4822 | InlayHintRefreshReason::Toggle(_)
4823 | InlayHintRefreshReason::ExcerptsRemoved(_)
4824 | InlayHintRefreshReason::ModifiersChanged(_)
4825 );
4826 let (invalidate_cache, required_languages) = match reason {
4827 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4828 match self.inlay_hint_cache.modifiers_override(enabled) {
4829 Some(enabled) => {
4830 if enabled {
4831 (InvalidationStrategy::RefreshRequested, None)
4832 } else {
4833 self.splice_inlays(
4834 &self
4835 .visible_inlay_hints(cx)
4836 .iter()
4837 .map(|inlay| inlay.id)
4838 .collect::<Vec<InlayId>>(),
4839 Vec::new(),
4840 cx,
4841 );
4842 return;
4843 }
4844 }
4845 None => return,
4846 }
4847 }
4848 InlayHintRefreshReason::Toggle(enabled) => {
4849 if self.inlay_hint_cache.toggle(enabled) {
4850 if enabled {
4851 (InvalidationStrategy::RefreshRequested, None)
4852 } else {
4853 self.splice_inlays(
4854 &self
4855 .visible_inlay_hints(cx)
4856 .iter()
4857 .map(|inlay| inlay.id)
4858 .collect::<Vec<InlayId>>(),
4859 Vec::new(),
4860 cx,
4861 );
4862 return;
4863 }
4864 } else {
4865 return;
4866 }
4867 }
4868 InlayHintRefreshReason::SettingsChange(new_settings) => {
4869 match self.inlay_hint_cache.update_settings(
4870 &self.buffer,
4871 new_settings,
4872 self.visible_inlay_hints(cx),
4873 cx,
4874 ) {
4875 ControlFlow::Break(Some(InlaySplice {
4876 to_remove,
4877 to_insert,
4878 })) => {
4879 self.splice_inlays(&to_remove, to_insert, cx);
4880 return;
4881 }
4882 ControlFlow::Break(None) => return,
4883 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4884 }
4885 }
4886 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4887 if let Some(InlaySplice {
4888 to_remove,
4889 to_insert,
4890 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4891 {
4892 self.splice_inlays(&to_remove, to_insert, cx);
4893 }
4894 self.display_map.update(cx, |display_map, _| {
4895 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4896 });
4897 return;
4898 }
4899 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4900 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4901 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4902 }
4903 InlayHintRefreshReason::RefreshRequested => {
4904 (InvalidationStrategy::RefreshRequested, None)
4905 }
4906 };
4907
4908 if let Some(InlaySplice {
4909 to_remove,
4910 to_insert,
4911 }) = self.inlay_hint_cache.spawn_hint_refresh(
4912 reason_description,
4913 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4914 invalidate_cache,
4915 ignore_debounce,
4916 cx,
4917 ) {
4918 self.splice_inlays(&to_remove, to_insert, cx);
4919 }
4920 }
4921
4922 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4923 self.display_map
4924 .read(cx)
4925 .current_inlays()
4926 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4927 .cloned()
4928 .collect()
4929 }
4930
4931 pub fn excerpts_for_inlay_hints_query(
4932 &self,
4933 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4934 cx: &mut Context<Editor>,
4935 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4936 let Some(project) = self.project.as_ref() else {
4937 return HashMap::default();
4938 };
4939 let project = project.read(cx);
4940 let multi_buffer = self.buffer().read(cx);
4941 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4942 let multi_buffer_visible_start = self
4943 .scroll_manager
4944 .anchor()
4945 .anchor
4946 .to_point(&multi_buffer_snapshot);
4947 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4948 multi_buffer_visible_start
4949 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4950 Bias::Left,
4951 );
4952 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4953 multi_buffer_snapshot
4954 .range_to_buffer_ranges(multi_buffer_visible_range)
4955 .into_iter()
4956 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4957 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4958 let buffer_file = project::File::from_dyn(buffer.file())?;
4959 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4960 let worktree_entry = buffer_worktree
4961 .read(cx)
4962 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4963 if worktree_entry.is_ignored {
4964 return None;
4965 }
4966
4967 let language = buffer.language()?;
4968 if let Some(restrict_to_languages) = restrict_to_languages {
4969 if !restrict_to_languages.contains(language) {
4970 return None;
4971 }
4972 }
4973 Some((
4974 excerpt_id,
4975 (
4976 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4977 buffer.version().clone(),
4978 excerpt_visible_range,
4979 ),
4980 ))
4981 })
4982 .collect()
4983 }
4984
4985 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4986 TextLayoutDetails {
4987 text_system: window.text_system().clone(),
4988 editor_style: self.style.clone().unwrap(),
4989 rem_size: window.rem_size(),
4990 scroll_anchor: self.scroll_manager.anchor(),
4991 visible_rows: self.visible_line_count(),
4992 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4993 }
4994 }
4995
4996 pub fn splice_inlays(
4997 &self,
4998 to_remove: &[InlayId],
4999 to_insert: Vec<Inlay>,
5000 cx: &mut Context<Self>,
5001 ) {
5002 self.display_map.update(cx, |display_map, cx| {
5003 display_map.splice_inlays(to_remove, to_insert, cx)
5004 });
5005 cx.notify();
5006 }
5007
5008 fn trigger_on_type_formatting(
5009 &self,
5010 input: String,
5011 window: &mut Window,
5012 cx: &mut Context<Self>,
5013 ) -> Option<Task<Result<()>>> {
5014 if input.len() != 1 {
5015 return None;
5016 }
5017
5018 let project = self.project.as_ref()?;
5019 let position = self.selections.newest_anchor().head();
5020 let (buffer, buffer_position) = self
5021 .buffer
5022 .read(cx)
5023 .text_anchor_for_position(position, cx)?;
5024
5025 let settings = language_settings::language_settings(
5026 buffer
5027 .read(cx)
5028 .language_at(buffer_position)
5029 .map(|l| l.name()),
5030 buffer.read(cx).file(),
5031 cx,
5032 );
5033 if !settings.use_on_type_format {
5034 return None;
5035 }
5036
5037 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5038 // hence we do LSP request & edit on host side only — add formats to host's history.
5039 let push_to_lsp_host_history = true;
5040 // If this is not the host, append its history with new edits.
5041 let push_to_client_history = project.read(cx).is_via_collab();
5042
5043 let on_type_formatting = project.update(cx, |project, cx| {
5044 project.on_type_format(
5045 buffer.clone(),
5046 buffer_position,
5047 input,
5048 push_to_lsp_host_history,
5049 cx,
5050 )
5051 });
5052 Some(cx.spawn_in(window, async move |editor, cx| {
5053 if let Some(transaction) = on_type_formatting.await? {
5054 if push_to_client_history {
5055 buffer
5056 .update(cx, |buffer, _| {
5057 buffer.push_transaction(transaction, Instant::now());
5058 buffer.finalize_last_transaction();
5059 })
5060 .ok();
5061 }
5062 editor.update(cx, |editor, cx| {
5063 editor.refresh_document_highlights(cx);
5064 })?;
5065 }
5066 Ok(())
5067 }))
5068 }
5069
5070 pub fn show_word_completions(
5071 &mut self,
5072 _: &ShowWordCompletions,
5073 window: &mut Window,
5074 cx: &mut Context<Self>,
5075 ) {
5076 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5077 }
5078
5079 pub fn show_completions(
5080 &mut self,
5081 options: &ShowCompletions,
5082 window: &mut Window,
5083 cx: &mut Context<Self>,
5084 ) {
5085 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5086 }
5087
5088 fn open_or_update_completions_menu(
5089 &mut self,
5090 requested_source: Option<CompletionsMenuSource>,
5091 trigger: Option<&str>,
5092 window: &mut Window,
5093 cx: &mut Context<Self>,
5094 ) {
5095 if self.pending_rename.is_some() {
5096 return;
5097 }
5098
5099 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5100
5101 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5102 // inserted and selected. To handle that case, the start of the selection is used so that
5103 // the menu starts with all choices.
5104 let position = self
5105 .selections
5106 .newest_anchor()
5107 .start
5108 .bias_right(&multibuffer_snapshot);
5109 if position.diff_base_anchor.is_some() {
5110 return;
5111 }
5112 let (buffer, buffer_position) =
5113 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5114 output
5115 } else {
5116 return;
5117 };
5118 let buffer_snapshot = buffer.read(cx).snapshot();
5119
5120 let query: Option<Arc<String>> =
5121 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5122
5123 drop(multibuffer_snapshot);
5124
5125 let provider = match requested_source {
5126 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5127 Some(CompletionsMenuSource::Words) => None,
5128 Some(CompletionsMenuSource::SnippetChoices) => {
5129 log::error!("bug: SnippetChoices requested_source is not handled");
5130 None
5131 }
5132 };
5133
5134 let sort_completions = provider
5135 .as_ref()
5136 .map_or(false, |provider| provider.sort_completions());
5137
5138 let filter_completions = provider
5139 .as_ref()
5140 .map_or(true, |provider| provider.filter_completions());
5141
5142 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5143 if filter_completions {
5144 menu.filter(query.clone(), provider.clone(), window, cx);
5145 }
5146 // When `is_incomplete` is false, no need to re-query completions when the current query
5147 // is a suffix of the initial query.
5148 if !menu.is_incomplete {
5149 // If the new query is a suffix of the old query (typing more characters) and
5150 // the previous result was complete, the existing completions can be filtered.
5151 //
5152 // Note that this is always true for snippet completions.
5153 let query_matches = match (&menu.initial_query, &query) {
5154 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5155 (None, _) => true,
5156 _ => false,
5157 };
5158 if query_matches {
5159 let position_matches = if menu.initial_position == position {
5160 true
5161 } else {
5162 let snapshot = self.buffer.read(cx).read(cx);
5163 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5164 };
5165 if position_matches {
5166 return;
5167 }
5168 }
5169 }
5170 };
5171
5172 let trigger_kind = match trigger {
5173 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5174 CompletionTriggerKind::TRIGGER_CHARACTER
5175 }
5176 _ => CompletionTriggerKind::INVOKED,
5177 };
5178 let completion_context = CompletionContext {
5179 trigger_character: trigger.and_then(|trigger| {
5180 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5181 Some(String::from(trigger))
5182 } else {
5183 None
5184 }
5185 }),
5186 trigger_kind,
5187 };
5188
5189 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5190 buffer_snapshot.surrounding_word(buffer_position)
5191 {
5192 let word_to_exclude = buffer_snapshot
5193 .text_for_range(word_range.clone())
5194 .collect::<String>();
5195 (
5196 buffer_snapshot.anchor_before(word_range.start)
5197 ..buffer_snapshot.anchor_after(buffer_position),
5198 Some(word_to_exclude),
5199 )
5200 } else {
5201 (buffer_position..buffer_position, None)
5202 };
5203
5204 let language = buffer_snapshot
5205 .language_at(buffer_position)
5206 .map(|language| language.name());
5207
5208 let completion_settings =
5209 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5210
5211 let show_completion_documentation = buffer_snapshot
5212 .settings_at(buffer_position, cx)
5213 .show_completion_documentation;
5214
5215 // The document can be large, so stay in reasonable bounds when searching for words,
5216 // otherwise completion pop-up might be slow to appear.
5217 const WORD_LOOKUP_ROWS: u32 = 5_000;
5218 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5219 let min_word_search = buffer_snapshot.clip_point(
5220 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5221 Bias::Left,
5222 );
5223 let max_word_search = buffer_snapshot.clip_point(
5224 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5225 Bias::Right,
5226 );
5227 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5228 ..buffer_snapshot.point_to_offset(max_word_search);
5229
5230 let skip_digits = query
5231 .as_ref()
5232 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5233
5234 let (mut words, provider_responses) = match &provider {
5235 Some(provider) => {
5236 let provider_responses = provider.completions(
5237 position.excerpt_id,
5238 &buffer,
5239 buffer_position,
5240 completion_context,
5241 window,
5242 cx,
5243 );
5244
5245 let words = match completion_settings.words {
5246 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5247 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5248 .background_spawn(async move {
5249 buffer_snapshot.words_in_range(WordsQuery {
5250 fuzzy_contents: None,
5251 range: word_search_range,
5252 skip_digits,
5253 })
5254 }),
5255 };
5256
5257 (words, provider_responses)
5258 }
5259 None => (
5260 cx.background_spawn(async move {
5261 buffer_snapshot.words_in_range(WordsQuery {
5262 fuzzy_contents: None,
5263 range: word_search_range,
5264 skip_digits,
5265 })
5266 }),
5267 Task::ready(Ok(Vec::new())),
5268 ),
5269 };
5270
5271 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5272
5273 let id = post_inc(&mut self.next_completion_id);
5274 let task = cx.spawn_in(window, async move |editor, cx| {
5275 let Ok(()) = editor.update(cx, |this, _| {
5276 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5277 }) else {
5278 return;
5279 };
5280
5281 // TODO: Ideally completions from different sources would be selectively re-queried, so
5282 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5283 let mut completions = Vec::new();
5284 let mut is_incomplete = false;
5285 if let Some(provider_responses) = provider_responses.await.log_err() {
5286 if !provider_responses.is_empty() {
5287 for response in provider_responses {
5288 completions.extend(response.completions);
5289 is_incomplete = is_incomplete || response.is_incomplete;
5290 }
5291 if completion_settings.words == WordsCompletionMode::Fallback {
5292 words = Task::ready(BTreeMap::default());
5293 }
5294 }
5295 }
5296
5297 let mut words = words.await;
5298 if let Some(word_to_exclude) = &word_to_exclude {
5299 words.remove(word_to_exclude);
5300 }
5301 for lsp_completion in &completions {
5302 words.remove(&lsp_completion.new_text);
5303 }
5304 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5305 replace_range: word_replace_range.clone(),
5306 new_text: word.clone(),
5307 label: CodeLabel::plain(word, None),
5308 icon_path: None,
5309 documentation: None,
5310 source: CompletionSource::BufferWord {
5311 word_range,
5312 resolved: false,
5313 },
5314 insert_text_mode: Some(InsertTextMode::AS_IS),
5315 confirm: None,
5316 }));
5317
5318 let menu = if completions.is_empty() {
5319 None
5320 } else {
5321 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5322 let languages = editor
5323 .workspace
5324 .as_ref()
5325 .and_then(|(workspace, _)| workspace.upgrade())
5326 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5327 let menu = CompletionsMenu::new(
5328 id,
5329 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5330 sort_completions,
5331 show_completion_documentation,
5332 position,
5333 query.clone(),
5334 is_incomplete,
5335 buffer.clone(),
5336 completions.into(),
5337 snippet_sort_order,
5338 languages,
5339 language,
5340 cx,
5341 );
5342
5343 let query = if filter_completions { query } else { None };
5344 let matches_task = if let Some(query) = query {
5345 menu.do_async_filtering(query, cx)
5346 } else {
5347 Task::ready(menu.unfiltered_matches())
5348 };
5349 (menu, matches_task)
5350 }) else {
5351 return;
5352 };
5353
5354 let matches = matches_task.await;
5355
5356 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5357 // Newer menu already set, so exit.
5358 match editor.context_menu.borrow().as_ref() {
5359 Some(CodeContextMenu::Completions(prev_menu)) => {
5360 if prev_menu.id > id {
5361 return;
5362 }
5363 }
5364 _ => {}
5365 };
5366
5367 // Only valid to take prev_menu because it the new menu is immediately set
5368 // below, or the menu is hidden.
5369 match editor.context_menu.borrow_mut().take() {
5370 Some(CodeContextMenu::Completions(prev_menu)) => {
5371 let position_matches =
5372 if prev_menu.initial_position == menu.initial_position {
5373 true
5374 } else {
5375 let snapshot = editor.buffer.read(cx).read(cx);
5376 prev_menu.initial_position.to_offset(&snapshot)
5377 == menu.initial_position.to_offset(&snapshot)
5378 };
5379 if position_matches {
5380 // Preserve markdown cache before `set_filter_results` because it will
5381 // try to populate the documentation cache.
5382 menu.preserve_markdown_cache(prev_menu);
5383 }
5384 }
5385 _ => {}
5386 };
5387
5388 menu.set_filter_results(matches, provider, window, cx);
5389 }) else {
5390 return;
5391 };
5392
5393 menu.visible().then_some(menu)
5394 };
5395
5396 editor
5397 .update_in(cx, |editor, window, cx| {
5398 if editor.focus_handle.is_focused(window) {
5399 if let Some(menu) = menu {
5400 *editor.context_menu.borrow_mut() =
5401 Some(CodeContextMenu::Completions(menu));
5402
5403 crate::hover_popover::hide_hover(editor, cx);
5404 if editor.show_edit_predictions_in_menu() {
5405 editor.update_visible_inline_completion(window, cx);
5406 } else {
5407 editor.discard_inline_completion(false, cx);
5408 }
5409
5410 cx.notify();
5411 return;
5412 }
5413 }
5414
5415 if editor.completion_tasks.len() <= 1 {
5416 // If there are no more completion tasks and the last menu was empty, we should hide it.
5417 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5418 // If it was already hidden and we don't show inline completions in the menu, we should
5419 // also show the inline-completion when available.
5420 if was_hidden && editor.show_edit_predictions_in_menu() {
5421 editor.update_visible_inline_completion(window, cx);
5422 }
5423 }
5424 })
5425 .ok();
5426 });
5427
5428 self.completion_tasks.push((id, task));
5429 }
5430
5431 #[cfg(feature = "test-support")]
5432 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5433 let menu = self.context_menu.borrow();
5434 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5435 let completions = menu.completions.borrow();
5436 Some(completions.to_vec())
5437 } else {
5438 None
5439 }
5440 }
5441
5442 pub fn with_completions_menu_matching_id<R>(
5443 &self,
5444 id: CompletionId,
5445 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5446 ) -> R {
5447 let mut context_menu = self.context_menu.borrow_mut();
5448 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5449 return f(None);
5450 };
5451 if completions_menu.id != id {
5452 return f(None);
5453 }
5454 f(Some(completions_menu))
5455 }
5456
5457 pub fn confirm_completion(
5458 &mut self,
5459 action: &ConfirmCompletion,
5460 window: &mut Window,
5461 cx: &mut Context<Self>,
5462 ) -> Option<Task<Result<()>>> {
5463 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5464 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5465 }
5466
5467 pub fn confirm_completion_insert(
5468 &mut self,
5469 _: &ConfirmCompletionInsert,
5470 window: &mut Window,
5471 cx: &mut Context<Self>,
5472 ) -> Option<Task<Result<()>>> {
5473 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5474 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5475 }
5476
5477 pub fn confirm_completion_replace(
5478 &mut self,
5479 _: &ConfirmCompletionReplace,
5480 window: &mut Window,
5481 cx: &mut Context<Self>,
5482 ) -> Option<Task<Result<()>>> {
5483 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5484 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5485 }
5486
5487 pub fn compose_completion(
5488 &mut self,
5489 action: &ComposeCompletion,
5490 window: &mut Window,
5491 cx: &mut Context<Self>,
5492 ) -> Option<Task<Result<()>>> {
5493 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5494 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5495 }
5496
5497 fn do_completion(
5498 &mut self,
5499 item_ix: Option<usize>,
5500 intent: CompletionIntent,
5501 window: &mut Window,
5502 cx: &mut Context<Editor>,
5503 ) -> Option<Task<Result<()>>> {
5504 use language::ToOffset as _;
5505
5506 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5507 else {
5508 return None;
5509 };
5510
5511 let candidate_id = {
5512 let entries = completions_menu.entries.borrow();
5513 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5514 if self.show_edit_predictions_in_menu() {
5515 self.discard_inline_completion(true, cx);
5516 }
5517 mat.candidate_id
5518 };
5519
5520 let completion = completions_menu
5521 .completions
5522 .borrow()
5523 .get(candidate_id)?
5524 .clone();
5525 cx.stop_propagation();
5526
5527 let buffer_handle = completions_menu.buffer.clone();
5528
5529 let CompletionEdit {
5530 new_text,
5531 snippet,
5532 replace_range,
5533 } = process_completion_for_edit(
5534 &completion,
5535 intent,
5536 &buffer_handle,
5537 &completions_menu.initial_position.text_anchor,
5538 cx,
5539 );
5540
5541 let buffer = buffer_handle.read(cx);
5542 let snapshot = self.buffer.read(cx).snapshot(cx);
5543 let newest_anchor = self.selections.newest_anchor();
5544 let replace_range_multibuffer = {
5545 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5546 let multibuffer_anchor = snapshot
5547 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5548 .unwrap()
5549 ..snapshot
5550 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5551 .unwrap();
5552 multibuffer_anchor.start.to_offset(&snapshot)
5553 ..multibuffer_anchor.end.to_offset(&snapshot)
5554 };
5555 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5556 return None;
5557 }
5558
5559 let old_text = buffer
5560 .text_for_range(replace_range.clone())
5561 .collect::<String>();
5562 let lookbehind = newest_anchor
5563 .start
5564 .text_anchor
5565 .to_offset(buffer)
5566 .saturating_sub(replace_range.start);
5567 let lookahead = replace_range
5568 .end
5569 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5570 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5571 let suffix = &old_text[lookbehind.min(old_text.len())..];
5572
5573 let selections = self.selections.all::<usize>(cx);
5574 let mut ranges = Vec::new();
5575 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5576
5577 for selection in &selections {
5578 let range = if selection.id == newest_anchor.id {
5579 replace_range_multibuffer.clone()
5580 } else {
5581 let mut range = selection.range();
5582
5583 // if prefix is present, don't duplicate it
5584 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5585 range.start = range.start.saturating_sub(lookbehind);
5586
5587 // if suffix is also present, mimic the newest cursor and replace it
5588 if selection.id != newest_anchor.id
5589 && snapshot.contains_str_at(range.end, suffix)
5590 {
5591 range.end += lookahead;
5592 }
5593 }
5594 range
5595 };
5596
5597 ranges.push(range.clone());
5598
5599 if !self.linked_edit_ranges.is_empty() {
5600 let start_anchor = snapshot.anchor_before(range.start);
5601 let end_anchor = snapshot.anchor_after(range.end);
5602 if let Some(ranges) = self
5603 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5604 {
5605 for (buffer, edits) in ranges {
5606 linked_edits
5607 .entry(buffer.clone())
5608 .or_default()
5609 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5610 }
5611 }
5612 }
5613 }
5614
5615 let common_prefix_len = old_text
5616 .chars()
5617 .zip(new_text.chars())
5618 .take_while(|(a, b)| a == b)
5619 .map(|(a, _)| a.len_utf8())
5620 .sum::<usize>();
5621
5622 cx.emit(EditorEvent::InputHandled {
5623 utf16_range_to_replace: None,
5624 text: new_text[common_prefix_len..].into(),
5625 });
5626
5627 self.transact(window, cx, |this, window, cx| {
5628 if let Some(mut snippet) = snippet {
5629 snippet.text = new_text.to_string();
5630 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5631 } else {
5632 this.buffer.update(cx, |buffer, cx| {
5633 let auto_indent = match completion.insert_text_mode {
5634 Some(InsertTextMode::AS_IS) => None,
5635 _ => this.autoindent_mode.clone(),
5636 };
5637 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5638 buffer.edit(edits, auto_indent, cx);
5639 });
5640 }
5641 for (buffer, edits) in linked_edits {
5642 buffer.update(cx, |buffer, cx| {
5643 let snapshot = buffer.snapshot();
5644 let edits = edits
5645 .into_iter()
5646 .map(|(range, text)| {
5647 use text::ToPoint as TP;
5648 let end_point = TP::to_point(&range.end, &snapshot);
5649 let start_point = TP::to_point(&range.start, &snapshot);
5650 (start_point..end_point, text)
5651 })
5652 .sorted_by_key(|(range, _)| range.start);
5653 buffer.edit(edits, None, cx);
5654 })
5655 }
5656
5657 this.refresh_inline_completion(true, false, window, cx);
5658 });
5659
5660 let show_new_completions_on_confirm = completion
5661 .confirm
5662 .as_ref()
5663 .map_or(false, |confirm| confirm(intent, window, cx));
5664 if show_new_completions_on_confirm {
5665 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5666 }
5667
5668 let provider = self.completion_provider.as_ref()?;
5669 drop(completion);
5670 let apply_edits = provider.apply_additional_edits_for_completion(
5671 buffer_handle,
5672 completions_menu.completions.clone(),
5673 candidate_id,
5674 true,
5675 cx,
5676 );
5677
5678 let editor_settings = EditorSettings::get_global(cx);
5679 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5680 // After the code completion is finished, users often want to know what signatures are needed.
5681 // so we should automatically call signature_help
5682 self.show_signature_help(&ShowSignatureHelp, window, cx);
5683 }
5684
5685 Some(cx.foreground_executor().spawn(async move {
5686 apply_edits.await?;
5687 Ok(())
5688 }))
5689 }
5690
5691 pub fn toggle_code_actions(
5692 &mut self,
5693 action: &ToggleCodeActions,
5694 window: &mut Window,
5695 cx: &mut Context<Self>,
5696 ) {
5697 let quick_launch = action.quick_launch;
5698 let mut context_menu = self.context_menu.borrow_mut();
5699 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5700 if code_actions.deployed_from == action.deployed_from {
5701 // Toggle if we're selecting the same one
5702 *context_menu = None;
5703 cx.notify();
5704 return;
5705 } else {
5706 // Otherwise, clear it and start a new one
5707 *context_menu = None;
5708 cx.notify();
5709 }
5710 }
5711 drop(context_menu);
5712 let snapshot = self.snapshot(window, cx);
5713 let deployed_from = action.deployed_from.clone();
5714 let mut task = self.code_actions_task.take();
5715 let action = action.clone();
5716 cx.spawn_in(window, async move |editor, cx| {
5717 while let Some(prev_task) = task {
5718 prev_task.await.log_err();
5719 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5720 }
5721
5722 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5723 if editor.focus_handle.is_focused(window) {
5724 let multibuffer_point = match &action.deployed_from {
5725 Some(CodeActionSource::Indicator(row)) => {
5726 DisplayPoint::new(*row, 0).to_point(&snapshot)
5727 }
5728 _ => editor.selections.newest::<Point>(cx).head(),
5729 };
5730 let (buffer, buffer_row) = snapshot
5731 .buffer_snapshot
5732 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5733 .and_then(|(buffer_snapshot, range)| {
5734 editor
5735 .buffer
5736 .read(cx)
5737 .buffer(buffer_snapshot.remote_id())
5738 .map(|buffer| (buffer, range.start.row))
5739 })?;
5740 let (_, code_actions) = editor
5741 .available_code_actions
5742 .clone()
5743 .and_then(|(location, code_actions)| {
5744 let snapshot = location.buffer.read(cx).snapshot();
5745 let point_range = location.range.to_point(&snapshot);
5746 let point_range = point_range.start.row..=point_range.end.row;
5747 if point_range.contains(&buffer_row) {
5748 Some((location, code_actions))
5749 } else {
5750 None
5751 }
5752 })
5753 .unzip();
5754 let buffer_id = buffer.read(cx).remote_id();
5755 let tasks = editor
5756 .tasks
5757 .get(&(buffer_id, buffer_row))
5758 .map(|t| Arc::new(t.to_owned()));
5759 if tasks.is_none() && code_actions.is_none() {
5760 return None;
5761 }
5762
5763 editor.completion_tasks.clear();
5764 editor.discard_inline_completion(false, cx);
5765 let task_context =
5766 tasks
5767 .as_ref()
5768 .zip(editor.project.clone())
5769 .map(|(tasks, project)| {
5770 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5771 });
5772
5773 Some(cx.spawn_in(window, async move |editor, cx| {
5774 let task_context = match task_context {
5775 Some(task_context) => task_context.await,
5776 None => None,
5777 };
5778 let resolved_tasks =
5779 tasks
5780 .zip(task_context.clone())
5781 .map(|(tasks, task_context)| ResolvedTasks {
5782 templates: tasks.resolve(&task_context).collect(),
5783 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5784 multibuffer_point.row,
5785 tasks.column,
5786 )),
5787 });
5788 let debug_scenarios = editor.update(cx, |editor, cx| {
5789 if cx.has_flag::<DebuggerFeatureFlag>() {
5790 maybe!({
5791 let project = editor.project.as_ref()?;
5792 let dap_store = project.read(cx).dap_store();
5793 let mut scenarios = vec![];
5794 let resolved_tasks = resolved_tasks.as_ref()?;
5795 let buffer = buffer.read(cx);
5796 let language = buffer.language()?;
5797 let file = buffer.file();
5798 let debug_adapter =
5799 language_settings(language.name().into(), file, cx)
5800 .debuggers
5801 .first()
5802 .map(SharedString::from)
5803 .or_else(|| {
5804 language
5805 .config()
5806 .debuggers
5807 .first()
5808 .map(SharedString::from)
5809 })?;
5810
5811 dap_store.update(cx, |dap_store, cx| {
5812 for (_, task) in &resolved_tasks.templates {
5813 if let Some(scenario) = dap_store
5814 .debug_scenario_for_build_task(
5815 task.original_task().clone(),
5816 debug_adapter.clone().into(),
5817 task.display_label().to_owned().into(),
5818 cx,
5819 )
5820 {
5821 scenarios.push(scenario);
5822 }
5823 }
5824 });
5825 Some(scenarios)
5826 })
5827 .unwrap_or_default()
5828 } else {
5829 vec![]
5830 }
5831 })?;
5832 let spawn_straight_away = quick_launch
5833 && resolved_tasks
5834 .as_ref()
5835 .map_or(false, |tasks| tasks.templates.len() == 1)
5836 && code_actions
5837 .as_ref()
5838 .map_or(true, |actions| actions.is_empty())
5839 && debug_scenarios.is_empty();
5840 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5841 crate::hover_popover::hide_hover(editor, cx);
5842 *editor.context_menu.borrow_mut() =
5843 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5844 buffer,
5845 actions: CodeActionContents::new(
5846 resolved_tasks,
5847 code_actions,
5848 debug_scenarios,
5849 task_context.unwrap_or_default(),
5850 ),
5851 selected_item: Default::default(),
5852 scroll_handle: UniformListScrollHandle::default(),
5853 deployed_from,
5854 }));
5855 if spawn_straight_away {
5856 if let Some(task) = editor.confirm_code_action(
5857 &ConfirmCodeAction { item_ix: Some(0) },
5858 window,
5859 cx,
5860 ) {
5861 cx.notify();
5862 return task;
5863 }
5864 }
5865 cx.notify();
5866 Task::ready(Ok(()))
5867 }) {
5868 task.await
5869 } else {
5870 Ok(())
5871 }
5872 }))
5873 } else {
5874 Some(Task::ready(Ok(())))
5875 }
5876 })?;
5877 if let Some(task) = spawned_test_task {
5878 task.await?;
5879 }
5880
5881 anyhow::Ok(())
5882 })
5883 .detach_and_log_err(cx);
5884 }
5885
5886 pub fn confirm_code_action(
5887 &mut self,
5888 action: &ConfirmCodeAction,
5889 window: &mut Window,
5890 cx: &mut Context<Self>,
5891 ) -> Option<Task<Result<()>>> {
5892 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5893
5894 let actions_menu =
5895 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5896 menu
5897 } else {
5898 return None;
5899 };
5900
5901 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5902 let action = actions_menu.actions.get(action_ix)?;
5903 let title = action.label();
5904 let buffer = actions_menu.buffer;
5905 let workspace = self.workspace()?;
5906
5907 match action {
5908 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5909 workspace.update(cx, |workspace, cx| {
5910 workspace.schedule_resolved_task(
5911 task_source_kind,
5912 resolved_task,
5913 false,
5914 window,
5915 cx,
5916 );
5917
5918 Some(Task::ready(Ok(())))
5919 })
5920 }
5921 CodeActionsItem::CodeAction {
5922 excerpt_id,
5923 action,
5924 provider,
5925 } => {
5926 let apply_code_action =
5927 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5928 let workspace = workspace.downgrade();
5929 Some(cx.spawn_in(window, async move |editor, cx| {
5930 let project_transaction = apply_code_action.await?;
5931 Self::open_project_transaction(
5932 &editor,
5933 workspace,
5934 project_transaction,
5935 title,
5936 cx,
5937 )
5938 .await
5939 }))
5940 }
5941 CodeActionsItem::DebugScenario(scenario) => {
5942 let context = actions_menu.actions.context.clone();
5943
5944 workspace.update(cx, |workspace, cx| {
5945 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
5946 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5947 });
5948 Some(Task::ready(Ok(())))
5949 }
5950 }
5951 }
5952
5953 pub async fn open_project_transaction(
5954 this: &WeakEntity<Editor>,
5955 workspace: WeakEntity<Workspace>,
5956 transaction: ProjectTransaction,
5957 title: String,
5958 cx: &mut AsyncWindowContext,
5959 ) -> Result<()> {
5960 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5961 cx.update(|_, cx| {
5962 entries.sort_unstable_by_key(|(buffer, _)| {
5963 buffer.read(cx).file().map(|f| f.path().clone())
5964 });
5965 })?;
5966
5967 // If the project transaction's edits are all contained within this editor, then
5968 // avoid opening a new editor to display them.
5969
5970 if let Some((buffer, transaction)) = entries.first() {
5971 if entries.len() == 1 {
5972 let excerpt = this.update(cx, |editor, cx| {
5973 editor
5974 .buffer()
5975 .read(cx)
5976 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5977 })?;
5978 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5979 if excerpted_buffer == *buffer {
5980 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5981 let excerpt_range = excerpt_range.to_offset(buffer);
5982 buffer
5983 .edited_ranges_for_transaction::<usize>(transaction)
5984 .all(|range| {
5985 excerpt_range.start <= range.start
5986 && excerpt_range.end >= range.end
5987 })
5988 })?;
5989
5990 if all_edits_within_excerpt {
5991 return Ok(());
5992 }
5993 }
5994 }
5995 }
5996 } else {
5997 return Ok(());
5998 }
5999
6000 let mut ranges_to_highlight = Vec::new();
6001 let excerpt_buffer = cx.new(|cx| {
6002 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6003 for (buffer_handle, transaction) in &entries {
6004 let edited_ranges = buffer_handle
6005 .read(cx)
6006 .edited_ranges_for_transaction::<Point>(transaction)
6007 .collect::<Vec<_>>();
6008 let (ranges, _) = multibuffer.set_excerpts_for_path(
6009 PathKey::for_buffer(buffer_handle, cx),
6010 buffer_handle.clone(),
6011 edited_ranges,
6012 DEFAULT_MULTIBUFFER_CONTEXT,
6013 cx,
6014 );
6015
6016 ranges_to_highlight.extend(ranges);
6017 }
6018 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6019 multibuffer
6020 })?;
6021
6022 workspace.update_in(cx, |workspace, window, cx| {
6023 let project = workspace.project().clone();
6024 let editor =
6025 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6026 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6027 editor.update(cx, |editor, cx| {
6028 editor.highlight_background::<Self>(
6029 &ranges_to_highlight,
6030 |theme| theme.editor_highlighted_line_background,
6031 cx,
6032 );
6033 });
6034 })?;
6035
6036 Ok(())
6037 }
6038
6039 pub fn clear_code_action_providers(&mut self) {
6040 self.code_action_providers.clear();
6041 self.available_code_actions.take();
6042 }
6043
6044 pub fn add_code_action_provider(
6045 &mut self,
6046 provider: Rc<dyn CodeActionProvider>,
6047 window: &mut Window,
6048 cx: &mut Context<Self>,
6049 ) {
6050 if self
6051 .code_action_providers
6052 .iter()
6053 .any(|existing_provider| existing_provider.id() == provider.id())
6054 {
6055 return;
6056 }
6057
6058 self.code_action_providers.push(provider);
6059 self.refresh_code_actions(window, cx);
6060 }
6061
6062 pub fn remove_code_action_provider(
6063 &mut self,
6064 id: Arc<str>,
6065 window: &mut Window,
6066 cx: &mut Context<Self>,
6067 ) {
6068 self.code_action_providers
6069 .retain(|provider| provider.id() != id);
6070 self.refresh_code_actions(window, cx);
6071 }
6072
6073 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6074 !self.code_action_providers.is_empty()
6075 && EditorSettings::get_global(cx).toolbar.code_actions
6076 }
6077
6078 pub fn has_available_code_actions(&self) -> bool {
6079 self.available_code_actions
6080 .as_ref()
6081 .is_some_and(|(_, actions)| !actions.is_empty())
6082 }
6083
6084 fn render_inline_code_actions(
6085 &self,
6086 icon_size: ui::IconSize,
6087 display_row: DisplayRow,
6088 is_active: bool,
6089 cx: &mut Context<Self>,
6090 ) -> AnyElement {
6091 let show_tooltip = !self.context_menu_visible();
6092 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6093 .icon_size(icon_size)
6094 .shape(ui::IconButtonShape::Square)
6095 .style(ButtonStyle::Transparent)
6096 .icon_color(ui::Color::Hidden)
6097 .toggle_state(is_active)
6098 .when(show_tooltip, |this| {
6099 this.tooltip({
6100 let focus_handle = self.focus_handle.clone();
6101 move |window, cx| {
6102 Tooltip::for_action_in(
6103 "Toggle Code Actions",
6104 &ToggleCodeActions {
6105 deployed_from: None,
6106 quick_launch: false,
6107 },
6108 &focus_handle,
6109 window,
6110 cx,
6111 )
6112 }
6113 })
6114 })
6115 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6116 window.focus(&editor.focus_handle(cx));
6117 editor.toggle_code_actions(
6118 &crate::actions::ToggleCodeActions {
6119 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6120 display_row,
6121 )),
6122 quick_launch: false,
6123 },
6124 window,
6125 cx,
6126 );
6127 }))
6128 .into_any_element()
6129 }
6130
6131 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6132 &self.context_menu
6133 }
6134
6135 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6136 let newest_selection = self.selections.newest_anchor().clone();
6137 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6138 let buffer = self.buffer.read(cx);
6139 if newest_selection.head().diff_base_anchor.is_some() {
6140 return None;
6141 }
6142 let (start_buffer, start) =
6143 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6144 let (end_buffer, end) =
6145 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6146 if start_buffer != end_buffer {
6147 return None;
6148 }
6149
6150 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6151 cx.background_executor()
6152 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6153 .await;
6154
6155 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6156 let providers = this.code_action_providers.clone();
6157 let tasks = this
6158 .code_action_providers
6159 .iter()
6160 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6161 .collect::<Vec<_>>();
6162 (providers, tasks)
6163 })?;
6164
6165 let mut actions = Vec::new();
6166 for (provider, provider_actions) in
6167 providers.into_iter().zip(future::join_all(tasks).await)
6168 {
6169 if let Some(provider_actions) = provider_actions.log_err() {
6170 actions.extend(provider_actions.into_iter().map(|action| {
6171 AvailableCodeAction {
6172 excerpt_id: newest_selection.start.excerpt_id,
6173 action,
6174 provider: provider.clone(),
6175 }
6176 }));
6177 }
6178 }
6179
6180 this.update(cx, |this, cx| {
6181 this.available_code_actions = if actions.is_empty() {
6182 None
6183 } else {
6184 Some((
6185 Location {
6186 buffer: start_buffer,
6187 range: start..end,
6188 },
6189 actions.into(),
6190 ))
6191 };
6192 cx.notify();
6193 })
6194 }));
6195 None
6196 }
6197
6198 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6199 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6200 self.show_git_blame_inline = false;
6201
6202 self.show_git_blame_inline_delay_task =
6203 Some(cx.spawn_in(window, async move |this, cx| {
6204 cx.background_executor().timer(delay).await;
6205
6206 this.update(cx, |this, cx| {
6207 this.show_git_blame_inline = true;
6208 cx.notify();
6209 })
6210 .log_err();
6211 }));
6212 }
6213 }
6214
6215 fn show_blame_popover(
6216 &mut self,
6217 blame_entry: &BlameEntry,
6218 position: gpui::Point<Pixels>,
6219 cx: &mut Context<Self>,
6220 ) {
6221 if let Some(state) = &mut self.inline_blame_popover {
6222 state.hide_task.take();
6223 cx.notify();
6224 } else {
6225 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6226 let show_task = cx.spawn(async move |editor, cx| {
6227 cx.background_executor()
6228 .timer(std::time::Duration::from_millis(delay))
6229 .await;
6230 editor
6231 .update(cx, |editor, cx| {
6232 if let Some(state) = &mut editor.inline_blame_popover {
6233 state.show_task = None;
6234 cx.notify();
6235 }
6236 })
6237 .ok();
6238 });
6239 let Some(blame) = self.blame.as_ref() else {
6240 return;
6241 };
6242 let blame = blame.read(cx);
6243 let details = blame.details_for_entry(&blame_entry);
6244 let markdown = cx.new(|cx| {
6245 Markdown::new(
6246 details
6247 .as_ref()
6248 .map(|message| message.message.clone())
6249 .unwrap_or_default(),
6250 None,
6251 None,
6252 cx,
6253 )
6254 });
6255 self.inline_blame_popover = Some(InlineBlamePopover {
6256 position,
6257 show_task: Some(show_task),
6258 hide_task: None,
6259 popover_bounds: None,
6260 popover_state: InlineBlamePopoverState {
6261 scroll_handle: ScrollHandle::new(),
6262 commit_message: details,
6263 markdown,
6264 },
6265 });
6266 }
6267 }
6268
6269 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6270 if let Some(state) = &mut self.inline_blame_popover {
6271 if state.show_task.is_some() {
6272 self.inline_blame_popover.take();
6273 cx.notify();
6274 } else {
6275 let hide_task = cx.spawn(async move |editor, cx| {
6276 cx.background_executor()
6277 .timer(std::time::Duration::from_millis(100))
6278 .await;
6279 editor
6280 .update(cx, |editor, cx| {
6281 editor.inline_blame_popover.take();
6282 cx.notify();
6283 })
6284 .ok();
6285 });
6286 state.hide_task = Some(hide_task);
6287 }
6288 }
6289 }
6290
6291 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6292 if self.pending_rename.is_some() {
6293 return None;
6294 }
6295
6296 let provider = self.semantics_provider.clone()?;
6297 let buffer = self.buffer.read(cx);
6298 let newest_selection = self.selections.newest_anchor().clone();
6299 let cursor_position = newest_selection.head();
6300 let (cursor_buffer, cursor_buffer_position) =
6301 buffer.text_anchor_for_position(cursor_position, cx)?;
6302 let (tail_buffer, tail_buffer_position) =
6303 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6304 if cursor_buffer != tail_buffer {
6305 return None;
6306 }
6307
6308 let snapshot = cursor_buffer.read(cx).snapshot();
6309 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6310 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6311 if start_word_range != end_word_range {
6312 self.document_highlights_task.take();
6313 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6314 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6315 return None;
6316 }
6317
6318 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6319 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6320 cx.background_executor()
6321 .timer(Duration::from_millis(debounce))
6322 .await;
6323
6324 let highlights = if let Some(highlights) = cx
6325 .update(|cx| {
6326 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6327 })
6328 .ok()
6329 .flatten()
6330 {
6331 highlights.await.log_err()
6332 } else {
6333 None
6334 };
6335
6336 if let Some(highlights) = highlights {
6337 this.update(cx, |this, cx| {
6338 if this.pending_rename.is_some() {
6339 return;
6340 }
6341
6342 let buffer_id = cursor_position.buffer_id;
6343 let buffer = this.buffer.read(cx);
6344 if !buffer
6345 .text_anchor_for_position(cursor_position, cx)
6346 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6347 {
6348 return;
6349 }
6350
6351 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6352 let mut write_ranges = Vec::new();
6353 let mut read_ranges = Vec::new();
6354 for highlight in highlights {
6355 for (excerpt_id, excerpt_range) in
6356 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6357 {
6358 let start = highlight
6359 .range
6360 .start
6361 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6362 let end = highlight
6363 .range
6364 .end
6365 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6366 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6367 continue;
6368 }
6369
6370 let range = Anchor {
6371 buffer_id,
6372 excerpt_id,
6373 text_anchor: start,
6374 diff_base_anchor: None,
6375 }..Anchor {
6376 buffer_id,
6377 excerpt_id,
6378 text_anchor: end,
6379 diff_base_anchor: None,
6380 };
6381 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6382 write_ranges.push(range);
6383 } else {
6384 read_ranges.push(range);
6385 }
6386 }
6387 }
6388
6389 this.highlight_background::<DocumentHighlightRead>(
6390 &read_ranges,
6391 |theme| theme.editor_document_highlight_read_background,
6392 cx,
6393 );
6394 this.highlight_background::<DocumentHighlightWrite>(
6395 &write_ranges,
6396 |theme| theme.editor_document_highlight_write_background,
6397 cx,
6398 );
6399 cx.notify();
6400 })
6401 .log_err();
6402 }
6403 }));
6404 None
6405 }
6406
6407 fn prepare_highlight_query_from_selection(
6408 &mut self,
6409 cx: &mut Context<Editor>,
6410 ) -> Option<(String, Range<Anchor>)> {
6411 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6412 return None;
6413 }
6414 if !EditorSettings::get_global(cx).selection_highlight {
6415 return None;
6416 }
6417 if self.selections.count() != 1 || self.selections.line_mode {
6418 return None;
6419 }
6420 let selection = self.selections.newest::<Point>(cx);
6421 if selection.is_empty() || selection.start.row != selection.end.row {
6422 return None;
6423 }
6424 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6425 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6426 let query = multi_buffer_snapshot
6427 .text_for_range(selection_anchor_range.clone())
6428 .collect::<String>();
6429 if query.trim().is_empty() {
6430 return None;
6431 }
6432 Some((query, selection_anchor_range))
6433 }
6434
6435 fn update_selection_occurrence_highlights(
6436 &mut self,
6437 query_text: String,
6438 query_range: Range<Anchor>,
6439 multi_buffer_range_to_query: Range<Point>,
6440 use_debounce: bool,
6441 window: &mut Window,
6442 cx: &mut Context<Editor>,
6443 ) -> Task<()> {
6444 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6445 cx.spawn_in(window, async move |editor, cx| {
6446 if use_debounce {
6447 cx.background_executor()
6448 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6449 .await;
6450 }
6451 let match_task = cx.background_spawn(async move {
6452 let buffer_ranges = multi_buffer_snapshot
6453 .range_to_buffer_ranges(multi_buffer_range_to_query)
6454 .into_iter()
6455 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6456 let mut match_ranges = Vec::new();
6457 let Ok(regex) = project::search::SearchQuery::text(
6458 query_text.clone(),
6459 false,
6460 false,
6461 false,
6462 Default::default(),
6463 Default::default(),
6464 false,
6465 None,
6466 ) else {
6467 return Vec::default();
6468 };
6469 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6470 match_ranges.extend(
6471 regex
6472 .search(&buffer_snapshot, Some(search_range.clone()))
6473 .await
6474 .into_iter()
6475 .filter_map(|match_range| {
6476 let match_start = buffer_snapshot
6477 .anchor_after(search_range.start + match_range.start);
6478 let match_end = buffer_snapshot
6479 .anchor_before(search_range.start + match_range.end);
6480 let match_anchor_range = Anchor::range_in_buffer(
6481 excerpt_id,
6482 buffer_snapshot.remote_id(),
6483 match_start..match_end,
6484 );
6485 (match_anchor_range != query_range).then_some(match_anchor_range)
6486 }),
6487 );
6488 }
6489 match_ranges
6490 });
6491 let match_ranges = match_task.await;
6492 editor
6493 .update_in(cx, |editor, _, cx| {
6494 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6495 if !match_ranges.is_empty() {
6496 editor.highlight_background::<SelectedTextHighlight>(
6497 &match_ranges,
6498 |theme| theme.editor_document_highlight_bracket_background,
6499 cx,
6500 )
6501 }
6502 })
6503 .log_err();
6504 })
6505 }
6506
6507 fn refresh_selected_text_highlights(
6508 &mut self,
6509 on_buffer_edit: bool,
6510 window: &mut Window,
6511 cx: &mut Context<Editor>,
6512 ) {
6513 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6514 else {
6515 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6516 self.quick_selection_highlight_task.take();
6517 self.debounced_selection_highlight_task.take();
6518 return;
6519 };
6520 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6521 if on_buffer_edit
6522 || self
6523 .quick_selection_highlight_task
6524 .as_ref()
6525 .map_or(true, |(prev_anchor_range, _)| {
6526 prev_anchor_range != &query_range
6527 })
6528 {
6529 let multi_buffer_visible_start = self
6530 .scroll_manager
6531 .anchor()
6532 .anchor
6533 .to_point(&multi_buffer_snapshot);
6534 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6535 multi_buffer_visible_start
6536 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6537 Bias::Left,
6538 );
6539 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6540 self.quick_selection_highlight_task = Some((
6541 query_range.clone(),
6542 self.update_selection_occurrence_highlights(
6543 query_text.clone(),
6544 query_range.clone(),
6545 multi_buffer_visible_range,
6546 false,
6547 window,
6548 cx,
6549 ),
6550 ));
6551 }
6552 if on_buffer_edit
6553 || self
6554 .debounced_selection_highlight_task
6555 .as_ref()
6556 .map_or(true, |(prev_anchor_range, _)| {
6557 prev_anchor_range != &query_range
6558 })
6559 {
6560 let multi_buffer_start = multi_buffer_snapshot
6561 .anchor_before(0)
6562 .to_point(&multi_buffer_snapshot);
6563 let multi_buffer_end = multi_buffer_snapshot
6564 .anchor_after(multi_buffer_snapshot.len())
6565 .to_point(&multi_buffer_snapshot);
6566 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6567 self.debounced_selection_highlight_task = Some((
6568 query_range.clone(),
6569 self.update_selection_occurrence_highlights(
6570 query_text,
6571 query_range,
6572 multi_buffer_full_range,
6573 true,
6574 window,
6575 cx,
6576 ),
6577 ));
6578 }
6579 }
6580
6581 pub fn refresh_inline_completion(
6582 &mut self,
6583 debounce: bool,
6584 user_requested: bool,
6585 window: &mut Window,
6586 cx: &mut Context<Self>,
6587 ) -> Option<()> {
6588 let provider = self.edit_prediction_provider()?;
6589 let cursor = self.selections.newest_anchor().head();
6590 let (buffer, cursor_buffer_position) =
6591 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6592
6593 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6594 self.discard_inline_completion(false, cx);
6595 return None;
6596 }
6597
6598 if !user_requested
6599 && (!self.should_show_edit_predictions()
6600 || !self.is_focused(window)
6601 || buffer.read(cx).is_empty())
6602 {
6603 self.discard_inline_completion(false, cx);
6604 return None;
6605 }
6606
6607 self.update_visible_inline_completion(window, cx);
6608 provider.refresh(
6609 self.project.clone(),
6610 buffer,
6611 cursor_buffer_position,
6612 debounce,
6613 cx,
6614 );
6615 Some(())
6616 }
6617
6618 fn show_edit_predictions_in_menu(&self) -> bool {
6619 match self.edit_prediction_settings {
6620 EditPredictionSettings::Disabled => false,
6621 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6622 }
6623 }
6624
6625 pub fn edit_predictions_enabled(&self) -> bool {
6626 match self.edit_prediction_settings {
6627 EditPredictionSettings::Disabled => false,
6628 EditPredictionSettings::Enabled { .. } => true,
6629 }
6630 }
6631
6632 fn edit_prediction_requires_modifier(&self) -> bool {
6633 match self.edit_prediction_settings {
6634 EditPredictionSettings::Disabled => false,
6635 EditPredictionSettings::Enabled {
6636 preview_requires_modifier,
6637 ..
6638 } => preview_requires_modifier,
6639 }
6640 }
6641
6642 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6643 if self.edit_prediction_provider.is_none() {
6644 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6645 } else {
6646 let selection = self.selections.newest_anchor();
6647 let cursor = selection.head();
6648
6649 if let Some((buffer, cursor_buffer_position)) =
6650 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6651 {
6652 self.edit_prediction_settings =
6653 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6654 }
6655 }
6656 }
6657
6658 fn edit_prediction_settings_at_position(
6659 &self,
6660 buffer: &Entity<Buffer>,
6661 buffer_position: language::Anchor,
6662 cx: &App,
6663 ) -> EditPredictionSettings {
6664 if !self.mode.is_full()
6665 || !self.show_inline_completions_override.unwrap_or(true)
6666 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6667 {
6668 return EditPredictionSettings::Disabled;
6669 }
6670
6671 let buffer = buffer.read(cx);
6672
6673 let file = buffer.file();
6674
6675 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6676 return EditPredictionSettings::Disabled;
6677 };
6678
6679 let by_provider = matches!(
6680 self.menu_inline_completions_policy,
6681 MenuInlineCompletionsPolicy::ByProvider
6682 );
6683
6684 let show_in_menu = by_provider
6685 && self
6686 .edit_prediction_provider
6687 .as_ref()
6688 .map_or(false, |provider| {
6689 provider.provider.show_completions_in_menu()
6690 });
6691
6692 let preview_requires_modifier =
6693 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6694
6695 EditPredictionSettings::Enabled {
6696 show_in_menu,
6697 preview_requires_modifier,
6698 }
6699 }
6700
6701 fn should_show_edit_predictions(&self) -> bool {
6702 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6703 }
6704
6705 pub fn edit_prediction_preview_is_active(&self) -> bool {
6706 matches!(
6707 self.edit_prediction_preview,
6708 EditPredictionPreview::Active { .. }
6709 )
6710 }
6711
6712 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6713 let cursor = self.selections.newest_anchor().head();
6714 if let Some((buffer, cursor_position)) =
6715 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6716 {
6717 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6718 } else {
6719 false
6720 }
6721 }
6722
6723 pub fn supports_minimap(&self, cx: &App) -> bool {
6724 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6725 }
6726
6727 fn edit_predictions_enabled_in_buffer(
6728 &self,
6729 buffer: &Entity<Buffer>,
6730 buffer_position: language::Anchor,
6731 cx: &App,
6732 ) -> bool {
6733 maybe!({
6734 if self.read_only(cx) {
6735 return Some(false);
6736 }
6737 let provider = self.edit_prediction_provider()?;
6738 if !provider.is_enabled(&buffer, buffer_position, cx) {
6739 return Some(false);
6740 }
6741 let buffer = buffer.read(cx);
6742 let Some(file) = buffer.file() else {
6743 return Some(true);
6744 };
6745 let settings = all_language_settings(Some(file), cx);
6746 Some(settings.edit_predictions_enabled_for_file(file, cx))
6747 })
6748 .unwrap_or(false)
6749 }
6750
6751 fn cycle_inline_completion(
6752 &mut self,
6753 direction: Direction,
6754 window: &mut Window,
6755 cx: &mut Context<Self>,
6756 ) -> Option<()> {
6757 let provider = self.edit_prediction_provider()?;
6758 let cursor = self.selections.newest_anchor().head();
6759 let (buffer, cursor_buffer_position) =
6760 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6761 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6762 return None;
6763 }
6764
6765 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6766 self.update_visible_inline_completion(window, cx);
6767
6768 Some(())
6769 }
6770
6771 pub fn show_inline_completion(
6772 &mut self,
6773 _: &ShowEditPrediction,
6774 window: &mut Window,
6775 cx: &mut Context<Self>,
6776 ) {
6777 if !self.has_active_inline_completion() {
6778 self.refresh_inline_completion(false, true, window, cx);
6779 return;
6780 }
6781
6782 self.update_visible_inline_completion(window, cx);
6783 }
6784
6785 pub fn display_cursor_names(
6786 &mut self,
6787 _: &DisplayCursorNames,
6788 window: &mut Window,
6789 cx: &mut Context<Self>,
6790 ) {
6791 self.show_cursor_names(window, cx);
6792 }
6793
6794 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6795 self.show_cursor_names = true;
6796 cx.notify();
6797 cx.spawn_in(window, async move |this, cx| {
6798 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6799 this.update(cx, |this, cx| {
6800 this.show_cursor_names = false;
6801 cx.notify()
6802 })
6803 .ok()
6804 })
6805 .detach();
6806 }
6807
6808 pub fn next_edit_prediction(
6809 &mut self,
6810 _: &NextEditPrediction,
6811 window: &mut Window,
6812 cx: &mut Context<Self>,
6813 ) {
6814 if self.has_active_inline_completion() {
6815 self.cycle_inline_completion(Direction::Next, window, cx);
6816 } else {
6817 let is_copilot_disabled = self
6818 .refresh_inline_completion(false, true, window, cx)
6819 .is_none();
6820 if is_copilot_disabled {
6821 cx.propagate();
6822 }
6823 }
6824 }
6825
6826 pub fn previous_edit_prediction(
6827 &mut self,
6828 _: &PreviousEditPrediction,
6829 window: &mut Window,
6830 cx: &mut Context<Self>,
6831 ) {
6832 if self.has_active_inline_completion() {
6833 self.cycle_inline_completion(Direction::Prev, window, cx);
6834 } else {
6835 let is_copilot_disabled = self
6836 .refresh_inline_completion(false, true, window, cx)
6837 .is_none();
6838 if is_copilot_disabled {
6839 cx.propagate();
6840 }
6841 }
6842 }
6843
6844 pub fn accept_edit_prediction(
6845 &mut self,
6846 _: &AcceptEditPrediction,
6847 window: &mut Window,
6848 cx: &mut Context<Self>,
6849 ) {
6850 if self.show_edit_predictions_in_menu() {
6851 self.hide_context_menu(window, cx);
6852 }
6853
6854 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6855 return;
6856 };
6857
6858 self.report_inline_completion_event(
6859 active_inline_completion.completion_id.clone(),
6860 true,
6861 cx,
6862 );
6863
6864 match &active_inline_completion.completion {
6865 InlineCompletion::Move { target, .. } => {
6866 let target = *target;
6867
6868 if let Some(position_map) = &self.last_position_map {
6869 if position_map
6870 .visible_row_range
6871 .contains(&target.to_display_point(&position_map.snapshot).row())
6872 || !self.edit_prediction_requires_modifier()
6873 {
6874 self.unfold_ranges(&[target..target], true, false, cx);
6875 // Note that this is also done in vim's handler of the Tab action.
6876 self.change_selections(
6877 Some(Autoscroll::newest()),
6878 window,
6879 cx,
6880 |selections| {
6881 selections.select_anchor_ranges([target..target]);
6882 },
6883 );
6884 self.clear_row_highlights::<EditPredictionPreview>();
6885
6886 self.edit_prediction_preview
6887 .set_previous_scroll_position(None);
6888 } else {
6889 self.edit_prediction_preview
6890 .set_previous_scroll_position(Some(
6891 position_map.snapshot.scroll_anchor,
6892 ));
6893
6894 self.highlight_rows::<EditPredictionPreview>(
6895 target..target,
6896 cx.theme().colors().editor_highlighted_line_background,
6897 RowHighlightOptions {
6898 autoscroll: true,
6899 ..Default::default()
6900 },
6901 cx,
6902 );
6903 self.request_autoscroll(Autoscroll::fit(), cx);
6904 }
6905 }
6906 }
6907 InlineCompletion::Edit { edits, .. } => {
6908 if let Some(provider) = self.edit_prediction_provider() {
6909 provider.accept(cx);
6910 }
6911
6912 // Store the transaction ID and selections before applying the edit
6913 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
6914
6915 let snapshot = self.buffer.read(cx).snapshot(cx);
6916 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6917
6918 self.buffer.update(cx, |buffer, cx| {
6919 buffer.edit(edits.iter().cloned(), None, cx)
6920 });
6921
6922 self.change_selections(None, window, cx, |s| {
6923 s.select_anchor_ranges([last_edit_end..last_edit_end]);
6924 });
6925
6926 let selections = self.selections.disjoint_anchors();
6927 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
6928 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
6929 if has_new_transaction {
6930 self.selection_history
6931 .insert_transaction(transaction_id_now, selections);
6932 }
6933 }
6934
6935 self.update_visible_inline_completion(window, cx);
6936 if self.active_inline_completion.is_none() {
6937 self.refresh_inline_completion(true, true, window, cx);
6938 }
6939
6940 cx.notify();
6941 }
6942 }
6943
6944 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6945 }
6946
6947 pub fn accept_partial_inline_completion(
6948 &mut self,
6949 _: &AcceptPartialEditPrediction,
6950 window: &mut Window,
6951 cx: &mut Context<Self>,
6952 ) {
6953 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6954 return;
6955 };
6956 if self.selections.count() != 1 {
6957 return;
6958 }
6959
6960 self.report_inline_completion_event(
6961 active_inline_completion.completion_id.clone(),
6962 true,
6963 cx,
6964 );
6965
6966 match &active_inline_completion.completion {
6967 InlineCompletion::Move { target, .. } => {
6968 let target = *target;
6969 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6970 selections.select_anchor_ranges([target..target]);
6971 });
6972 }
6973 InlineCompletion::Edit { edits, .. } => {
6974 // Find an insertion that starts at the cursor position.
6975 let snapshot = self.buffer.read(cx).snapshot(cx);
6976 let cursor_offset = self.selections.newest::<usize>(cx).head();
6977 let insertion = edits.iter().find_map(|(range, text)| {
6978 let range = range.to_offset(&snapshot);
6979 if range.is_empty() && range.start == cursor_offset {
6980 Some(text)
6981 } else {
6982 None
6983 }
6984 });
6985
6986 if let Some(text) = insertion {
6987 let mut partial_completion = text
6988 .chars()
6989 .by_ref()
6990 .take_while(|c| c.is_alphabetic())
6991 .collect::<String>();
6992 if partial_completion.is_empty() {
6993 partial_completion = text
6994 .chars()
6995 .by_ref()
6996 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6997 .collect::<String>();
6998 }
6999
7000 cx.emit(EditorEvent::InputHandled {
7001 utf16_range_to_replace: None,
7002 text: partial_completion.clone().into(),
7003 });
7004
7005 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7006
7007 self.refresh_inline_completion(true, true, window, cx);
7008 cx.notify();
7009 } else {
7010 self.accept_edit_prediction(&Default::default(), window, cx);
7011 }
7012 }
7013 }
7014 }
7015
7016 fn discard_inline_completion(
7017 &mut self,
7018 should_report_inline_completion_event: bool,
7019 cx: &mut Context<Self>,
7020 ) -> bool {
7021 if should_report_inline_completion_event {
7022 let completion_id = self
7023 .active_inline_completion
7024 .as_ref()
7025 .and_then(|active_completion| active_completion.completion_id.clone());
7026
7027 self.report_inline_completion_event(completion_id, false, cx);
7028 }
7029
7030 if let Some(provider) = self.edit_prediction_provider() {
7031 provider.discard(cx);
7032 }
7033
7034 self.take_active_inline_completion(cx)
7035 }
7036
7037 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7038 let Some(provider) = self.edit_prediction_provider() else {
7039 return;
7040 };
7041
7042 let Some((_, buffer, _)) = self
7043 .buffer
7044 .read(cx)
7045 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7046 else {
7047 return;
7048 };
7049
7050 let extension = buffer
7051 .read(cx)
7052 .file()
7053 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7054
7055 let event_type = match accepted {
7056 true => "Edit Prediction Accepted",
7057 false => "Edit Prediction Discarded",
7058 };
7059 telemetry::event!(
7060 event_type,
7061 provider = provider.name(),
7062 prediction_id = id,
7063 suggestion_accepted = accepted,
7064 file_extension = extension,
7065 );
7066 }
7067
7068 pub fn has_active_inline_completion(&self) -> bool {
7069 self.active_inline_completion.is_some()
7070 }
7071
7072 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7073 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7074 return false;
7075 };
7076
7077 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7078 self.clear_highlights::<InlineCompletionHighlight>(cx);
7079 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7080 true
7081 }
7082
7083 /// Returns true when we're displaying the edit prediction popover below the cursor
7084 /// like we are not previewing and the LSP autocomplete menu is visible
7085 /// or we are in `when_holding_modifier` mode.
7086 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7087 if self.edit_prediction_preview_is_active()
7088 || !self.show_edit_predictions_in_menu()
7089 || !self.edit_predictions_enabled()
7090 {
7091 return false;
7092 }
7093
7094 if self.has_visible_completions_menu() {
7095 return true;
7096 }
7097
7098 has_completion && self.edit_prediction_requires_modifier()
7099 }
7100
7101 fn handle_modifiers_changed(
7102 &mut self,
7103 modifiers: Modifiers,
7104 position_map: &PositionMap,
7105 window: &mut Window,
7106 cx: &mut Context<Self>,
7107 ) {
7108 if self.show_edit_predictions_in_menu() {
7109 self.update_edit_prediction_preview(&modifiers, window, cx);
7110 }
7111
7112 self.update_selection_mode(&modifiers, position_map, window, cx);
7113
7114 let mouse_position = window.mouse_position();
7115 if !position_map.text_hitbox.is_hovered(window) {
7116 return;
7117 }
7118
7119 self.update_hovered_link(
7120 position_map.point_for_position(mouse_position),
7121 &position_map.snapshot,
7122 modifiers,
7123 window,
7124 cx,
7125 )
7126 }
7127
7128 fn multi_cursor_modifier(
7129 cursor_event: bool,
7130 modifiers: &Modifiers,
7131 cx: &mut Context<Self>,
7132 ) -> bool {
7133 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7134 if cursor_event {
7135 match multi_cursor_setting {
7136 MultiCursorModifier::Alt => modifiers.alt,
7137 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7138 }
7139 } else {
7140 match multi_cursor_setting {
7141 MultiCursorModifier::Alt => modifiers.secondary(),
7142 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7143 }
7144 }
7145 }
7146
7147 fn columnar_selection_modifiers(multi_cursor_modifier: bool, modifiers: &Modifiers) -> bool {
7148 modifiers.shift && multi_cursor_modifier && modifiers.number_of_modifiers() == 2
7149 }
7150
7151 fn update_selection_mode(
7152 &mut self,
7153 modifiers: &Modifiers,
7154 position_map: &PositionMap,
7155 window: &mut Window,
7156 cx: &mut Context<Self>,
7157 ) {
7158 let multi_cursor_modifier = Self::multi_cursor_modifier(true, modifiers, cx);
7159 if !Self::columnar_selection_modifiers(multi_cursor_modifier, modifiers)
7160 || self.selections.pending.is_none()
7161 {
7162 return;
7163 }
7164
7165 let mouse_position = window.mouse_position();
7166 let point_for_position = position_map.point_for_position(mouse_position);
7167 let position = point_for_position.previous_valid;
7168
7169 self.select(
7170 SelectPhase::BeginColumnar {
7171 position,
7172 reset: false,
7173 goal_column: point_for_position.exact_unclipped.column(),
7174 },
7175 window,
7176 cx,
7177 );
7178 }
7179
7180 fn update_edit_prediction_preview(
7181 &mut self,
7182 modifiers: &Modifiers,
7183 window: &mut Window,
7184 cx: &mut Context<Self>,
7185 ) {
7186 let mut modifiers_held = false;
7187 if let Some(accept_keystroke) = self
7188 .accept_edit_prediction_keybind(false, window, cx)
7189 .keystroke()
7190 {
7191 modifiers_held = modifiers_held
7192 || (&accept_keystroke.modifiers == modifiers
7193 && accept_keystroke.modifiers.modified());
7194 };
7195 if let Some(accept_partial_keystroke) = self
7196 .accept_edit_prediction_keybind(true, window, cx)
7197 .keystroke()
7198 {
7199 modifiers_held = modifiers_held
7200 || (&accept_partial_keystroke.modifiers == modifiers
7201 && accept_partial_keystroke.modifiers.modified());
7202 }
7203
7204 if modifiers_held {
7205 if matches!(
7206 self.edit_prediction_preview,
7207 EditPredictionPreview::Inactive { .. }
7208 ) {
7209 self.edit_prediction_preview = EditPredictionPreview::Active {
7210 previous_scroll_position: None,
7211 since: Instant::now(),
7212 };
7213
7214 self.update_visible_inline_completion(window, cx);
7215 cx.notify();
7216 }
7217 } else if let EditPredictionPreview::Active {
7218 previous_scroll_position,
7219 since,
7220 } = self.edit_prediction_preview
7221 {
7222 if let (Some(previous_scroll_position), Some(position_map)) =
7223 (previous_scroll_position, self.last_position_map.as_ref())
7224 {
7225 self.set_scroll_position(
7226 previous_scroll_position
7227 .scroll_position(&position_map.snapshot.display_snapshot),
7228 window,
7229 cx,
7230 );
7231 }
7232
7233 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7234 released_too_fast: since.elapsed() < Duration::from_millis(200),
7235 };
7236 self.clear_row_highlights::<EditPredictionPreview>();
7237 self.update_visible_inline_completion(window, cx);
7238 cx.notify();
7239 }
7240 }
7241
7242 fn update_visible_inline_completion(
7243 &mut self,
7244 _window: &mut Window,
7245 cx: &mut Context<Self>,
7246 ) -> Option<()> {
7247 let selection = self.selections.newest_anchor();
7248 let cursor = selection.head();
7249 let multibuffer = self.buffer.read(cx).snapshot(cx);
7250 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7251 let excerpt_id = cursor.excerpt_id;
7252
7253 let show_in_menu = self.show_edit_predictions_in_menu();
7254 let completions_menu_has_precedence = !show_in_menu
7255 && (self.context_menu.borrow().is_some()
7256 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7257
7258 if completions_menu_has_precedence
7259 || !offset_selection.is_empty()
7260 || self
7261 .active_inline_completion
7262 .as_ref()
7263 .map_or(false, |completion| {
7264 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7265 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7266 !invalidation_range.contains(&offset_selection.head())
7267 })
7268 {
7269 self.discard_inline_completion(false, cx);
7270 return None;
7271 }
7272
7273 self.take_active_inline_completion(cx);
7274 let Some(provider) = self.edit_prediction_provider() else {
7275 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7276 return None;
7277 };
7278
7279 let (buffer, cursor_buffer_position) =
7280 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7281
7282 self.edit_prediction_settings =
7283 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7284
7285 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7286
7287 if self.edit_prediction_indent_conflict {
7288 let cursor_point = cursor.to_point(&multibuffer);
7289
7290 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7291
7292 if let Some((_, indent)) = indents.iter().next() {
7293 if indent.len == cursor_point.column {
7294 self.edit_prediction_indent_conflict = false;
7295 }
7296 }
7297 }
7298
7299 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7300 let edits = inline_completion
7301 .edits
7302 .into_iter()
7303 .flat_map(|(range, new_text)| {
7304 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7305 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7306 Some((start..end, new_text))
7307 })
7308 .collect::<Vec<_>>();
7309 if edits.is_empty() {
7310 return None;
7311 }
7312
7313 let first_edit_start = edits.first().unwrap().0.start;
7314 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7315 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7316
7317 let last_edit_end = edits.last().unwrap().0.end;
7318 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7319 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7320
7321 let cursor_row = cursor.to_point(&multibuffer).row;
7322
7323 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7324
7325 let mut inlay_ids = Vec::new();
7326 let invalidation_row_range;
7327 let move_invalidation_row_range = if cursor_row < edit_start_row {
7328 Some(cursor_row..edit_end_row)
7329 } else if cursor_row > edit_end_row {
7330 Some(edit_start_row..cursor_row)
7331 } else {
7332 None
7333 };
7334 let is_move =
7335 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7336 let completion = if is_move {
7337 invalidation_row_range =
7338 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7339 let target = first_edit_start;
7340 InlineCompletion::Move { target, snapshot }
7341 } else {
7342 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7343 && !self.inline_completions_hidden_for_vim_mode;
7344
7345 if show_completions_in_buffer {
7346 if edits
7347 .iter()
7348 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7349 {
7350 let mut inlays = Vec::new();
7351 for (range, new_text) in &edits {
7352 let inlay = Inlay::inline_completion(
7353 post_inc(&mut self.next_inlay_id),
7354 range.start,
7355 new_text.as_str(),
7356 );
7357 inlay_ids.push(inlay.id);
7358 inlays.push(inlay);
7359 }
7360
7361 self.splice_inlays(&[], inlays, cx);
7362 } else {
7363 let background_color = cx.theme().status().deleted_background;
7364 self.highlight_text::<InlineCompletionHighlight>(
7365 edits.iter().map(|(range, _)| range.clone()).collect(),
7366 HighlightStyle {
7367 background_color: Some(background_color),
7368 ..Default::default()
7369 },
7370 cx,
7371 );
7372 }
7373 }
7374
7375 invalidation_row_range = edit_start_row..edit_end_row;
7376
7377 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7378 if provider.show_tab_accept_marker() {
7379 EditDisplayMode::TabAccept
7380 } else {
7381 EditDisplayMode::Inline
7382 }
7383 } else {
7384 EditDisplayMode::DiffPopover
7385 };
7386
7387 InlineCompletion::Edit {
7388 edits,
7389 edit_preview: inline_completion.edit_preview,
7390 display_mode,
7391 snapshot,
7392 }
7393 };
7394
7395 let invalidation_range = multibuffer
7396 .anchor_before(Point::new(invalidation_row_range.start, 0))
7397 ..multibuffer.anchor_after(Point::new(
7398 invalidation_row_range.end,
7399 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7400 ));
7401
7402 self.stale_inline_completion_in_menu = None;
7403 self.active_inline_completion = Some(InlineCompletionState {
7404 inlay_ids,
7405 completion,
7406 completion_id: inline_completion.id,
7407 invalidation_range,
7408 });
7409
7410 cx.notify();
7411
7412 Some(())
7413 }
7414
7415 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7416 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7417 }
7418
7419 fn clear_tasks(&mut self) {
7420 self.tasks.clear()
7421 }
7422
7423 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7424 if self.tasks.insert(key, value).is_some() {
7425 // This case should hopefully be rare, but just in case...
7426 log::error!(
7427 "multiple different run targets found on a single line, only the last target will be rendered"
7428 )
7429 }
7430 }
7431
7432 /// Get all display points of breakpoints that will be rendered within editor
7433 ///
7434 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7435 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7436 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7437 fn active_breakpoints(
7438 &self,
7439 range: Range<DisplayRow>,
7440 window: &mut Window,
7441 cx: &mut Context<Self>,
7442 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7443 let mut breakpoint_display_points = HashMap::default();
7444
7445 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7446 return breakpoint_display_points;
7447 };
7448
7449 let snapshot = self.snapshot(window, cx);
7450
7451 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7452 let Some(project) = self.project.as_ref() else {
7453 return breakpoint_display_points;
7454 };
7455
7456 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7457 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7458
7459 for (buffer_snapshot, range, excerpt_id) in
7460 multi_buffer_snapshot.range_to_buffer_ranges(range)
7461 {
7462 let Some(buffer) = project
7463 .read(cx)
7464 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7465 else {
7466 continue;
7467 };
7468 let breakpoints = breakpoint_store.read(cx).breakpoints(
7469 &buffer,
7470 Some(
7471 buffer_snapshot.anchor_before(range.start)
7472 ..buffer_snapshot.anchor_after(range.end),
7473 ),
7474 buffer_snapshot,
7475 cx,
7476 );
7477 for (breakpoint, state) in breakpoints {
7478 let multi_buffer_anchor =
7479 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7480 let position = multi_buffer_anchor
7481 .to_point(&multi_buffer_snapshot)
7482 .to_display_point(&snapshot);
7483
7484 breakpoint_display_points.insert(
7485 position.row(),
7486 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7487 );
7488 }
7489 }
7490
7491 breakpoint_display_points
7492 }
7493
7494 fn breakpoint_context_menu(
7495 &self,
7496 anchor: Anchor,
7497 window: &mut Window,
7498 cx: &mut Context<Self>,
7499 ) -> Entity<ui::ContextMenu> {
7500 let weak_editor = cx.weak_entity();
7501 let focus_handle = self.focus_handle(cx);
7502
7503 let row = self
7504 .buffer
7505 .read(cx)
7506 .snapshot(cx)
7507 .summary_for_anchor::<Point>(&anchor)
7508 .row;
7509
7510 let breakpoint = self
7511 .breakpoint_at_row(row, window, cx)
7512 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7513
7514 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7515 "Edit Log Breakpoint"
7516 } else {
7517 "Set Log Breakpoint"
7518 };
7519
7520 let condition_breakpoint_msg = if breakpoint
7521 .as_ref()
7522 .is_some_and(|bp| bp.1.condition.is_some())
7523 {
7524 "Edit Condition Breakpoint"
7525 } else {
7526 "Set Condition Breakpoint"
7527 };
7528
7529 let hit_condition_breakpoint_msg = if breakpoint
7530 .as_ref()
7531 .is_some_and(|bp| bp.1.hit_condition.is_some())
7532 {
7533 "Edit Hit Condition Breakpoint"
7534 } else {
7535 "Set Hit Condition Breakpoint"
7536 };
7537
7538 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7539 "Unset Breakpoint"
7540 } else {
7541 "Set Breakpoint"
7542 };
7543
7544 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7545
7546 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7547 BreakpointState::Enabled => Some("Disable"),
7548 BreakpointState::Disabled => Some("Enable"),
7549 });
7550
7551 let (anchor, breakpoint) =
7552 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7553
7554 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7555 menu.on_blur_subscription(Subscription::new(|| {}))
7556 .context(focus_handle)
7557 .when(run_to_cursor, |this| {
7558 let weak_editor = weak_editor.clone();
7559 this.entry("Run to cursor", None, move |window, cx| {
7560 weak_editor
7561 .update(cx, |editor, cx| {
7562 editor.change_selections(None, window, cx, |s| {
7563 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7564 });
7565 })
7566 .ok();
7567
7568 window.dispatch_action(Box::new(RunToCursor), cx);
7569 })
7570 .separator()
7571 })
7572 .when_some(toggle_state_msg, |this, msg| {
7573 this.entry(msg, None, {
7574 let weak_editor = weak_editor.clone();
7575 let breakpoint = breakpoint.clone();
7576 move |_window, cx| {
7577 weak_editor
7578 .update(cx, |this, cx| {
7579 this.edit_breakpoint_at_anchor(
7580 anchor,
7581 breakpoint.as_ref().clone(),
7582 BreakpointEditAction::InvertState,
7583 cx,
7584 );
7585 })
7586 .log_err();
7587 }
7588 })
7589 })
7590 .entry(set_breakpoint_msg, None, {
7591 let weak_editor = weak_editor.clone();
7592 let breakpoint = breakpoint.clone();
7593 move |_window, cx| {
7594 weak_editor
7595 .update(cx, |this, cx| {
7596 this.edit_breakpoint_at_anchor(
7597 anchor,
7598 breakpoint.as_ref().clone(),
7599 BreakpointEditAction::Toggle,
7600 cx,
7601 );
7602 })
7603 .log_err();
7604 }
7605 })
7606 .entry(log_breakpoint_msg, None, {
7607 let breakpoint = breakpoint.clone();
7608 let weak_editor = weak_editor.clone();
7609 move |window, cx| {
7610 weak_editor
7611 .update(cx, |this, cx| {
7612 this.add_edit_breakpoint_block(
7613 anchor,
7614 breakpoint.as_ref(),
7615 BreakpointPromptEditAction::Log,
7616 window,
7617 cx,
7618 );
7619 })
7620 .log_err();
7621 }
7622 })
7623 .entry(condition_breakpoint_msg, None, {
7624 let breakpoint = breakpoint.clone();
7625 let weak_editor = weak_editor.clone();
7626 move |window, cx| {
7627 weak_editor
7628 .update(cx, |this, cx| {
7629 this.add_edit_breakpoint_block(
7630 anchor,
7631 breakpoint.as_ref(),
7632 BreakpointPromptEditAction::Condition,
7633 window,
7634 cx,
7635 );
7636 })
7637 .log_err();
7638 }
7639 })
7640 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7641 weak_editor
7642 .update(cx, |this, cx| {
7643 this.add_edit_breakpoint_block(
7644 anchor,
7645 breakpoint.as_ref(),
7646 BreakpointPromptEditAction::HitCondition,
7647 window,
7648 cx,
7649 );
7650 })
7651 .log_err();
7652 })
7653 })
7654 }
7655
7656 fn render_breakpoint(
7657 &self,
7658 position: Anchor,
7659 row: DisplayRow,
7660 breakpoint: &Breakpoint,
7661 state: Option<BreakpointSessionState>,
7662 cx: &mut Context<Self>,
7663 ) -> IconButton {
7664 let is_rejected = state.is_some_and(|s| !s.verified);
7665 // Is it a breakpoint that shows up when hovering over gutter?
7666 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7667 (false, false),
7668 |PhantomBreakpointIndicator {
7669 is_active,
7670 display_row,
7671 collides_with_existing_breakpoint,
7672 }| {
7673 (
7674 is_active && display_row == row,
7675 collides_with_existing_breakpoint,
7676 )
7677 },
7678 );
7679
7680 let (color, icon) = {
7681 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7682 (false, false) => ui::IconName::DebugBreakpoint,
7683 (true, false) => ui::IconName::DebugLogBreakpoint,
7684 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7685 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7686 };
7687
7688 let color = if is_phantom {
7689 Color::Hint
7690 } else if is_rejected {
7691 Color::Disabled
7692 } else {
7693 Color::Debugger
7694 };
7695
7696 (color, icon)
7697 };
7698
7699 let breakpoint = Arc::from(breakpoint.clone());
7700
7701 let alt_as_text = gpui::Keystroke {
7702 modifiers: Modifiers::secondary_key(),
7703 ..Default::default()
7704 };
7705 let primary_action_text = if breakpoint.is_disabled() {
7706 "Enable breakpoint"
7707 } else if is_phantom && !collides_with_existing {
7708 "Set breakpoint"
7709 } else {
7710 "Unset breakpoint"
7711 };
7712 let focus_handle = self.focus_handle.clone();
7713
7714 let meta = if is_rejected {
7715 SharedString::from("No executable code is associated with this line.")
7716 } else if collides_with_existing && !breakpoint.is_disabled() {
7717 SharedString::from(format!(
7718 "{alt_as_text}-click to disable,\nright-click for more options."
7719 ))
7720 } else {
7721 SharedString::from("Right-click for more options.")
7722 };
7723 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7724 .icon_size(IconSize::XSmall)
7725 .size(ui::ButtonSize::None)
7726 .when(is_rejected, |this| {
7727 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7728 })
7729 .icon_color(color)
7730 .style(ButtonStyle::Transparent)
7731 .on_click(cx.listener({
7732 let breakpoint = breakpoint.clone();
7733
7734 move |editor, event: &ClickEvent, window, cx| {
7735 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7736 BreakpointEditAction::InvertState
7737 } else {
7738 BreakpointEditAction::Toggle
7739 };
7740
7741 window.focus(&editor.focus_handle(cx));
7742 editor.edit_breakpoint_at_anchor(
7743 position,
7744 breakpoint.as_ref().clone(),
7745 edit_action,
7746 cx,
7747 );
7748 }
7749 }))
7750 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7751 editor.set_breakpoint_context_menu(
7752 row,
7753 Some(position),
7754 event.down.position,
7755 window,
7756 cx,
7757 );
7758 }))
7759 .tooltip(move |window, cx| {
7760 Tooltip::with_meta_in(
7761 primary_action_text,
7762 Some(&ToggleBreakpoint),
7763 meta.clone(),
7764 &focus_handle,
7765 window,
7766 cx,
7767 )
7768 })
7769 }
7770
7771 fn build_tasks_context(
7772 project: &Entity<Project>,
7773 buffer: &Entity<Buffer>,
7774 buffer_row: u32,
7775 tasks: &Arc<RunnableTasks>,
7776 cx: &mut Context<Self>,
7777 ) -> Task<Option<task::TaskContext>> {
7778 let position = Point::new(buffer_row, tasks.column);
7779 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7780 let location = Location {
7781 buffer: buffer.clone(),
7782 range: range_start..range_start,
7783 };
7784 // Fill in the environmental variables from the tree-sitter captures
7785 let mut captured_task_variables = TaskVariables::default();
7786 for (capture_name, value) in tasks.extra_variables.clone() {
7787 captured_task_variables.insert(
7788 task::VariableName::Custom(capture_name.into()),
7789 value.clone(),
7790 );
7791 }
7792 project.update(cx, |project, cx| {
7793 project.task_store().update(cx, |task_store, cx| {
7794 task_store.task_context_for_location(captured_task_variables, location, cx)
7795 })
7796 })
7797 }
7798
7799 pub fn spawn_nearest_task(
7800 &mut self,
7801 action: &SpawnNearestTask,
7802 window: &mut Window,
7803 cx: &mut Context<Self>,
7804 ) {
7805 let Some((workspace, _)) = self.workspace.clone() else {
7806 return;
7807 };
7808 let Some(project) = self.project.clone() else {
7809 return;
7810 };
7811
7812 // Try to find a closest, enclosing node using tree-sitter that has a
7813 // task
7814 let Some((buffer, buffer_row, tasks)) = self
7815 .find_enclosing_node_task(cx)
7816 // Or find the task that's closest in row-distance.
7817 .or_else(|| self.find_closest_task(cx))
7818 else {
7819 return;
7820 };
7821
7822 let reveal_strategy = action.reveal;
7823 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7824 cx.spawn_in(window, async move |_, cx| {
7825 let context = task_context.await?;
7826 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7827
7828 let resolved = &mut resolved_task.resolved;
7829 resolved.reveal = reveal_strategy;
7830
7831 workspace
7832 .update_in(cx, |workspace, window, cx| {
7833 workspace.schedule_resolved_task(
7834 task_source_kind,
7835 resolved_task,
7836 false,
7837 window,
7838 cx,
7839 );
7840 })
7841 .ok()
7842 })
7843 .detach();
7844 }
7845
7846 fn find_closest_task(
7847 &mut self,
7848 cx: &mut Context<Self>,
7849 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7850 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7851
7852 let ((buffer_id, row), tasks) = self
7853 .tasks
7854 .iter()
7855 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7856
7857 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7858 let tasks = Arc::new(tasks.to_owned());
7859 Some((buffer, *row, tasks))
7860 }
7861
7862 fn find_enclosing_node_task(
7863 &mut self,
7864 cx: &mut Context<Self>,
7865 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7866 let snapshot = self.buffer.read(cx).snapshot(cx);
7867 let offset = self.selections.newest::<usize>(cx).head();
7868 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7869 let buffer_id = excerpt.buffer().remote_id();
7870
7871 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7872 let mut cursor = layer.node().walk();
7873
7874 while cursor.goto_first_child_for_byte(offset).is_some() {
7875 if cursor.node().end_byte() == offset {
7876 cursor.goto_next_sibling();
7877 }
7878 }
7879
7880 // Ascend to the smallest ancestor that contains the range and has a task.
7881 loop {
7882 let node = cursor.node();
7883 let node_range = node.byte_range();
7884 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7885
7886 // Check if this node contains our offset
7887 if node_range.start <= offset && node_range.end >= offset {
7888 // If it contains offset, check for task
7889 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7890 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7891 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7892 }
7893 }
7894
7895 if !cursor.goto_parent() {
7896 break;
7897 }
7898 }
7899 None
7900 }
7901
7902 fn render_run_indicator(
7903 &self,
7904 _style: &EditorStyle,
7905 is_active: bool,
7906 row: DisplayRow,
7907 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7908 cx: &mut Context<Self>,
7909 ) -> IconButton {
7910 let color = Color::Muted;
7911 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7912
7913 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7914 .shape(ui::IconButtonShape::Square)
7915 .icon_size(IconSize::XSmall)
7916 .icon_color(color)
7917 .toggle_state(is_active)
7918 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7919 let quick_launch = e.down.button == MouseButton::Left;
7920 window.focus(&editor.focus_handle(cx));
7921 editor.toggle_code_actions(
7922 &ToggleCodeActions {
7923 deployed_from: Some(CodeActionSource::Indicator(row)),
7924 quick_launch,
7925 },
7926 window,
7927 cx,
7928 );
7929 }))
7930 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7931 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7932 }))
7933 }
7934
7935 pub fn context_menu_visible(&self) -> bool {
7936 !self.edit_prediction_preview_is_active()
7937 && self
7938 .context_menu
7939 .borrow()
7940 .as_ref()
7941 .map_or(false, |menu| menu.visible())
7942 }
7943
7944 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7945 self.context_menu
7946 .borrow()
7947 .as_ref()
7948 .map(|menu| menu.origin())
7949 }
7950
7951 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7952 self.context_menu_options = Some(options);
7953 }
7954
7955 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7956 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7957
7958 fn render_edit_prediction_popover(
7959 &mut self,
7960 text_bounds: &Bounds<Pixels>,
7961 content_origin: gpui::Point<Pixels>,
7962 right_margin: Pixels,
7963 editor_snapshot: &EditorSnapshot,
7964 visible_row_range: Range<DisplayRow>,
7965 scroll_top: f32,
7966 scroll_bottom: f32,
7967 line_layouts: &[LineWithInvisibles],
7968 line_height: Pixels,
7969 scroll_pixel_position: gpui::Point<Pixels>,
7970 newest_selection_head: Option<DisplayPoint>,
7971 editor_width: Pixels,
7972 style: &EditorStyle,
7973 window: &mut Window,
7974 cx: &mut App,
7975 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7976 if self.mode().is_minimap() {
7977 return None;
7978 }
7979 let active_inline_completion = self.active_inline_completion.as_ref()?;
7980
7981 if self.edit_prediction_visible_in_cursor_popover(true) {
7982 return None;
7983 }
7984
7985 match &active_inline_completion.completion {
7986 InlineCompletion::Move { target, .. } => {
7987 let target_display_point = target.to_display_point(editor_snapshot);
7988
7989 if self.edit_prediction_requires_modifier() {
7990 if !self.edit_prediction_preview_is_active() {
7991 return None;
7992 }
7993
7994 self.render_edit_prediction_modifier_jump_popover(
7995 text_bounds,
7996 content_origin,
7997 visible_row_range,
7998 line_layouts,
7999 line_height,
8000 scroll_pixel_position,
8001 newest_selection_head,
8002 target_display_point,
8003 window,
8004 cx,
8005 )
8006 } else {
8007 self.render_edit_prediction_eager_jump_popover(
8008 text_bounds,
8009 content_origin,
8010 editor_snapshot,
8011 visible_row_range,
8012 scroll_top,
8013 scroll_bottom,
8014 line_height,
8015 scroll_pixel_position,
8016 target_display_point,
8017 editor_width,
8018 window,
8019 cx,
8020 )
8021 }
8022 }
8023 InlineCompletion::Edit {
8024 display_mode: EditDisplayMode::Inline,
8025 ..
8026 } => None,
8027 InlineCompletion::Edit {
8028 display_mode: EditDisplayMode::TabAccept,
8029 edits,
8030 ..
8031 } => {
8032 let range = &edits.first()?.0;
8033 let target_display_point = range.end.to_display_point(editor_snapshot);
8034
8035 self.render_edit_prediction_end_of_line_popover(
8036 "Accept",
8037 editor_snapshot,
8038 visible_row_range,
8039 target_display_point,
8040 line_height,
8041 scroll_pixel_position,
8042 content_origin,
8043 editor_width,
8044 window,
8045 cx,
8046 )
8047 }
8048 InlineCompletion::Edit {
8049 edits,
8050 edit_preview,
8051 display_mode: EditDisplayMode::DiffPopover,
8052 snapshot,
8053 } => self.render_edit_prediction_diff_popover(
8054 text_bounds,
8055 content_origin,
8056 right_margin,
8057 editor_snapshot,
8058 visible_row_range,
8059 line_layouts,
8060 line_height,
8061 scroll_pixel_position,
8062 newest_selection_head,
8063 editor_width,
8064 style,
8065 edits,
8066 edit_preview,
8067 snapshot,
8068 window,
8069 cx,
8070 ),
8071 }
8072 }
8073
8074 fn render_edit_prediction_modifier_jump_popover(
8075 &mut self,
8076 text_bounds: &Bounds<Pixels>,
8077 content_origin: gpui::Point<Pixels>,
8078 visible_row_range: Range<DisplayRow>,
8079 line_layouts: &[LineWithInvisibles],
8080 line_height: Pixels,
8081 scroll_pixel_position: gpui::Point<Pixels>,
8082 newest_selection_head: Option<DisplayPoint>,
8083 target_display_point: DisplayPoint,
8084 window: &mut Window,
8085 cx: &mut App,
8086 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8087 let scrolled_content_origin =
8088 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8089
8090 const SCROLL_PADDING_Y: Pixels = px(12.);
8091
8092 if target_display_point.row() < visible_row_range.start {
8093 return self.render_edit_prediction_scroll_popover(
8094 |_| SCROLL_PADDING_Y,
8095 IconName::ArrowUp,
8096 visible_row_range,
8097 line_layouts,
8098 newest_selection_head,
8099 scrolled_content_origin,
8100 window,
8101 cx,
8102 );
8103 } else if target_display_point.row() >= visible_row_range.end {
8104 return self.render_edit_prediction_scroll_popover(
8105 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8106 IconName::ArrowDown,
8107 visible_row_range,
8108 line_layouts,
8109 newest_selection_head,
8110 scrolled_content_origin,
8111 window,
8112 cx,
8113 );
8114 }
8115
8116 const POLE_WIDTH: Pixels = px(2.);
8117
8118 let line_layout =
8119 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8120 let target_column = target_display_point.column() as usize;
8121
8122 let target_x = line_layout.x_for_index(target_column);
8123 let target_y =
8124 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8125
8126 let flag_on_right = target_x < text_bounds.size.width / 2.;
8127
8128 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8129 border_color.l += 0.001;
8130
8131 let mut element = v_flex()
8132 .items_end()
8133 .when(flag_on_right, |el| el.items_start())
8134 .child(if flag_on_right {
8135 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8136 .rounded_bl(px(0.))
8137 .rounded_tl(px(0.))
8138 .border_l_2()
8139 .border_color(border_color)
8140 } else {
8141 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8142 .rounded_br(px(0.))
8143 .rounded_tr(px(0.))
8144 .border_r_2()
8145 .border_color(border_color)
8146 })
8147 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8148 .into_any();
8149
8150 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8151
8152 let mut origin = scrolled_content_origin + point(target_x, target_y)
8153 - point(
8154 if flag_on_right {
8155 POLE_WIDTH
8156 } else {
8157 size.width - POLE_WIDTH
8158 },
8159 size.height - line_height,
8160 );
8161
8162 origin.x = origin.x.max(content_origin.x);
8163
8164 element.prepaint_at(origin, window, cx);
8165
8166 Some((element, origin))
8167 }
8168
8169 fn render_edit_prediction_scroll_popover(
8170 &mut self,
8171 to_y: impl Fn(Size<Pixels>) -> Pixels,
8172 scroll_icon: IconName,
8173 visible_row_range: Range<DisplayRow>,
8174 line_layouts: &[LineWithInvisibles],
8175 newest_selection_head: Option<DisplayPoint>,
8176 scrolled_content_origin: gpui::Point<Pixels>,
8177 window: &mut Window,
8178 cx: &mut App,
8179 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8180 let mut element = self
8181 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8182 .into_any();
8183
8184 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8185
8186 let cursor = newest_selection_head?;
8187 let cursor_row_layout =
8188 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8189 let cursor_column = cursor.column() as usize;
8190
8191 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8192
8193 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8194
8195 element.prepaint_at(origin, window, cx);
8196 Some((element, origin))
8197 }
8198
8199 fn render_edit_prediction_eager_jump_popover(
8200 &mut self,
8201 text_bounds: &Bounds<Pixels>,
8202 content_origin: gpui::Point<Pixels>,
8203 editor_snapshot: &EditorSnapshot,
8204 visible_row_range: Range<DisplayRow>,
8205 scroll_top: f32,
8206 scroll_bottom: f32,
8207 line_height: Pixels,
8208 scroll_pixel_position: gpui::Point<Pixels>,
8209 target_display_point: DisplayPoint,
8210 editor_width: Pixels,
8211 window: &mut Window,
8212 cx: &mut App,
8213 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8214 if target_display_point.row().as_f32() < scroll_top {
8215 let mut element = self
8216 .render_edit_prediction_line_popover(
8217 "Jump to Edit",
8218 Some(IconName::ArrowUp),
8219 window,
8220 cx,
8221 )?
8222 .into_any();
8223
8224 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8225 let offset = point(
8226 (text_bounds.size.width - size.width) / 2.,
8227 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8228 );
8229
8230 let origin = text_bounds.origin + offset;
8231 element.prepaint_at(origin, window, cx);
8232 Some((element, origin))
8233 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8234 let mut element = self
8235 .render_edit_prediction_line_popover(
8236 "Jump to Edit",
8237 Some(IconName::ArrowDown),
8238 window,
8239 cx,
8240 )?
8241 .into_any();
8242
8243 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8244 let offset = point(
8245 (text_bounds.size.width - size.width) / 2.,
8246 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8247 );
8248
8249 let origin = text_bounds.origin + offset;
8250 element.prepaint_at(origin, window, cx);
8251 Some((element, origin))
8252 } else {
8253 self.render_edit_prediction_end_of_line_popover(
8254 "Jump to Edit",
8255 editor_snapshot,
8256 visible_row_range,
8257 target_display_point,
8258 line_height,
8259 scroll_pixel_position,
8260 content_origin,
8261 editor_width,
8262 window,
8263 cx,
8264 )
8265 }
8266 }
8267
8268 fn render_edit_prediction_end_of_line_popover(
8269 self: &mut Editor,
8270 label: &'static str,
8271 editor_snapshot: &EditorSnapshot,
8272 visible_row_range: Range<DisplayRow>,
8273 target_display_point: DisplayPoint,
8274 line_height: Pixels,
8275 scroll_pixel_position: gpui::Point<Pixels>,
8276 content_origin: gpui::Point<Pixels>,
8277 editor_width: Pixels,
8278 window: &mut Window,
8279 cx: &mut App,
8280 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8281 let target_line_end = DisplayPoint::new(
8282 target_display_point.row(),
8283 editor_snapshot.line_len(target_display_point.row()),
8284 );
8285
8286 let mut element = self
8287 .render_edit_prediction_line_popover(label, None, window, cx)?
8288 .into_any();
8289
8290 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8291
8292 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8293
8294 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8295 let mut origin = start_point
8296 + line_origin
8297 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8298 origin.x = origin.x.max(content_origin.x);
8299
8300 let max_x = content_origin.x + editor_width - size.width;
8301
8302 if origin.x > max_x {
8303 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8304
8305 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8306 origin.y += offset;
8307 IconName::ArrowUp
8308 } else {
8309 origin.y -= offset;
8310 IconName::ArrowDown
8311 };
8312
8313 element = self
8314 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8315 .into_any();
8316
8317 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8318
8319 origin.x = content_origin.x + editor_width - size.width - px(2.);
8320 }
8321
8322 element.prepaint_at(origin, window, cx);
8323 Some((element, origin))
8324 }
8325
8326 fn render_edit_prediction_diff_popover(
8327 self: &Editor,
8328 text_bounds: &Bounds<Pixels>,
8329 content_origin: gpui::Point<Pixels>,
8330 right_margin: Pixels,
8331 editor_snapshot: &EditorSnapshot,
8332 visible_row_range: Range<DisplayRow>,
8333 line_layouts: &[LineWithInvisibles],
8334 line_height: Pixels,
8335 scroll_pixel_position: gpui::Point<Pixels>,
8336 newest_selection_head: Option<DisplayPoint>,
8337 editor_width: Pixels,
8338 style: &EditorStyle,
8339 edits: &Vec<(Range<Anchor>, String)>,
8340 edit_preview: &Option<language::EditPreview>,
8341 snapshot: &language::BufferSnapshot,
8342 window: &mut Window,
8343 cx: &mut App,
8344 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8345 let edit_start = edits
8346 .first()
8347 .unwrap()
8348 .0
8349 .start
8350 .to_display_point(editor_snapshot);
8351 let edit_end = edits
8352 .last()
8353 .unwrap()
8354 .0
8355 .end
8356 .to_display_point(editor_snapshot);
8357
8358 let is_visible = visible_row_range.contains(&edit_start.row())
8359 || visible_row_range.contains(&edit_end.row());
8360 if !is_visible {
8361 return None;
8362 }
8363
8364 let highlighted_edits =
8365 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8366
8367 let styled_text = highlighted_edits.to_styled_text(&style.text);
8368 let line_count = highlighted_edits.text.lines().count();
8369
8370 const BORDER_WIDTH: Pixels = px(1.);
8371
8372 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8373 let has_keybind = keybind.is_some();
8374
8375 let mut element = h_flex()
8376 .items_start()
8377 .child(
8378 h_flex()
8379 .bg(cx.theme().colors().editor_background)
8380 .border(BORDER_WIDTH)
8381 .shadow_sm()
8382 .border_color(cx.theme().colors().border)
8383 .rounded_l_lg()
8384 .when(line_count > 1, |el| el.rounded_br_lg())
8385 .pr_1()
8386 .child(styled_text),
8387 )
8388 .child(
8389 h_flex()
8390 .h(line_height + BORDER_WIDTH * 2.)
8391 .px_1p5()
8392 .gap_1()
8393 // Workaround: For some reason, there's a gap if we don't do this
8394 .ml(-BORDER_WIDTH)
8395 .shadow(vec![gpui::BoxShadow {
8396 color: gpui::black().opacity(0.05),
8397 offset: point(px(1.), px(1.)),
8398 blur_radius: px(2.),
8399 spread_radius: px(0.),
8400 }])
8401 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8402 .border(BORDER_WIDTH)
8403 .border_color(cx.theme().colors().border)
8404 .rounded_r_lg()
8405 .id("edit_prediction_diff_popover_keybind")
8406 .when(!has_keybind, |el| {
8407 let status_colors = cx.theme().status();
8408
8409 el.bg(status_colors.error_background)
8410 .border_color(status_colors.error.opacity(0.6))
8411 .child(Icon::new(IconName::Info).color(Color::Error))
8412 .cursor_default()
8413 .hoverable_tooltip(move |_window, cx| {
8414 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8415 })
8416 })
8417 .children(keybind),
8418 )
8419 .into_any();
8420
8421 let longest_row =
8422 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8423 let longest_line_width = if visible_row_range.contains(&longest_row) {
8424 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8425 } else {
8426 layout_line(
8427 longest_row,
8428 editor_snapshot,
8429 style,
8430 editor_width,
8431 |_| false,
8432 window,
8433 cx,
8434 )
8435 .width
8436 };
8437
8438 let viewport_bounds =
8439 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8440 right: -right_margin,
8441 ..Default::default()
8442 });
8443
8444 let x_after_longest =
8445 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8446 - scroll_pixel_position.x;
8447
8448 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8449
8450 // Fully visible if it can be displayed within the window (allow overlapping other
8451 // panes). However, this is only allowed if the popover starts within text_bounds.
8452 let can_position_to_the_right = x_after_longest < text_bounds.right()
8453 && x_after_longest + element_bounds.width < viewport_bounds.right();
8454
8455 let mut origin = if can_position_to_the_right {
8456 point(
8457 x_after_longest,
8458 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8459 - scroll_pixel_position.y,
8460 )
8461 } else {
8462 let cursor_row = newest_selection_head.map(|head| head.row());
8463 let above_edit = edit_start
8464 .row()
8465 .0
8466 .checked_sub(line_count as u32)
8467 .map(DisplayRow);
8468 let below_edit = Some(edit_end.row() + 1);
8469 let above_cursor =
8470 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8471 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8472
8473 // Place the edit popover adjacent to the edit if there is a location
8474 // available that is onscreen and does not obscure the cursor. Otherwise,
8475 // place it adjacent to the cursor.
8476 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8477 .into_iter()
8478 .flatten()
8479 .find(|&start_row| {
8480 let end_row = start_row + line_count as u32;
8481 visible_row_range.contains(&start_row)
8482 && visible_row_range.contains(&end_row)
8483 && cursor_row.map_or(true, |cursor_row| {
8484 !((start_row..end_row).contains(&cursor_row))
8485 })
8486 })?;
8487
8488 content_origin
8489 + point(
8490 -scroll_pixel_position.x,
8491 row_target.as_f32() * line_height - scroll_pixel_position.y,
8492 )
8493 };
8494
8495 origin.x -= BORDER_WIDTH;
8496
8497 window.defer_draw(element, origin, 1);
8498
8499 // Do not return an element, since it will already be drawn due to defer_draw.
8500 None
8501 }
8502
8503 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8504 px(30.)
8505 }
8506
8507 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8508 if self.read_only(cx) {
8509 cx.theme().players().read_only()
8510 } else {
8511 self.style.as_ref().unwrap().local_player
8512 }
8513 }
8514
8515 fn render_edit_prediction_accept_keybind(
8516 &self,
8517 window: &mut Window,
8518 cx: &App,
8519 ) -> Option<AnyElement> {
8520 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8521 let accept_keystroke = accept_binding.keystroke()?;
8522
8523 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8524
8525 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8526 Color::Accent
8527 } else {
8528 Color::Muted
8529 };
8530
8531 h_flex()
8532 .px_0p5()
8533 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8534 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8535 .text_size(TextSize::XSmall.rems(cx))
8536 .child(h_flex().children(ui::render_modifiers(
8537 &accept_keystroke.modifiers,
8538 PlatformStyle::platform(),
8539 Some(modifiers_color),
8540 Some(IconSize::XSmall.rems().into()),
8541 true,
8542 )))
8543 .when(is_platform_style_mac, |parent| {
8544 parent.child(accept_keystroke.key.clone())
8545 })
8546 .when(!is_platform_style_mac, |parent| {
8547 parent.child(
8548 Key::new(
8549 util::capitalize(&accept_keystroke.key),
8550 Some(Color::Default),
8551 )
8552 .size(Some(IconSize::XSmall.rems().into())),
8553 )
8554 })
8555 .into_any()
8556 .into()
8557 }
8558
8559 fn render_edit_prediction_line_popover(
8560 &self,
8561 label: impl Into<SharedString>,
8562 icon: Option<IconName>,
8563 window: &mut Window,
8564 cx: &App,
8565 ) -> Option<Stateful<Div>> {
8566 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8567
8568 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8569 let has_keybind = keybind.is_some();
8570
8571 let result = h_flex()
8572 .id("ep-line-popover")
8573 .py_0p5()
8574 .pl_1()
8575 .pr(padding_right)
8576 .gap_1()
8577 .rounded_md()
8578 .border_1()
8579 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8580 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8581 .shadow_sm()
8582 .when(!has_keybind, |el| {
8583 let status_colors = cx.theme().status();
8584
8585 el.bg(status_colors.error_background)
8586 .border_color(status_colors.error.opacity(0.6))
8587 .pl_2()
8588 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8589 .cursor_default()
8590 .hoverable_tooltip(move |_window, cx| {
8591 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8592 })
8593 })
8594 .children(keybind)
8595 .child(
8596 Label::new(label)
8597 .size(LabelSize::Small)
8598 .when(!has_keybind, |el| {
8599 el.color(cx.theme().status().error.into()).strikethrough()
8600 }),
8601 )
8602 .when(!has_keybind, |el| {
8603 el.child(
8604 h_flex().ml_1().child(
8605 Icon::new(IconName::Info)
8606 .size(IconSize::Small)
8607 .color(cx.theme().status().error.into()),
8608 ),
8609 )
8610 })
8611 .when_some(icon, |element, icon| {
8612 element.child(
8613 div()
8614 .mt(px(1.5))
8615 .child(Icon::new(icon).size(IconSize::Small)),
8616 )
8617 });
8618
8619 Some(result)
8620 }
8621
8622 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8623 let accent_color = cx.theme().colors().text_accent;
8624 let editor_bg_color = cx.theme().colors().editor_background;
8625 editor_bg_color.blend(accent_color.opacity(0.1))
8626 }
8627
8628 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8629 let accent_color = cx.theme().colors().text_accent;
8630 let editor_bg_color = cx.theme().colors().editor_background;
8631 editor_bg_color.blend(accent_color.opacity(0.6))
8632 }
8633
8634 fn render_edit_prediction_cursor_popover(
8635 &self,
8636 min_width: Pixels,
8637 max_width: Pixels,
8638 cursor_point: Point,
8639 style: &EditorStyle,
8640 accept_keystroke: Option<&gpui::Keystroke>,
8641 _window: &Window,
8642 cx: &mut Context<Editor>,
8643 ) -> Option<AnyElement> {
8644 let provider = self.edit_prediction_provider.as_ref()?;
8645
8646 if provider.provider.needs_terms_acceptance(cx) {
8647 return Some(
8648 h_flex()
8649 .min_w(min_width)
8650 .flex_1()
8651 .px_2()
8652 .py_1()
8653 .gap_3()
8654 .elevation_2(cx)
8655 .hover(|style| style.bg(cx.theme().colors().element_hover))
8656 .id("accept-terms")
8657 .cursor_pointer()
8658 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8659 .on_click(cx.listener(|this, _event, window, cx| {
8660 cx.stop_propagation();
8661 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8662 window.dispatch_action(
8663 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8664 cx,
8665 );
8666 }))
8667 .child(
8668 h_flex()
8669 .flex_1()
8670 .gap_2()
8671 .child(Icon::new(IconName::ZedPredict))
8672 .child(Label::new("Accept Terms of Service"))
8673 .child(div().w_full())
8674 .child(
8675 Icon::new(IconName::ArrowUpRight)
8676 .color(Color::Muted)
8677 .size(IconSize::Small),
8678 )
8679 .into_any_element(),
8680 )
8681 .into_any(),
8682 );
8683 }
8684
8685 let is_refreshing = provider.provider.is_refreshing(cx);
8686
8687 fn pending_completion_container() -> Div {
8688 h_flex()
8689 .h_full()
8690 .flex_1()
8691 .gap_2()
8692 .child(Icon::new(IconName::ZedPredict))
8693 }
8694
8695 let completion = match &self.active_inline_completion {
8696 Some(prediction) => {
8697 if !self.has_visible_completions_menu() {
8698 const RADIUS: Pixels = px(6.);
8699 const BORDER_WIDTH: Pixels = px(1.);
8700
8701 return Some(
8702 h_flex()
8703 .elevation_2(cx)
8704 .border(BORDER_WIDTH)
8705 .border_color(cx.theme().colors().border)
8706 .when(accept_keystroke.is_none(), |el| {
8707 el.border_color(cx.theme().status().error)
8708 })
8709 .rounded(RADIUS)
8710 .rounded_tl(px(0.))
8711 .overflow_hidden()
8712 .child(div().px_1p5().child(match &prediction.completion {
8713 InlineCompletion::Move { target, snapshot } => {
8714 use text::ToPoint as _;
8715 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8716 {
8717 Icon::new(IconName::ZedPredictDown)
8718 } else {
8719 Icon::new(IconName::ZedPredictUp)
8720 }
8721 }
8722 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8723 }))
8724 .child(
8725 h_flex()
8726 .gap_1()
8727 .py_1()
8728 .px_2()
8729 .rounded_r(RADIUS - BORDER_WIDTH)
8730 .border_l_1()
8731 .border_color(cx.theme().colors().border)
8732 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8733 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8734 el.child(
8735 Label::new("Hold")
8736 .size(LabelSize::Small)
8737 .when(accept_keystroke.is_none(), |el| {
8738 el.strikethrough()
8739 })
8740 .line_height_style(LineHeightStyle::UiLabel),
8741 )
8742 })
8743 .id("edit_prediction_cursor_popover_keybind")
8744 .when(accept_keystroke.is_none(), |el| {
8745 let status_colors = cx.theme().status();
8746
8747 el.bg(status_colors.error_background)
8748 .border_color(status_colors.error.opacity(0.6))
8749 .child(Icon::new(IconName::Info).color(Color::Error))
8750 .cursor_default()
8751 .hoverable_tooltip(move |_window, cx| {
8752 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8753 .into()
8754 })
8755 })
8756 .when_some(
8757 accept_keystroke.as_ref(),
8758 |el, accept_keystroke| {
8759 el.child(h_flex().children(ui::render_modifiers(
8760 &accept_keystroke.modifiers,
8761 PlatformStyle::platform(),
8762 Some(Color::Default),
8763 Some(IconSize::XSmall.rems().into()),
8764 false,
8765 )))
8766 },
8767 ),
8768 )
8769 .into_any(),
8770 );
8771 }
8772
8773 self.render_edit_prediction_cursor_popover_preview(
8774 prediction,
8775 cursor_point,
8776 style,
8777 cx,
8778 )?
8779 }
8780
8781 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8782 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8783 stale_completion,
8784 cursor_point,
8785 style,
8786 cx,
8787 )?,
8788
8789 None => {
8790 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8791 }
8792 },
8793
8794 None => pending_completion_container().child(Label::new("No Prediction")),
8795 };
8796
8797 let completion = if is_refreshing {
8798 completion
8799 .with_animation(
8800 "loading-completion",
8801 Animation::new(Duration::from_secs(2))
8802 .repeat()
8803 .with_easing(pulsating_between(0.4, 0.8)),
8804 |label, delta| label.opacity(delta),
8805 )
8806 .into_any_element()
8807 } else {
8808 completion.into_any_element()
8809 };
8810
8811 let has_completion = self.active_inline_completion.is_some();
8812
8813 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8814 Some(
8815 h_flex()
8816 .min_w(min_width)
8817 .max_w(max_width)
8818 .flex_1()
8819 .elevation_2(cx)
8820 .border_color(cx.theme().colors().border)
8821 .child(
8822 div()
8823 .flex_1()
8824 .py_1()
8825 .px_2()
8826 .overflow_hidden()
8827 .child(completion),
8828 )
8829 .when_some(accept_keystroke, |el, accept_keystroke| {
8830 if !accept_keystroke.modifiers.modified() {
8831 return el;
8832 }
8833
8834 el.child(
8835 h_flex()
8836 .h_full()
8837 .border_l_1()
8838 .rounded_r_lg()
8839 .border_color(cx.theme().colors().border)
8840 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8841 .gap_1()
8842 .py_1()
8843 .px_2()
8844 .child(
8845 h_flex()
8846 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8847 .when(is_platform_style_mac, |parent| parent.gap_1())
8848 .child(h_flex().children(ui::render_modifiers(
8849 &accept_keystroke.modifiers,
8850 PlatformStyle::platform(),
8851 Some(if !has_completion {
8852 Color::Muted
8853 } else {
8854 Color::Default
8855 }),
8856 None,
8857 false,
8858 ))),
8859 )
8860 .child(Label::new("Preview").into_any_element())
8861 .opacity(if has_completion { 1.0 } else { 0.4 }),
8862 )
8863 })
8864 .into_any(),
8865 )
8866 }
8867
8868 fn render_edit_prediction_cursor_popover_preview(
8869 &self,
8870 completion: &InlineCompletionState,
8871 cursor_point: Point,
8872 style: &EditorStyle,
8873 cx: &mut Context<Editor>,
8874 ) -> Option<Div> {
8875 use text::ToPoint as _;
8876
8877 fn render_relative_row_jump(
8878 prefix: impl Into<String>,
8879 current_row: u32,
8880 target_row: u32,
8881 ) -> Div {
8882 let (row_diff, arrow) = if target_row < current_row {
8883 (current_row - target_row, IconName::ArrowUp)
8884 } else {
8885 (target_row - current_row, IconName::ArrowDown)
8886 };
8887
8888 h_flex()
8889 .child(
8890 Label::new(format!("{}{}", prefix.into(), row_diff))
8891 .color(Color::Muted)
8892 .size(LabelSize::Small),
8893 )
8894 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8895 }
8896
8897 match &completion.completion {
8898 InlineCompletion::Move {
8899 target, snapshot, ..
8900 } => Some(
8901 h_flex()
8902 .px_2()
8903 .gap_2()
8904 .flex_1()
8905 .child(
8906 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8907 Icon::new(IconName::ZedPredictDown)
8908 } else {
8909 Icon::new(IconName::ZedPredictUp)
8910 },
8911 )
8912 .child(Label::new("Jump to Edit")),
8913 ),
8914
8915 InlineCompletion::Edit {
8916 edits,
8917 edit_preview,
8918 snapshot,
8919 display_mode: _,
8920 } => {
8921 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8922
8923 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8924 &snapshot,
8925 &edits,
8926 edit_preview.as_ref()?,
8927 true,
8928 cx,
8929 )
8930 .first_line_preview();
8931
8932 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8933 .with_default_highlights(&style.text, highlighted_edits.highlights);
8934
8935 let preview = h_flex()
8936 .gap_1()
8937 .min_w_16()
8938 .child(styled_text)
8939 .when(has_more_lines, |parent| parent.child("…"));
8940
8941 let left = if first_edit_row != cursor_point.row {
8942 render_relative_row_jump("", cursor_point.row, first_edit_row)
8943 .into_any_element()
8944 } else {
8945 Icon::new(IconName::ZedPredict).into_any_element()
8946 };
8947
8948 Some(
8949 h_flex()
8950 .h_full()
8951 .flex_1()
8952 .gap_2()
8953 .pr_1()
8954 .overflow_x_hidden()
8955 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8956 .child(left)
8957 .child(preview),
8958 )
8959 }
8960 }
8961 }
8962
8963 pub fn render_context_menu(
8964 &self,
8965 style: &EditorStyle,
8966 max_height_in_lines: u32,
8967 window: &mut Window,
8968 cx: &mut Context<Editor>,
8969 ) -> Option<AnyElement> {
8970 let menu = self.context_menu.borrow();
8971 let menu = menu.as_ref()?;
8972 if !menu.visible() {
8973 return None;
8974 };
8975 Some(menu.render(style, max_height_in_lines, window, cx))
8976 }
8977
8978 fn render_context_menu_aside(
8979 &mut self,
8980 max_size: Size<Pixels>,
8981 window: &mut Window,
8982 cx: &mut Context<Editor>,
8983 ) -> Option<AnyElement> {
8984 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8985 if menu.visible() {
8986 menu.render_aside(max_size, window, cx)
8987 } else {
8988 None
8989 }
8990 })
8991 }
8992
8993 fn hide_context_menu(
8994 &mut self,
8995 window: &mut Window,
8996 cx: &mut Context<Self>,
8997 ) -> Option<CodeContextMenu> {
8998 cx.notify();
8999 self.completion_tasks.clear();
9000 let context_menu = self.context_menu.borrow_mut().take();
9001 self.stale_inline_completion_in_menu.take();
9002 self.update_visible_inline_completion(window, cx);
9003 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9004 if let Some(completion_provider) = &self.completion_provider {
9005 completion_provider.selection_changed(None, window, cx);
9006 }
9007 }
9008 context_menu
9009 }
9010
9011 fn show_snippet_choices(
9012 &mut self,
9013 choices: &Vec<String>,
9014 selection: Range<Anchor>,
9015 cx: &mut Context<Self>,
9016 ) {
9017 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9018 (Some(a), Some(b)) if a == b => a,
9019 _ => {
9020 log::error!("expected anchor range to have matching buffer IDs");
9021 return;
9022 }
9023 };
9024 let multi_buffer = self.buffer().read(cx);
9025 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9026 return;
9027 };
9028
9029 let id = post_inc(&mut self.next_completion_id);
9030 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9031 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9032 CompletionsMenu::new_snippet_choices(
9033 id,
9034 true,
9035 choices,
9036 selection,
9037 buffer,
9038 snippet_sort_order,
9039 ),
9040 ));
9041 }
9042
9043 pub fn insert_snippet(
9044 &mut self,
9045 insertion_ranges: &[Range<usize>],
9046 snippet: Snippet,
9047 window: &mut Window,
9048 cx: &mut Context<Self>,
9049 ) -> Result<()> {
9050 struct Tabstop<T> {
9051 is_end_tabstop: bool,
9052 ranges: Vec<Range<T>>,
9053 choices: Option<Vec<String>>,
9054 }
9055
9056 let tabstops = self.buffer.update(cx, |buffer, cx| {
9057 let snippet_text: Arc<str> = snippet.text.clone().into();
9058 let edits = insertion_ranges
9059 .iter()
9060 .cloned()
9061 .map(|range| (range, snippet_text.clone()));
9062 let autoindent_mode = AutoindentMode::Block {
9063 original_indent_columns: Vec::new(),
9064 };
9065 buffer.edit(edits, Some(autoindent_mode), cx);
9066
9067 let snapshot = &*buffer.read(cx);
9068 let snippet = &snippet;
9069 snippet
9070 .tabstops
9071 .iter()
9072 .map(|tabstop| {
9073 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9074 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9075 });
9076 let mut tabstop_ranges = tabstop
9077 .ranges
9078 .iter()
9079 .flat_map(|tabstop_range| {
9080 let mut delta = 0_isize;
9081 insertion_ranges.iter().map(move |insertion_range| {
9082 let insertion_start = insertion_range.start as isize + delta;
9083 delta +=
9084 snippet.text.len() as isize - insertion_range.len() as isize;
9085
9086 let start = ((insertion_start + tabstop_range.start) as usize)
9087 .min(snapshot.len());
9088 let end = ((insertion_start + tabstop_range.end) as usize)
9089 .min(snapshot.len());
9090 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9091 })
9092 })
9093 .collect::<Vec<_>>();
9094 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9095
9096 Tabstop {
9097 is_end_tabstop,
9098 ranges: tabstop_ranges,
9099 choices: tabstop.choices.clone(),
9100 }
9101 })
9102 .collect::<Vec<_>>()
9103 });
9104 if let Some(tabstop) = tabstops.first() {
9105 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9106 // Reverse order so that the first range is the newest created selection.
9107 // Completions will use it and autoscroll will prioritize it.
9108 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9109 });
9110
9111 if let Some(choices) = &tabstop.choices {
9112 if let Some(selection) = tabstop.ranges.first() {
9113 self.show_snippet_choices(choices, selection.clone(), cx)
9114 }
9115 }
9116
9117 // If we're already at the last tabstop and it's at the end of the snippet,
9118 // we're done, we don't need to keep the state around.
9119 if !tabstop.is_end_tabstop {
9120 let choices = tabstops
9121 .iter()
9122 .map(|tabstop| tabstop.choices.clone())
9123 .collect();
9124
9125 let ranges = tabstops
9126 .into_iter()
9127 .map(|tabstop| tabstop.ranges)
9128 .collect::<Vec<_>>();
9129
9130 self.snippet_stack.push(SnippetState {
9131 active_index: 0,
9132 ranges,
9133 choices,
9134 });
9135 }
9136
9137 // Check whether the just-entered snippet ends with an auto-closable bracket.
9138 if self.autoclose_regions.is_empty() {
9139 let snapshot = self.buffer.read(cx).snapshot(cx);
9140 for selection in &mut self.selections.all::<Point>(cx) {
9141 let selection_head = selection.head();
9142 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9143 continue;
9144 };
9145
9146 let mut bracket_pair = None;
9147 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9148 let prev_chars = snapshot
9149 .reversed_chars_at(selection_head)
9150 .collect::<String>();
9151 for (pair, enabled) in scope.brackets() {
9152 if enabled
9153 && pair.close
9154 && prev_chars.starts_with(pair.start.as_str())
9155 && next_chars.starts_with(pair.end.as_str())
9156 {
9157 bracket_pair = Some(pair.clone());
9158 break;
9159 }
9160 }
9161 if let Some(pair) = bracket_pair {
9162 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9163 let autoclose_enabled =
9164 self.use_autoclose && snapshot_settings.use_autoclose;
9165 if autoclose_enabled {
9166 let start = snapshot.anchor_after(selection_head);
9167 let end = snapshot.anchor_after(selection_head);
9168 self.autoclose_regions.push(AutocloseRegion {
9169 selection_id: selection.id,
9170 range: start..end,
9171 pair,
9172 });
9173 }
9174 }
9175 }
9176 }
9177 }
9178 Ok(())
9179 }
9180
9181 pub fn move_to_next_snippet_tabstop(
9182 &mut self,
9183 window: &mut Window,
9184 cx: &mut Context<Self>,
9185 ) -> bool {
9186 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9187 }
9188
9189 pub fn move_to_prev_snippet_tabstop(
9190 &mut self,
9191 window: &mut Window,
9192 cx: &mut Context<Self>,
9193 ) -> bool {
9194 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9195 }
9196
9197 pub fn move_to_snippet_tabstop(
9198 &mut self,
9199 bias: Bias,
9200 window: &mut Window,
9201 cx: &mut Context<Self>,
9202 ) -> bool {
9203 if let Some(mut snippet) = self.snippet_stack.pop() {
9204 match bias {
9205 Bias::Left => {
9206 if snippet.active_index > 0 {
9207 snippet.active_index -= 1;
9208 } else {
9209 self.snippet_stack.push(snippet);
9210 return false;
9211 }
9212 }
9213 Bias::Right => {
9214 if snippet.active_index + 1 < snippet.ranges.len() {
9215 snippet.active_index += 1;
9216 } else {
9217 self.snippet_stack.push(snippet);
9218 return false;
9219 }
9220 }
9221 }
9222 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9223 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9224 // Reverse order so that the first range is the newest created selection.
9225 // Completions will use it and autoscroll will prioritize it.
9226 s.select_ranges(current_ranges.iter().rev().cloned())
9227 });
9228
9229 if let Some(choices) = &snippet.choices[snippet.active_index] {
9230 if let Some(selection) = current_ranges.first() {
9231 self.show_snippet_choices(&choices, selection.clone(), cx);
9232 }
9233 }
9234
9235 // If snippet state is not at the last tabstop, push it back on the stack
9236 if snippet.active_index + 1 < snippet.ranges.len() {
9237 self.snippet_stack.push(snippet);
9238 }
9239 return true;
9240 }
9241 }
9242
9243 false
9244 }
9245
9246 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9247 self.transact(window, cx, |this, window, cx| {
9248 this.select_all(&SelectAll, window, cx);
9249 this.insert("", window, cx);
9250 });
9251 }
9252
9253 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9254 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9255 self.transact(window, cx, |this, window, cx| {
9256 this.select_autoclose_pair(window, cx);
9257 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9258 if !this.linked_edit_ranges.is_empty() {
9259 let selections = this.selections.all::<MultiBufferPoint>(cx);
9260 let snapshot = this.buffer.read(cx).snapshot(cx);
9261
9262 for selection in selections.iter() {
9263 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9264 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9265 if selection_start.buffer_id != selection_end.buffer_id {
9266 continue;
9267 }
9268 if let Some(ranges) =
9269 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9270 {
9271 for (buffer, entries) in ranges {
9272 linked_ranges.entry(buffer).or_default().extend(entries);
9273 }
9274 }
9275 }
9276 }
9277
9278 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9279 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9280 for selection in &mut selections {
9281 if selection.is_empty() {
9282 let old_head = selection.head();
9283 let mut new_head =
9284 movement::left(&display_map, old_head.to_display_point(&display_map))
9285 .to_point(&display_map);
9286 if let Some((buffer, line_buffer_range)) = display_map
9287 .buffer_snapshot
9288 .buffer_line_for_row(MultiBufferRow(old_head.row))
9289 {
9290 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9291 let indent_len = match indent_size.kind {
9292 IndentKind::Space => {
9293 buffer.settings_at(line_buffer_range.start, cx).tab_size
9294 }
9295 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9296 };
9297 if old_head.column <= indent_size.len && old_head.column > 0 {
9298 let indent_len = indent_len.get();
9299 new_head = cmp::min(
9300 new_head,
9301 MultiBufferPoint::new(
9302 old_head.row,
9303 ((old_head.column - 1) / indent_len) * indent_len,
9304 ),
9305 );
9306 }
9307 }
9308
9309 selection.set_head(new_head, SelectionGoal::None);
9310 }
9311 }
9312
9313 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9314 s.select(selections)
9315 });
9316 this.insert("", window, cx);
9317 let empty_str: Arc<str> = Arc::from("");
9318 for (buffer, edits) in linked_ranges {
9319 let snapshot = buffer.read(cx).snapshot();
9320 use text::ToPoint as TP;
9321
9322 let edits = edits
9323 .into_iter()
9324 .map(|range| {
9325 let end_point = TP::to_point(&range.end, &snapshot);
9326 let mut start_point = TP::to_point(&range.start, &snapshot);
9327
9328 if end_point == start_point {
9329 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9330 .saturating_sub(1);
9331 start_point =
9332 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9333 };
9334
9335 (start_point..end_point, empty_str.clone())
9336 })
9337 .sorted_by_key(|(range, _)| range.start)
9338 .collect::<Vec<_>>();
9339 buffer.update(cx, |this, cx| {
9340 this.edit(edits, None, cx);
9341 })
9342 }
9343 this.refresh_inline_completion(true, false, window, cx);
9344 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9345 });
9346 }
9347
9348 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9349 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9350 self.transact(window, cx, |this, window, cx| {
9351 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9352 s.move_with(|map, selection| {
9353 if selection.is_empty() {
9354 let cursor = movement::right(map, selection.head());
9355 selection.end = cursor;
9356 selection.reversed = true;
9357 selection.goal = SelectionGoal::None;
9358 }
9359 })
9360 });
9361 this.insert("", window, cx);
9362 this.refresh_inline_completion(true, false, window, cx);
9363 });
9364 }
9365
9366 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9367 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9368 if self.move_to_prev_snippet_tabstop(window, cx) {
9369 return;
9370 }
9371 self.outdent(&Outdent, window, cx);
9372 }
9373
9374 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9375 if self.move_to_next_snippet_tabstop(window, cx) {
9376 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9377 return;
9378 }
9379 if self.read_only(cx) {
9380 return;
9381 }
9382 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9383 let mut selections = self.selections.all_adjusted(cx);
9384 let buffer = self.buffer.read(cx);
9385 let snapshot = buffer.snapshot(cx);
9386 let rows_iter = selections.iter().map(|s| s.head().row);
9387 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9388
9389 let has_some_cursor_in_whitespace = selections
9390 .iter()
9391 .filter(|selection| selection.is_empty())
9392 .any(|selection| {
9393 let cursor = selection.head();
9394 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9395 cursor.column < current_indent.len
9396 });
9397
9398 let mut edits = Vec::new();
9399 let mut prev_edited_row = 0;
9400 let mut row_delta = 0;
9401 for selection in &mut selections {
9402 if selection.start.row != prev_edited_row {
9403 row_delta = 0;
9404 }
9405 prev_edited_row = selection.end.row;
9406
9407 // If the selection is non-empty, then increase the indentation of the selected lines.
9408 if !selection.is_empty() {
9409 row_delta =
9410 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9411 continue;
9412 }
9413
9414 let cursor = selection.head();
9415 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9416 if let Some(suggested_indent) =
9417 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9418 {
9419 // Don't do anything if already at suggested indent
9420 // and there is any other cursor which is not
9421 if has_some_cursor_in_whitespace
9422 && cursor.column == current_indent.len
9423 && current_indent.len == suggested_indent.len
9424 {
9425 continue;
9426 }
9427
9428 // Adjust line and move cursor to suggested indent
9429 // if cursor is not at suggested indent
9430 if cursor.column < suggested_indent.len
9431 && cursor.column <= current_indent.len
9432 && current_indent.len <= suggested_indent.len
9433 {
9434 selection.start = Point::new(cursor.row, suggested_indent.len);
9435 selection.end = selection.start;
9436 if row_delta == 0 {
9437 edits.extend(Buffer::edit_for_indent_size_adjustment(
9438 cursor.row,
9439 current_indent,
9440 suggested_indent,
9441 ));
9442 row_delta = suggested_indent.len - current_indent.len;
9443 }
9444 continue;
9445 }
9446
9447 // If current indent is more than suggested indent
9448 // only move cursor to current indent and skip indent
9449 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9450 selection.start = Point::new(cursor.row, current_indent.len);
9451 selection.end = selection.start;
9452 continue;
9453 }
9454 }
9455
9456 // Otherwise, insert a hard or soft tab.
9457 let settings = buffer.language_settings_at(cursor, cx);
9458 let tab_size = if settings.hard_tabs {
9459 IndentSize::tab()
9460 } else {
9461 let tab_size = settings.tab_size.get();
9462 let indent_remainder = snapshot
9463 .text_for_range(Point::new(cursor.row, 0)..cursor)
9464 .flat_map(str::chars)
9465 .fold(row_delta % tab_size, |counter: u32, c| {
9466 if c == '\t' {
9467 0
9468 } else {
9469 (counter + 1) % tab_size
9470 }
9471 });
9472
9473 let chars_to_next_tab_stop = tab_size - indent_remainder;
9474 IndentSize::spaces(chars_to_next_tab_stop)
9475 };
9476 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9477 selection.end = selection.start;
9478 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9479 row_delta += tab_size.len;
9480 }
9481
9482 self.transact(window, cx, |this, window, cx| {
9483 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9484 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9485 s.select(selections)
9486 });
9487 this.refresh_inline_completion(true, false, window, cx);
9488 });
9489 }
9490
9491 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9492 if self.read_only(cx) {
9493 return;
9494 }
9495 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9496 let mut selections = self.selections.all::<Point>(cx);
9497 let mut prev_edited_row = 0;
9498 let mut row_delta = 0;
9499 let mut edits = Vec::new();
9500 let buffer = self.buffer.read(cx);
9501 let snapshot = buffer.snapshot(cx);
9502 for selection in &mut selections {
9503 if selection.start.row != prev_edited_row {
9504 row_delta = 0;
9505 }
9506 prev_edited_row = selection.end.row;
9507
9508 row_delta =
9509 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9510 }
9511
9512 self.transact(window, cx, |this, window, cx| {
9513 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9514 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9515 s.select(selections)
9516 });
9517 });
9518 }
9519
9520 fn indent_selection(
9521 buffer: &MultiBuffer,
9522 snapshot: &MultiBufferSnapshot,
9523 selection: &mut Selection<Point>,
9524 edits: &mut Vec<(Range<Point>, String)>,
9525 delta_for_start_row: u32,
9526 cx: &App,
9527 ) -> u32 {
9528 let settings = buffer.language_settings_at(selection.start, cx);
9529 let tab_size = settings.tab_size.get();
9530 let indent_kind = if settings.hard_tabs {
9531 IndentKind::Tab
9532 } else {
9533 IndentKind::Space
9534 };
9535 let mut start_row = selection.start.row;
9536 let mut end_row = selection.end.row + 1;
9537
9538 // If a selection ends at the beginning of a line, don't indent
9539 // that last line.
9540 if selection.end.column == 0 && selection.end.row > selection.start.row {
9541 end_row -= 1;
9542 }
9543
9544 // Avoid re-indenting a row that has already been indented by a
9545 // previous selection, but still update this selection's column
9546 // to reflect that indentation.
9547 if delta_for_start_row > 0 {
9548 start_row += 1;
9549 selection.start.column += delta_for_start_row;
9550 if selection.end.row == selection.start.row {
9551 selection.end.column += delta_for_start_row;
9552 }
9553 }
9554
9555 let mut delta_for_end_row = 0;
9556 let has_multiple_rows = start_row + 1 != end_row;
9557 for row in start_row..end_row {
9558 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9559 let indent_delta = match (current_indent.kind, indent_kind) {
9560 (IndentKind::Space, IndentKind::Space) => {
9561 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9562 IndentSize::spaces(columns_to_next_tab_stop)
9563 }
9564 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9565 (_, IndentKind::Tab) => IndentSize::tab(),
9566 };
9567
9568 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9569 0
9570 } else {
9571 selection.start.column
9572 };
9573 let row_start = Point::new(row, start);
9574 edits.push((
9575 row_start..row_start,
9576 indent_delta.chars().collect::<String>(),
9577 ));
9578
9579 // Update this selection's endpoints to reflect the indentation.
9580 if row == selection.start.row {
9581 selection.start.column += indent_delta.len;
9582 }
9583 if row == selection.end.row {
9584 selection.end.column += indent_delta.len;
9585 delta_for_end_row = indent_delta.len;
9586 }
9587 }
9588
9589 if selection.start.row == selection.end.row {
9590 delta_for_start_row + delta_for_end_row
9591 } else {
9592 delta_for_end_row
9593 }
9594 }
9595
9596 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9597 if self.read_only(cx) {
9598 return;
9599 }
9600 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9601 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9602 let selections = self.selections.all::<Point>(cx);
9603 let mut deletion_ranges = Vec::new();
9604 let mut last_outdent = None;
9605 {
9606 let buffer = self.buffer.read(cx);
9607 let snapshot = buffer.snapshot(cx);
9608 for selection in &selections {
9609 let settings = buffer.language_settings_at(selection.start, cx);
9610 let tab_size = settings.tab_size.get();
9611 let mut rows = selection.spanned_rows(false, &display_map);
9612
9613 // Avoid re-outdenting a row that has already been outdented by a
9614 // previous selection.
9615 if let Some(last_row) = last_outdent {
9616 if last_row == rows.start {
9617 rows.start = rows.start.next_row();
9618 }
9619 }
9620 let has_multiple_rows = rows.len() > 1;
9621 for row in rows.iter_rows() {
9622 let indent_size = snapshot.indent_size_for_line(row);
9623 if indent_size.len > 0 {
9624 let deletion_len = match indent_size.kind {
9625 IndentKind::Space => {
9626 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9627 if columns_to_prev_tab_stop == 0 {
9628 tab_size
9629 } else {
9630 columns_to_prev_tab_stop
9631 }
9632 }
9633 IndentKind::Tab => 1,
9634 };
9635 let start = if has_multiple_rows
9636 || deletion_len > selection.start.column
9637 || indent_size.len < selection.start.column
9638 {
9639 0
9640 } else {
9641 selection.start.column - deletion_len
9642 };
9643 deletion_ranges.push(
9644 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9645 );
9646 last_outdent = Some(row);
9647 }
9648 }
9649 }
9650 }
9651
9652 self.transact(window, cx, |this, window, cx| {
9653 this.buffer.update(cx, |buffer, cx| {
9654 let empty_str: Arc<str> = Arc::default();
9655 buffer.edit(
9656 deletion_ranges
9657 .into_iter()
9658 .map(|range| (range, empty_str.clone())),
9659 None,
9660 cx,
9661 );
9662 });
9663 let selections = this.selections.all::<usize>(cx);
9664 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9665 s.select(selections)
9666 });
9667 });
9668 }
9669
9670 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9671 if self.read_only(cx) {
9672 return;
9673 }
9674 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9675 let selections = self
9676 .selections
9677 .all::<usize>(cx)
9678 .into_iter()
9679 .map(|s| s.range());
9680
9681 self.transact(window, cx, |this, window, cx| {
9682 this.buffer.update(cx, |buffer, cx| {
9683 buffer.autoindent_ranges(selections, cx);
9684 });
9685 let selections = this.selections.all::<usize>(cx);
9686 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9687 s.select(selections)
9688 });
9689 });
9690 }
9691
9692 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9693 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9694 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9695 let selections = self.selections.all::<Point>(cx);
9696
9697 let mut new_cursors = Vec::new();
9698 let mut edit_ranges = Vec::new();
9699 let mut selections = selections.iter().peekable();
9700 while let Some(selection) = selections.next() {
9701 let mut rows = selection.spanned_rows(false, &display_map);
9702 let goal_display_column = selection.head().to_display_point(&display_map).column();
9703
9704 // Accumulate contiguous regions of rows that we want to delete.
9705 while let Some(next_selection) = selections.peek() {
9706 let next_rows = next_selection.spanned_rows(false, &display_map);
9707 if next_rows.start <= rows.end {
9708 rows.end = next_rows.end;
9709 selections.next().unwrap();
9710 } else {
9711 break;
9712 }
9713 }
9714
9715 let buffer = &display_map.buffer_snapshot;
9716 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9717 let edit_end;
9718 let cursor_buffer_row;
9719 if buffer.max_point().row >= rows.end.0 {
9720 // If there's a line after the range, delete the \n from the end of the row range
9721 // and position the cursor on the next line.
9722 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9723 cursor_buffer_row = rows.end;
9724 } else {
9725 // If there isn't a line after the range, delete the \n from the line before the
9726 // start of the row range and position the cursor there.
9727 edit_start = edit_start.saturating_sub(1);
9728 edit_end = buffer.len();
9729 cursor_buffer_row = rows.start.previous_row();
9730 }
9731
9732 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9733 *cursor.column_mut() =
9734 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9735
9736 new_cursors.push((
9737 selection.id,
9738 buffer.anchor_after(cursor.to_point(&display_map)),
9739 ));
9740 edit_ranges.push(edit_start..edit_end);
9741 }
9742
9743 self.transact(window, cx, |this, window, cx| {
9744 let buffer = this.buffer.update(cx, |buffer, cx| {
9745 let empty_str: Arc<str> = Arc::default();
9746 buffer.edit(
9747 edit_ranges
9748 .into_iter()
9749 .map(|range| (range, empty_str.clone())),
9750 None,
9751 cx,
9752 );
9753 buffer.snapshot(cx)
9754 });
9755 let new_selections = new_cursors
9756 .into_iter()
9757 .map(|(id, cursor)| {
9758 let cursor = cursor.to_point(&buffer);
9759 Selection {
9760 id,
9761 start: cursor,
9762 end: cursor,
9763 reversed: false,
9764 goal: SelectionGoal::None,
9765 }
9766 })
9767 .collect();
9768
9769 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9770 s.select(new_selections);
9771 });
9772 });
9773 }
9774
9775 pub fn join_lines_impl(
9776 &mut self,
9777 insert_whitespace: bool,
9778 window: &mut Window,
9779 cx: &mut Context<Self>,
9780 ) {
9781 if self.read_only(cx) {
9782 return;
9783 }
9784 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9785 for selection in self.selections.all::<Point>(cx) {
9786 let start = MultiBufferRow(selection.start.row);
9787 // Treat single line selections as if they include the next line. Otherwise this action
9788 // would do nothing for single line selections individual cursors.
9789 let end = if selection.start.row == selection.end.row {
9790 MultiBufferRow(selection.start.row + 1)
9791 } else {
9792 MultiBufferRow(selection.end.row)
9793 };
9794
9795 if let Some(last_row_range) = row_ranges.last_mut() {
9796 if start <= last_row_range.end {
9797 last_row_range.end = end;
9798 continue;
9799 }
9800 }
9801 row_ranges.push(start..end);
9802 }
9803
9804 let snapshot = self.buffer.read(cx).snapshot(cx);
9805 let mut cursor_positions = Vec::new();
9806 for row_range in &row_ranges {
9807 let anchor = snapshot.anchor_before(Point::new(
9808 row_range.end.previous_row().0,
9809 snapshot.line_len(row_range.end.previous_row()),
9810 ));
9811 cursor_positions.push(anchor..anchor);
9812 }
9813
9814 self.transact(window, cx, |this, window, cx| {
9815 for row_range in row_ranges.into_iter().rev() {
9816 for row in row_range.iter_rows().rev() {
9817 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9818 let next_line_row = row.next_row();
9819 let indent = snapshot.indent_size_for_line(next_line_row);
9820 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9821
9822 let replace =
9823 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9824 " "
9825 } else {
9826 ""
9827 };
9828
9829 this.buffer.update(cx, |buffer, cx| {
9830 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9831 });
9832 }
9833 }
9834
9835 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9836 s.select_anchor_ranges(cursor_positions)
9837 });
9838 });
9839 }
9840
9841 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9842 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9843 self.join_lines_impl(true, window, cx);
9844 }
9845
9846 pub fn sort_lines_case_sensitive(
9847 &mut self,
9848 _: &SortLinesCaseSensitive,
9849 window: &mut Window,
9850 cx: &mut Context<Self>,
9851 ) {
9852 self.manipulate_lines(window, cx, |lines| lines.sort())
9853 }
9854
9855 pub fn sort_lines_case_insensitive(
9856 &mut self,
9857 _: &SortLinesCaseInsensitive,
9858 window: &mut Window,
9859 cx: &mut Context<Self>,
9860 ) {
9861 self.manipulate_lines(window, cx, |lines| {
9862 lines.sort_by_key(|line| line.to_lowercase())
9863 })
9864 }
9865
9866 pub fn unique_lines_case_insensitive(
9867 &mut self,
9868 _: &UniqueLinesCaseInsensitive,
9869 window: &mut Window,
9870 cx: &mut Context<Self>,
9871 ) {
9872 self.manipulate_lines(window, cx, |lines| {
9873 let mut seen = HashSet::default();
9874 lines.retain(|line| seen.insert(line.to_lowercase()));
9875 })
9876 }
9877
9878 pub fn unique_lines_case_sensitive(
9879 &mut self,
9880 _: &UniqueLinesCaseSensitive,
9881 window: &mut Window,
9882 cx: &mut Context<Self>,
9883 ) {
9884 self.manipulate_lines(window, cx, |lines| {
9885 let mut seen = HashSet::default();
9886 lines.retain(|line| seen.insert(*line));
9887 })
9888 }
9889
9890 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9891 let Some(project) = self.project.clone() else {
9892 return;
9893 };
9894 self.reload(project, window, cx)
9895 .detach_and_notify_err(window, cx);
9896 }
9897
9898 pub fn restore_file(
9899 &mut self,
9900 _: &::git::RestoreFile,
9901 window: &mut Window,
9902 cx: &mut Context<Self>,
9903 ) {
9904 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9905 let mut buffer_ids = HashSet::default();
9906 let snapshot = self.buffer().read(cx).snapshot(cx);
9907 for selection in self.selections.all::<usize>(cx) {
9908 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9909 }
9910
9911 let buffer = self.buffer().read(cx);
9912 let ranges = buffer_ids
9913 .into_iter()
9914 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9915 .collect::<Vec<_>>();
9916
9917 self.restore_hunks_in_ranges(ranges, window, cx);
9918 }
9919
9920 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9921 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9922 let selections = self
9923 .selections
9924 .all(cx)
9925 .into_iter()
9926 .map(|s| s.range())
9927 .collect();
9928 self.restore_hunks_in_ranges(selections, window, cx);
9929 }
9930
9931 pub fn restore_hunks_in_ranges(
9932 &mut self,
9933 ranges: Vec<Range<Point>>,
9934 window: &mut Window,
9935 cx: &mut Context<Editor>,
9936 ) {
9937 let mut revert_changes = HashMap::default();
9938 let chunk_by = self
9939 .snapshot(window, cx)
9940 .hunks_for_ranges(ranges)
9941 .into_iter()
9942 .chunk_by(|hunk| hunk.buffer_id);
9943 for (buffer_id, hunks) in &chunk_by {
9944 let hunks = hunks.collect::<Vec<_>>();
9945 for hunk in &hunks {
9946 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9947 }
9948 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9949 }
9950 drop(chunk_by);
9951 if !revert_changes.is_empty() {
9952 self.transact(window, cx, |editor, window, cx| {
9953 editor.restore(revert_changes, window, cx);
9954 });
9955 }
9956 }
9957
9958 pub fn open_active_item_in_terminal(
9959 &mut self,
9960 _: &OpenInTerminal,
9961 window: &mut Window,
9962 cx: &mut Context<Self>,
9963 ) {
9964 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9965 let project_path = buffer.read(cx).project_path(cx)?;
9966 let project = self.project.as_ref()?.read(cx);
9967 let entry = project.entry_for_path(&project_path, cx)?;
9968 let parent = match &entry.canonical_path {
9969 Some(canonical_path) => canonical_path.to_path_buf(),
9970 None => project.absolute_path(&project_path, cx)?,
9971 }
9972 .parent()?
9973 .to_path_buf();
9974 Some(parent)
9975 }) {
9976 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9977 }
9978 }
9979
9980 fn set_breakpoint_context_menu(
9981 &mut self,
9982 display_row: DisplayRow,
9983 position: Option<Anchor>,
9984 clicked_point: gpui::Point<Pixels>,
9985 window: &mut Window,
9986 cx: &mut Context<Self>,
9987 ) {
9988 if !cx.has_flag::<DebuggerFeatureFlag>() {
9989 return;
9990 }
9991 let source = self
9992 .buffer
9993 .read(cx)
9994 .snapshot(cx)
9995 .anchor_before(Point::new(display_row.0, 0u32));
9996
9997 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9998
9999 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10000 self,
10001 source,
10002 clicked_point,
10003 context_menu,
10004 window,
10005 cx,
10006 );
10007 }
10008
10009 fn add_edit_breakpoint_block(
10010 &mut self,
10011 anchor: Anchor,
10012 breakpoint: &Breakpoint,
10013 edit_action: BreakpointPromptEditAction,
10014 window: &mut Window,
10015 cx: &mut Context<Self>,
10016 ) {
10017 let weak_editor = cx.weak_entity();
10018 let bp_prompt = cx.new(|cx| {
10019 BreakpointPromptEditor::new(
10020 weak_editor,
10021 anchor,
10022 breakpoint.clone(),
10023 edit_action,
10024 window,
10025 cx,
10026 )
10027 });
10028
10029 let height = bp_prompt.update(cx, |this, cx| {
10030 this.prompt
10031 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10032 });
10033 let cloned_prompt = bp_prompt.clone();
10034 let blocks = vec![BlockProperties {
10035 style: BlockStyle::Sticky,
10036 placement: BlockPlacement::Above(anchor),
10037 height: Some(height),
10038 render: Arc::new(move |cx| {
10039 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10040 cloned_prompt.clone().into_any_element()
10041 }),
10042 priority: 0,
10043 render_in_minimap: true,
10044 }];
10045
10046 let focus_handle = bp_prompt.focus_handle(cx);
10047 window.focus(&focus_handle);
10048
10049 let block_ids = self.insert_blocks(blocks, None, cx);
10050 bp_prompt.update(cx, |prompt, _| {
10051 prompt.add_block_ids(block_ids);
10052 });
10053 }
10054
10055 pub(crate) fn breakpoint_at_row(
10056 &self,
10057 row: u32,
10058 window: &mut Window,
10059 cx: &mut Context<Self>,
10060 ) -> Option<(Anchor, Breakpoint)> {
10061 let snapshot = self.snapshot(window, cx);
10062 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10063
10064 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10065 }
10066
10067 pub(crate) fn breakpoint_at_anchor(
10068 &self,
10069 breakpoint_position: Anchor,
10070 snapshot: &EditorSnapshot,
10071 cx: &mut Context<Self>,
10072 ) -> Option<(Anchor, Breakpoint)> {
10073 let project = self.project.clone()?;
10074
10075 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10076 snapshot
10077 .buffer_snapshot
10078 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10079 })?;
10080
10081 let enclosing_excerpt = breakpoint_position.excerpt_id;
10082 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10083 let buffer_snapshot = buffer.read(cx).snapshot();
10084
10085 let row = buffer_snapshot
10086 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10087 .row;
10088
10089 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10090 let anchor_end = snapshot
10091 .buffer_snapshot
10092 .anchor_after(Point::new(row, line_len));
10093
10094 let bp = self
10095 .breakpoint_store
10096 .as_ref()?
10097 .read_with(cx, |breakpoint_store, cx| {
10098 breakpoint_store
10099 .breakpoints(
10100 &buffer,
10101 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10102 &buffer_snapshot,
10103 cx,
10104 )
10105 .next()
10106 .and_then(|(bp, _)| {
10107 let breakpoint_row = buffer_snapshot
10108 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10109 .row;
10110
10111 if breakpoint_row == row {
10112 snapshot
10113 .buffer_snapshot
10114 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10115 .map(|position| (position, bp.bp.clone()))
10116 } else {
10117 None
10118 }
10119 })
10120 });
10121 bp
10122 }
10123
10124 pub fn edit_log_breakpoint(
10125 &mut self,
10126 _: &EditLogBreakpoint,
10127 window: &mut Window,
10128 cx: &mut Context<Self>,
10129 ) {
10130 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10131 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10132 message: None,
10133 state: BreakpointState::Enabled,
10134 condition: None,
10135 hit_condition: None,
10136 });
10137
10138 self.add_edit_breakpoint_block(
10139 anchor,
10140 &breakpoint,
10141 BreakpointPromptEditAction::Log,
10142 window,
10143 cx,
10144 );
10145 }
10146 }
10147
10148 fn breakpoints_at_cursors(
10149 &self,
10150 window: &mut Window,
10151 cx: &mut Context<Self>,
10152 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10153 let snapshot = self.snapshot(window, cx);
10154 let cursors = self
10155 .selections
10156 .disjoint_anchors()
10157 .into_iter()
10158 .map(|selection| {
10159 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10160
10161 let breakpoint_position = self
10162 .breakpoint_at_row(cursor_position.row, window, cx)
10163 .map(|bp| bp.0)
10164 .unwrap_or_else(|| {
10165 snapshot
10166 .display_snapshot
10167 .buffer_snapshot
10168 .anchor_after(Point::new(cursor_position.row, 0))
10169 });
10170
10171 let breakpoint = self
10172 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10173 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10174
10175 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10176 })
10177 // 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.
10178 .collect::<HashMap<Anchor, _>>();
10179
10180 cursors.into_iter().collect()
10181 }
10182
10183 pub fn enable_breakpoint(
10184 &mut self,
10185 _: &crate::actions::EnableBreakpoint,
10186 window: &mut Window,
10187 cx: &mut Context<Self>,
10188 ) {
10189 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10190 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10191 continue;
10192 };
10193 self.edit_breakpoint_at_anchor(
10194 anchor,
10195 breakpoint,
10196 BreakpointEditAction::InvertState,
10197 cx,
10198 );
10199 }
10200 }
10201
10202 pub fn disable_breakpoint(
10203 &mut self,
10204 _: &crate::actions::DisableBreakpoint,
10205 window: &mut Window,
10206 cx: &mut Context<Self>,
10207 ) {
10208 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10209 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10210 continue;
10211 };
10212 self.edit_breakpoint_at_anchor(
10213 anchor,
10214 breakpoint,
10215 BreakpointEditAction::InvertState,
10216 cx,
10217 );
10218 }
10219 }
10220
10221 pub fn toggle_breakpoint(
10222 &mut self,
10223 _: &crate::actions::ToggleBreakpoint,
10224 window: &mut Window,
10225 cx: &mut Context<Self>,
10226 ) {
10227 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10228 if let Some(breakpoint) = breakpoint {
10229 self.edit_breakpoint_at_anchor(
10230 anchor,
10231 breakpoint,
10232 BreakpointEditAction::Toggle,
10233 cx,
10234 );
10235 } else {
10236 self.edit_breakpoint_at_anchor(
10237 anchor,
10238 Breakpoint::new_standard(),
10239 BreakpointEditAction::Toggle,
10240 cx,
10241 );
10242 }
10243 }
10244 }
10245
10246 pub fn edit_breakpoint_at_anchor(
10247 &mut self,
10248 breakpoint_position: Anchor,
10249 breakpoint: Breakpoint,
10250 edit_action: BreakpointEditAction,
10251 cx: &mut Context<Self>,
10252 ) {
10253 let Some(breakpoint_store) = &self.breakpoint_store else {
10254 return;
10255 };
10256
10257 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10258 if breakpoint_position == Anchor::min() {
10259 self.buffer()
10260 .read(cx)
10261 .excerpt_buffer_ids()
10262 .into_iter()
10263 .next()
10264 } else {
10265 None
10266 }
10267 }) else {
10268 return;
10269 };
10270
10271 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10272 return;
10273 };
10274
10275 breakpoint_store.update(cx, |breakpoint_store, cx| {
10276 breakpoint_store.toggle_breakpoint(
10277 buffer,
10278 BreakpointWithPosition {
10279 position: breakpoint_position.text_anchor,
10280 bp: breakpoint,
10281 },
10282 edit_action,
10283 cx,
10284 );
10285 });
10286
10287 cx.notify();
10288 }
10289
10290 #[cfg(any(test, feature = "test-support"))]
10291 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10292 self.breakpoint_store.clone()
10293 }
10294
10295 pub fn prepare_restore_change(
10296 &self,
10297 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10298 hunk: &MultiBufferDiffHunk,
10299 cx: &mut App,
10300 ) -> Option<()> {
10301 if hunk.is_created_file() {
10302 return None;
10303 }
10304 let buffer = self.buffer.read(cx);
10305 let diff = buffer.diff_for(hunk.buffer_id)?;
10306 let buffer = buffer.buffer(hunk.buffer_id)?;
10307 let buffer = buffer.read(cx);
10308 let original_text = diff
10309 .read(cx)
10310 .base_text()
10311 .as_rope()
10312 .slice(hunk.diff_base_byte_range.clone());
10313 let buffer_snapshot = buffer.snapshot();
10314 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10315 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10316 probe
10317 .0
10318 .start
10319 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10320 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10321 }) {
10322 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10323 Some(())
10324 } else {
10325 None
10326 }
10327 }
10328
10329 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10330 self.manipulate_lines(window, cx, |lines| lines.reverse())
10331 }
10332
10333 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10334 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10335 }
10336
10337 fn manipulate_lines<Fn>(
10338 &mut self,
10339 window: &mut Window,
10340 cx: &mut Context<Self>,
10341 mut callback: Fn,
10342 ) where
10343 Fn: FnMut(&mut Vec<&str>),
10344 {
10345 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10346
10347 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10348 let buffer = self.buffer.read(cx).snapshot(cx);
10349
10350 let mut edits = Vec::new();
10351
10352 let selections = self.selections.all::<Point>(cx);
10353 let mut selections = selections.iter().peekable();
10354 let mut contiguous_row_selections = Vec::new();
10355 let mut new_selections = Vec::new();
10356 let mut added_lines = 0;
10357 let mut removed_lines = 0;
10358
10359 while let Some(selection) = selections.next() {
10360 let (start_row, end_row) = consume_contiguous_rows(
10361 &mut contiguous_row_selections,
10362 selection,
10363 &display_map,
10364 &mut selections,
10365 );
10366
10367 let start_point = Point::new(start_row.0, 0);
10368 let end_point = Point::new(
10369 end_row.previous_row().0,
10370 buffer.line_len(end_row.previous_row()),
10371 );
10372 let text = buffer
10373 .text_for_range(start_point..end_point)
10374 .collect::<String>();
10375
10376 let mut lines = text.split('\n').collect_vec();
10377
10378 let lines_before = lines.len();
10379 callback(&mut lines);
10380 let lines_after = lines.len();
10381
10382 edits.push((start_point..end_point, lines.join("\n")));
10383
10384 // Selections must change based on added and removed line count
10385 let start_row =
10386 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10387 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10388 new_selections.push(Selection {
10389 id: selection.id,
10390 start: start_row,
10391 end: end_row,
10392 goal: SelectionGoal::None,
10393 reversed: selection.reversed,
10394 });
10395
10396 if lines_after > lines_before {
10397 added_lines += lines_after - lines_before;
10398 } else if lines_before > lines_after {
10399 removed_lines += lines_before - lines_after;
10400 }
10401 }
10402
10403 self.transact(window, cx, |this, window, cx| {
10404 let buffer = this.buffer.update(cx, |buffer, cx| {
10405 buffer.edit(edits, None, cx);
10406 buffer.snapshot(cx)
10407 });
10408
10409 // Recalculate offsets on newly edited buffer
10410 let new_selections = new_selections
10411 .iter()
10412 .map(|s| {
10413 let start_point = Point::new(s.start.0, 0);
10414 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10415 Selection {
10416 id: s.id,
10417 start: buffer.point_to_offset(start_point),
10418 end: buffer.point_to_offset(end_point),
10419 goal: s.goal,
10420 reversed: s.reversed,
10421 }
10422 })
10423 .collect();
10424
10425 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10426 s.select(new_selections);
10427 });
10428
10429 this.request_autoscroll(Autoscroll::fit(), cx);
10430 });
10431 }
10432
10433 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10434 self.manipulate_text(window, cx, |text| {
10435 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10436 if has_upper_case_characters {
10437 text.to_lowercase()
10438 } else {
10439 text.to_uppercase()
10440 }
10441 })
10442 }
10443
10444 pub fn convert_to_upper_case(
10445 &mut self,
10446 _: &ConvertToUpperCase,
10447 window: &mut Window,
10448 cx: &mut Context<Self>,
10449 ) {
10450 self.manipulate_text(window, cx, |text| text.to_uppercase())
10451 }
10452
10453 pub fn convert_to_lower_case(
10454 &mut self,
10455 _: &ConvertToLowerCase,
10456 window: &mut Window,
10457 cx: &mut Context<Self>,
10458 ) {
10459 self.manipulate_text(window, cx, |text| text.to_lowercase())
10460 }
10461
10462 pub fn convert_to_title_case(
10463 &mut self,
10464 _: &ConvertToTitleCase,
10465 window: &mut Window,
10466 cx: &mut Context<Self>,
10467 ) {
10468 self.manipulate_text(window, cx, |text| {
10469 text.split('\n')
10470 .map(|line| line.to_case(Case::Title))
10471 .join("\n")
10472 })
10473 }
10474
10475 pub fn convert_to_snake_case(
10476 &mut self,
10477 _: &ConvertToSnakeCase,
10478 window: &mut Window,
10479 cx: &mut Context<Self>,
10480 ) {
10481 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10482 }
10483
10484 pub fn convert_to_kebab_case(
10485 &mut self,
10486 _: &ConvertToKebabCase,
10487 window: &mut Window,
10488 cx: &mut Context<Self>,
10489 ) {
10490 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10491 }
10492
10493 pub fn convert_to_upper_camel_case(
10494 &mut self,
10495 _: &ConvertToUpperCamelCase,
10496 window: &mut Window,
10497 cx: &mut Context<Self>,
10498 ) {
10499 self.manipulate_text(window, cx, |text| {
10500 text.split('\n')
10501 .map(|line| line.to_case(Case::UpperCamel))
10502 .join("\n")
10503 })
10504 }
10505
10506 pub fn convert_to_lower_camel_case(
10507 &mut self,
10508 _: &ConvertToLowerCamelCase,
10509 window: &mut Window,
10510 cx: &mut Context<Self>,
10511 ) {
10512 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10513 }
10514
10515 pub fn convert_to_opposite_case(
10516 &mut self,
10517 _: &ConvertToOppositeCase,
10518 window: &mut Window,
10519 cx: &mut Context<Self>,
10520 ) {
10521 self.manipulate_text(window, cx, |text| {
10522 text.chars()
10523 .fold(String::with_capacity(text.len()), |mut t, c| {
10524 if c.is_uppercase() {
10525 t.extend(c.to_lowercase());
10526 } else {
10527 t.extend(c.to_uppercase());
10528 }
10529 t
10530 })
10531 })
10532 }
10533
10534 pub fn convert_to_rot13(
10535 &mut self,
10536 _: &ConvertToRot13,
10537 window: &mut Window,
10538 cx: &mut Context<Self>,
10539 ) {
10540 self.manipulate_text(window, cx, |text| {
10541 text.chars()
10542 .map(|c| match c {
10543 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10544 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10545 _ => c,
10546 })
10547 .collect()
10548 })
10549 }
10550
10551 pub fn convert_to_rot47(
10552 &mut self,
10553 _: &ConvertToRot47,
10554 window: &mut Window,
10555 cx: &mut Context<Self>,
10556 ) {
10557 self.manipulate_text(window, cx, |text| {
10558 text.chars()
10559 .map(|c| {
10560 let code_point = c as u32;
10561 if code_point >= 33 && code_point <= 126 {
10562 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10563 }
10564 c
10565 })
10566 .collect()
10567 })
10568 }
10569
10570 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10571 where
10572 Fn: FnMut(&str) -> String,
10573 {
10574 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10575 let buffer = self.buffer.read(cx).snapshot(cx);
10576
10577 let mut new_selections = Vec::new();
10578 let mut edits = Vec::new();
10579 let mut selection_adjustment = 0i32;
10580
10581 for selection in self.selections.all::<usize>(cx) {
10582 let selection_is_empty = selection.is_empty();
10583
10584 let (start, end) = if selection_is_empty {
10585 let word_range = movement::surrounding_word(
10586 &display_map,
10587 selection.start.to_display_point(&display_map),
10588 );
10589 let start = word_range.start.to_offset(&display_map, Bias::Left);
10590 let end = word_range.end.to_offset(&display_map, Bias::Left);
10591 (start, end)
10592 } else {
10593 (selection.start, selection.end)
10594 };
10595
10596 let text = buffer.text_for_range(start..end).collect::<String>();
10597 let old_length = text.len() as i32;
10598 let text = callback(&text);
10599
10600 new_selections.push(Selection {
10601 start: (start as i32 - selection_adjustment) as usize,
10602 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10603 goal: SelectionGoal::None,
10604 ..selection
10605 });
10606
10607 selection_adjustment += old_length - text.len() as i32;
10608
10609 edits.push((start..end, text));
10610 }
10611
10612 self.transact(window, cx, |this, window, cx| {
10613 this.buffer.update(cx, |buffer, cx| {
10614 buffer.edit(edits, None, cx);
10615 });
10616
10617 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10618 s.select(new_selections);
10619 });
10620
10621 this.request_autoscroll(Autoscroll::fit(), cx);
10622 });
10623 }
10624
10625 pub fn move_selection_on_drop(
10626 &mut self,
10627 selection: &Selection<Anchor>,
10628 target: DisplayPoint,
10629 is_cut: bool,
10630 window: &mut Window,
10631 cx: &mut Context<Self>,
10632 ) {
10633 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10634 let buffer = &display_map.buffer_snapshot;
10635 let mut edits = Vec::new();
10636 let insert_point = display_map
10637 .clip_point(target, Bias::Left)
10638 .to_point(&display_map);
10639 let text = buffer
10640 .text_for_range(selection.start..selection.end)
10641 .collect::<String>();
10642 if is_cut {
10643 edits.push(((selection.start..selection.end), String::new()));
10644 }
10645 let insert_anchor = buffer.anchor_before(insert_point);
10646 edits.push(((insert_anchor..insert_anchor), text));
10647 let last_edit_start = insert_anchor.bias_left(buffer);
10648 let last_edit_end = insert_anchor.bias_right(buffer);
10649 self.transact(window, cx, |this, window, cx| {
10650 this.buffer.update(cx, |buffer, cx| {
10651 buffer.edit(edits, None, cx);
10652 });
10653 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10654 s.select_anchor_ranges([last_edit_start..last_edit_end]);
10655 });
10656 });
10657 }
10658
10659 pub fn clear_selection_drag_state(&mut self) {
10660 self.selection_drag_state = SelectionDragState::None;
10661 }
10662
10663 pub fn duplicate(
10664 &mut self,
10665 upwards: bool,
10666 whole_lines: bool,
10667 window: &mut Window,
10668 cx: &mut Context<Self>,
10669 ) {
10670 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10671
10672 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10673 let buffer = &display_map.buffer_snapshot;
10674 let selections = self.selections.all::<Point>(cx);
10675
10676 let mut edits = Vec::new();
10677 let mut selections_iter = selections.iter().peekable();
10678 while let Some(selection) = selections_iter.next() {
10679 let mut rows = selection.spanned_rows(false, &display_map);
10680 // duplicate line-wise
10681 if whole_lines || selection.start == selection.end {
10682 // Avoid duplicating the same lines twice.
10683 while let Some(next_selection) = selections_iter.peek() {
10684 let next_rows = next_selection.spanned_rows(false, &display_map);
10685 if next_rows.start < rows.end {
10686 rows.end = next_rows.end;
10687 selections_iter.next().unwrap();
10688 } else {
10689 break;
10690 }
10691 }
10692
10693 // Copy the text from the selected row region and splice it either at the start
10694 // or end of the region.
10695 let start = Point::new(rows.start.0, 0);
10696 let end = Point::new(
10697 rows.end.previous_row().0,
10698 buffer.line_len(rows.end.previous_row()),
10699 );
10700 let text = buffer
10701 .text_for_range(start..end)
10702 .chain(Some("\n"))
10703 .collect::<String>();
10704 let insert_location = if upwards {
10705 Point::new(rows.end.0, 0)
10706 } else {
10707 start
10708 };
10709 edits.push((insert_location..insert_location, text));
10710 } else {
10711 // duplicate character-wise
10712 let start = selection.start;
10713 let end = selection.end;
10714 let text = buffer.text_for_range(start..end).collect::<String>();
10715 edits.push((selection.end..selection.end, text));
10716 }
10717 }
10718
10719 self.transact(window, cx, |this, _, cx| {
10720 this.buffer.update(cx, |buffer, cx| {
10721 buffer.edit(edits, None, cx);
10722 });
10723
10724 this.request_autoscroll(Autoscroll::fit(), cx);
10725 });
10726 }
10727
10728 pub fn duplicate_line_up(
10729 &mut self,
10730 _: &DuplicateLineUp,
10731 window: &mut Window,
10732 cx: &mut Context<Self>,
10733 ) {
10734 self.duplicate(true, true, window, cx);
10735 }
10736
10737 pub fn duplicate_line_down(
10738 &mut self,
10739 _: &DuplicateLineDown,
10740 window: &mut Window,
10741 cx: &mut Context<Self>,
10742 ) {
10743 self.duplicate(false, true, window, cx);
10744 }
10745
10746 pub fn duplicate_selection(
10747 &mut self,
10748 _: &DuplicateSelection,
10749 window: &mut Window,
10750 cx: &mut Context<Self>,
10751 ) {
10752 self.duplicate(false, false, window, cx);
10753 }
10754
10755 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10756 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10757
10758 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10759 let buffer = self.buffer.read(cx).snapshot(cx);
10760
10761 let mut edits = Vec::new();
10762 let mut unfold_ranges = Vec::new();
10763 let mut refold_creases = Vec::new();
10764
10765 let selections = self.selections.all::<Point>(cx);
10766 let mut selections = selections.iter().peekable();
10767 let mut contiguous_row_selections = Vec::new();
10768 let mut new_selections = Vec::new();
10769
10770 while let Some(selection) = selections.next() {
10771 // Find all the selections that span a contiguous row range
10772 let (start_row, end_row) = consume_contiguous_rows(
10773 &mut contiguous_row_selections,
10774 selection,
10775 &display_map,
10776 &mut selections,
10777 );
10778
10779 // Move the text spanned by the row range to be before the line preceding the row range
10780 if start_row.0 > 0 {
10781 let range_to_move = Point::new(
10782 start_row.previous_row().0,
10783 buffer.line_len(start_row.previous_row()),
10784 )
10785 ..Point::new(
10786 end_row.previous_row().0,
10787 buffer.line_len(end_row.previous_row()),
10788 );
10789 let insertion_point = display_map
10790 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10791 .0;
10792
10793 // Don't move lines across excerpts
10794 if buffer
10795 .excerpt_containing(insertion_point..range_to_move.end)
10796 .is_some()
10797 {
10798 let text = buffer
10799 .text_for_range(range_to_move.clone())
10800 .flat_map(|s| s.chars())
10801 .skip(1)
10802 .chain(['\n'])
10803 .collect::<String>();
10804
10805 edits.push((
10806 buffer.anchor_after(range_to_move.start)
10807 ..buffer.anchor_before(range_to_move.end),
10808 String::new(),
10809 ));
10810 let insertion_anchor = buffer.anchor_after(insertion_point);
10811 edits.push((insertion_anchor..insertion_anchor, text));
10812
10813 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10814
10815 // Move selections up
10816 new_selections.extend(contiguous_row_selections.drain(..).map(
10817 |mut selection| {
10818 selection.start.row -= row_delta;
10819 selection.end.row -= row_delta;
10820 selection
10821 },
10822 ));
10823
10824 // Move folds up
10825 unfold_ranges.push(range_to_move.clone());
10826 for fold in display_map.folds_in_range(
10827 buffer.anchor_before(range_to_move.start)
10828 ..buffer.anchor_after(range_to_move.end),
10829 ) {
10830 let mut start = fold.range.start.to_point(&buffer);
10831 let mut end = fold.range.end.to_point(&buffer);
10832 start.row -= row_delta;
10833 end.row -= row_delta;
10834 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10835 }
10836 }
10837 }
10838
10839 // If we didn't move line(s), preserve the existing selections
10840 new_selections.append(&mut contiguous_row_selections);
10841 }
10842
10843 self.transact(window, cx, |this, window, cx| {
10844 this.unfold_ranges(&unfold_ranges, true, true, cx);
10845 this.buffer.update(cx, |buffer, cx| {
10846 for (range, text) in edits {
10847 buffer.edit([(range, text)], None, cx);
10848 }
10849 });
10850 this.fold_creases(refold_creases, true, window, cx);
10851 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10852 s.select(new_selections);
10853 })
10854 });
10855 }
10856
10857 pub fn move_line_down(
10858 &mut self,
10859 _: &MoveLineDown,
10860 window: &mut Window,
10861 cx: &mut Context<Self>,
10862 ) {
10863 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10864
10865 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10866 let buffer = self.buffer.read(cx).snapshot(cx);
10867
10868 let mut edits = Vec::new();
10869 let mut unfold_ranges = Vec::new();
10870 let mut refold_creases = Vec::new();
10871
10872 let selections = self.selections.all::<Point>(cx);
10873 let mut selections = selections.iter().peekable();
10874 let mut contiguous_row_selections = Vec::new();
10875 let mut new_selections = Vec::new();
10876
10877 while let Some(selection) = selections.next() {
10878 // Find all the selections that span a contiguous row range
10879 let (start_row, end_row) = consume_contiguous_rows(
10880 &mut contiguous_row_selections,
10881 selection,
10882 &display_map,
10883 &mut selections,
10884 );
10885
10886 // Move the text spanned by the row range to be after the last line of the row range
10887 if end_row.0 <= buffer.max_point().row {
10888 let range_to_move =
10889 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10890 let insertion_point = display_map
10891 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10892 .0;
10893
10894 // Don't move lines across excerpt boundaries
10895 if buffer
10896 .excerpt_containing(range_to_move.start..insertion_point)
10897 .is_some()
10898 {
10899 let mut text = String::from("\n");
10900 text.extend(buffer.text_for_range(range_to_move.clone()));
10901 text.pop(); // Drop trailing newline
10902 edits.push((
10903 buffer.anchor_after(range_to_move.start)
10904 ..buffer.anchor_before(range_to_move.end),
10905 String::new(),
10906 ));
10907 let insertion_anchor = buffer.anchor_after(insertion_point);
10908 edits.push((insertion_anchor..insertion_anchor, text));
10909
10910 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10911
10912 // Move selections down
10913 new_selections.extend(contiguous_row_selections.drain(..).map(
10914 |mut selection| {
10915 selection.start.row += row_delta;
10916 selection.end.row += row_delta;
10917 selection
10918 },
10919 ));
10920
10921 // Move folds down
10922 unfold_ranges.push(range_to_move.clone());
10923 for fold in display_map.folds_in_range(
10924 buffer.anchor_before(range_to_move.start)
10925 ..buffer.anchor_after(range_to_move.end),
10926 ) {
10927 let mut start = fold.range.start.to_point(&buffer);
10928 let mut end = fold.range.end.to_point(&buffer);
10929 start.row += row_delta;
10930 end.row += row_delta;
10931 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10932 }
10933 }
10934 }
10935
10936 // If we didn't move line(s), preserve the existing selections
10937 new_selections.append(&mut contiguous_row_selections);
10938 }
10939
10940 self.transact(window, cx, |this, window, cx| {
10941 this.unfold_ranges(&unfold_ranges, true, true, cx);
10942 this.buffer.update(cx, |buffer, cx| {
10943 for (range, text) in edits {
10944 buffer.edit([(range, text)], None, cx);
10945 }
10946 });
10947 this.fold_creases(refold_creases, true, window, cx);
10948 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10949 s.select(new_selections)
10950 });
10951 });
10952 }
10953
10954 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10955 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10956 let text_layout_details = &self.text_layout_details(window);
10957 self.transact(window, cx, |this, window, cx| {
10958 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10959 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10960 s.move_with(|display_map, selection| {
10961 if !selection.is_empty() {
10962 return;
10963 }
10964
10965 let mut head = selection.head();
10966 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10967 if head.column() == display_map.line_len(head.row()) {
10968 transpose_offset = display_map
10969 .buffer_snapshot
10970 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10971 }
10972
10973 if transpose_offset == 0 {
10974 return;
10975 }
10976
10977 *head.column_mut() += 1;
10978 head = display_map.clip_point(head, Bias::Right);
10979 let goal = SelectionGoal::HorizontalPosition(
10980 display_map
10981 .x_for_display_point(head, text_layout_details)
10982 .into(),
10983 );
10984 selection.collapse_to(head, goal);
10985
10986 let transpose_start = display_map
10987 .buffer_snapshot
10988 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10989 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10990 let transpose_end = display_map
10991 .buffer_snapshot
10992 .clip_offset(transpose_offset + 1, Bias::Right);
10993 if let Some(ch) =
10994 display_map.buffer_snapshot.chars_at(transpose_start).next()
10995 {
10996 edits.push((transpose_start..transpose_offset, String::new()));
10997 edits.push((transpose_end..transpose_end, ch.to_string()));
10998 }
10999 }
11000 });
11001 edits
11002 });
11003 this.buffer
11004 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11005 let selections = this.selections.all::<usize>(cx);
11006 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11007 s.select(selections);
11008 });
11009 });
11010 }
11011
11012 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11013 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11014 self.rewrap_impl(RewrapOptions::default(), cx)
11015 }
11016
11017 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11018 let buffer = self.buffer.read(cx).snapshot(cx);
11019 let selections = self.selections.all::<Point>(cx);
11020
11021 // Shrink and split selections to respect paragraph boundaries.
11022 let ranges = selections.into_iter().flat_map(|selection| {
11023 let language_settings = buffer.language_settings_at(selection.head(), cx);
11024 let language_scope = buffer.language_scope_at(selection.head());
11025
11026 let Some(start_row) = (selection.start.row..=selection.end.row)
11027 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11028 else {
11029 return vec![];
11030 };
11031 let Some(end_row) = (selection.start.row..=selection.end.row)
11032 .rev()
11033 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11034 else {
11035 return vec![];
11036 };
11037
11038 let mut row = start_row;
11039 let mut ranges = Vec::new();
11040 while let Some(blank_row) =
11041 (row..end_row).find(|row| buffer.is_line_blank(MultiBufferRow(*row)))
11042 {
11043 let next_paragraph_start = (blank_row + 1..=end_row)
11044 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11045 .unwrap();
11046 ranges.push((
11047 language_settings.clone(),
11048 language_scope.clone(),
11049 Point::new(row, 0)..Point::new(blank_row - 1, 0),
11050 ));
11051 row = next_paragraph_start;
11052 }
11053 ranges.push((
11054 language_settings.clone(),
11055 language_scope.clone(),
11056 Point::new(row, 0)..Point::new(end_row, 0),
11057 ));
11058
11059 ranges
11060 });
11061
11062 let mut edits = Vec::new();
11063 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11064
11065 for (language_settings, language_scope, range) in ranges {
11066 let mut start_row = range.start.row;
11067 let mut end_row = range.end.row;
11068
11069 // Skip selections that overlap with a range that has already been rewrapped.
11070 let selection_range = start_row..end_row;
11071 if rewrapped_row_ranges
11072 .iter()
11073 .any(|range| range.overlaps(&selection_range))
11074 {
11075 continue;
11076 }
11077
11078 let tab_size = language_settings.tab_size;
11079
11080 // Since not all lines in the selection may be at the same indent
11081 // level, choose the indent size that is the most common between all
11082 // of the lines.
11083 //
11084 // If there is a tie, we use the deepest indent.
11085 let (indent_size, indent_end) = {
11086 let mut indent_size_occurrences = HashMap::default();
11087 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
11088
11089 for row in start_row..=end_row {
11090 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11091 rows_by_indent_size.entry(indent).or_default().push(row);
11092 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
11093 }
11094
11095 let indent_size = indent_size_occurrences
11096 .into_iter()
11097 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
11098 .map(|(indent, _)| indent)
11099 .unwrap_or_default();
11100 let row = rows_by_indent_size[&indent_size][0];
11101 let indent_end = Point::new(row, indent_size.len);
11102
11103 (indent_size, indent_end)
11104 };
11105
11106 let mut line_prefix = indent_size.chars().collect::<String>();
11107
11108 let mut inside_comment = false;
11109 if let Some(comment_prefix) = language_scope.and_then(|language| {
11110 language
11111 .line_comment_prefixes()
11112 .iter()
11113 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11114 .cloned()
11115 }) {
11116 line_prefix.push_str(&comment_prefix);
11117 inside_comment = true;
11118 }
11119
11120 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11121 RewrapBehavior::InComments => inside_comment,
11122 RewrapBehavior::InSelections => !range.is_empty(),
11123 RewrapBehavior::Anywhere => true,
11124 };
11125
11126 let should_rewrap = options.override_language_settings
11127 || allow_rewrap_based_on_language
11128 || self.hard_wrap.is_some();
11129 if !should_rewrap {
11130 continue;
11131 }
11132
11133 if range.is_empty() {
11134 'expand_upwards: while start_row > 0 {
11135 let prev_row = start_row - 1;
11136 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11137 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11138 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11139 {
11140 start_row = prev_row;
11141 } else {
11142 break 'expand_upwards;
11143 }
11144 }
11145
11146 'expand_downwards: while end_row < buffer.max_point().row {
11147 let next_row = end_row + 1;
11148 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11149 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11150 && !buffer.is_line_blank(MultiBufferRow(next_row))
11151 {
11152 end_row = next_row;
11153 } else {
11154 break 'expand_downwards;
11155 }
11156 }
11157 }
11158
11159 let start = Point::new(start_row, 0);
11160 let start_offset = start.to_offset(&buffer);
11161 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11162 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11163 let Some(lines_without_prefixes) = selection_text
11164 .lines()
11165 .map(|line| {
11166 line.strip_prefix(&line_prefix)
11167 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
11168 .with_context(|| {
11169 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11170 })
11171 })
11172 .collect::<Result<Vec<_>, _>>()
11173 .log_err()
11174 else {
11175 continue;
11176 };
11177
11178 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11179 buffer
11180 .language_settings_at(Point::new(start_row, 0), cx)
11181 .preferred_line_length as usize
11182 });
11183 let wrapped_text = wrap_with_prefix(
11184 line_prefix,
11185 lines_without_prefixes.join("\n"),
11186 wrap_column,
11187 tab_size,
11188 options.preserve_existing_whitespace,
11189 );
11190
11191 // TODO: should always use char-based diff while still supporting cursor behavior that
11192 // matches vim.
11193 let mut diff_options = DiffOptions::default();
11194 if options.override_language_settings {
11195 diff_options.max_word_diff_len = 0;
11196 diff_options.max_word_diff_line_count = 0;
11197 } else {
11198 diff_options.max_word_diff_len = usize::MAX;
11199 diff_options.max_word_diff_line_count = usize::MAX;
11200 }
11201
11202 for (old_range, new_text) in
11203 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11204 {
11205 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11206 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11207 edits.push((edit_start..edit_end, new_text));
11208 }
11209
11210 rewrapped_row_ranges.push(start_row..=end_row);
11211 }
11212
11213 self.buffer
11214 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11215 }
11216
11217 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11218 let mut text = String::new();
11219 let buffer = self.buffer.read(cx).snapshot(cx);
11220 let mut selections = self.selections.all::<Point>(cx);
11221 let mut clipboard_selections = Vec::with_capacity(selections.len());
11222 {
11223 let max_point = buffer.max_point();
11224 let mut is_first = true;
11225 for selection in &mut selections {
11226 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11227 if is_entire_line {
11228 selection.start = Point::new(selection.start.row, 0);
11229 if !selection.is_empty() && selection.end.column == 0 {
11230 selection.end = cmp::min(max_point, selection.end);
11231 } else {
11232 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11233 }
11234 selection.goal = SelectionGoal::None;
11235 }
11236 if is_first {
11237 is_first = false;
11238 } else {
11239 text += "\n";
11240 }
11241 let mut len = 0;
11242 for chunk in buffer.text_for_range(selection.start..selection.end) {
11243 text.push_str(chunk);
11244 len += chunk.len();
11245 }
11246 clipboard_selections.push(ClipboardSelection {
11247 len,
11248 is_entire_line,
11249 first_line_indent: buffer
11250 .indent_size_for_line(MultiBufferRow(selection.start.row))
11251 .len,
11252 });
11253 }
11254 }
11255
11256 self.transact(window, cx, |this, window, cx| {
11257 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11258 s.select(selections);
11259 });
11260 this.insert("", window, cx);
11261 });
11262 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11263 }
11264
11265 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11266 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11267 let item = self.cut_common(window, cx);
11268 cx.write_to_clipboard(item);
11269 }
11270
11271 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11272 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11273 self.change_selections(None, window, cx, |s| {
11274 s.move_with(|snapshot, sel| {
11275 if sel.is_empty() {
11276 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11277 }
11278 });
11279 });
11280 let item = self.cut_common(window, cx);
11281 cx.set_global(KillRing(item))
11282 }
11283
11284 pub fn kill_ring_yank(
11285 &mut self,
11286 _: &KillRingYank,
11287 window: &mut Window,
11288 cx: &mut Context<Self>,
11289 ) {
11290 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11291 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11292 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11293 (kill_ring.text().to_string(), kill_ring.metadata_json())
11294 } else {
11295 return;
11296 }
11297 } else {
11298 return;
11299 };
11300 self.do_paste(&text, metadata, false, window, cx);
11301 }
11302
11303 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11304 self.do_copy(true, cx);
11305 }
11306
11307 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11308 self.do_copy(false, cx);
11309 }
11310
11311 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11312 let selections = self.selections.all::<Point>(cx);
11313 let buffer = self.buffer.read(cx).read(cx);
11314 let mut text = String::new();
11315
11316 let mut clipboard_selections = Vec::with_capacity(selections.len());
11317 {
11318 let max_point = buffer.max_point();
11319 let mut is_first = true;
11320 for selection in &selections {
11321 let mut start = selection.start;
11322 let mut end = selection.end;
11323 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11324 if is_entire_line {
11325 start = Point::new(start.row, 0);
11326 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11327 }
11328
11329 let mut trimmed_selections = Vec::new();
11330 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11331 let row = MultiBufferRow(start.row);
11332 let first_indent = buffer.indent_size_for_line(row);
11333 if first_indent.len == 0 || start.column > first_indent.len {
11334 trimmed_selections.push(start..end);
11335 } else {
11336 trimmed_selections.push(
11337 Point::new(row.0, first_indent.len)
11338 ..Point::new(row.0, buffer.line_len(row)),
11339 );
11340 for row in start.row + 1..=end.row {
11341 let mut line_len = buffer.line_len(MultiBufferRow(row));
11342 if row == end.row {
11343 line_len = end.column;
11344 }
11345 if line_len == 0 {
11346 trimmed_selections
11347 .push(Point::new(row, 0)..Point::new(row, line_len));
11348 continue;
11349 }
11350 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11351 if row_indent_size.len >= first_indent.len {
11352 trimmed_selections.push(
11353 Point::new(row, first_indent.len)..Point::new(row, line_len),
11354 );
11355 } else {
11356 trimmed_selections.clear();
11357 trimmed_selections.push(start..end);
11358 break;
11359 }
11360 }
11361 }
11362 } else {
11363 trimmed_selections.push(start..end);
11364 }
11365
11366 for trimmed_range in trimmed_selections {
11367 if is_first {
11368 is_first = false;
11369 } else {
11370 text += "\n";
11371 }
11372 let mut len = 0;
11373 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11374 text.push_str(chunk);
11375 len += chunk.len();
11376 }
11377 clipboard_selections.push(ClipboardSelection {
11378 len,
11379 is_entire_line,
11380 first_line_indent: buffer
11381 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11382 .len,
11383 });
11384 }
11385 }
11386 }
11387
11388 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11389 text,
11390 clipboard_selections,
11391 ));
11392 }
11393
11394 pub fn do_paste(
11395 &mut self,
11396 text: &String,
11397 clipboard_selections: Option<Vec<ClipboardSelection>>,
11398 handle_entire_lines: bool,
11399 window: &mut Window,
11400 cx: &mut Context<Self>,
11401 ) {
11402 if self.read_only(cx) {
11403 return;
11404 }
11405
11406 let clipboard_text = Cow::Borrowed(text);
11407
11408 self.transact(window, cx, |this, window, cx| {
11409 if let Some(mut clipboard_selections) = clipboard_selections {
11410 let old_selections = this.selections.all::<usize>(cx);
11411 let all_selections_were_entire_line =
11412 clipboard_selections.iter().all(|s| s.is_entire_line);
11413 let first_selection_indent_column =
11414 clipboard_selections.first().map(|s| s.first_line_indent);
11415 if clipboard_selections.len() != old_selections.len() {
11416 clipboard_selections.drain(..);
11417 }
11418 let cursor_offset = this.selections.last::<usize>(cx).head();
11419 let mut auto_indent_on_paste = true;
11420
11421 this.buffer.update(cx, |buffer, cx| {
11422 let snapshot = buffer.read(cx);
11423 auto_indent_on_paste = snapshot
11424 .language_settings_at(cursor_offset, cx)
11425 .auto_indent_on_paste;
11426
11427 let mut start_offset = 0;
11428 let mut edits = Vec::new();
11429 let mut original_indent_columns = Vec::new();
11430 for (ix, selection) in old_selections.iter().enumerate() {
11431 let to_insert;
11432 let entire_line;
11433 let original_indent_column;
11434 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11435 let end_offset = start_offset + clipboard_selection.len;
11436 to_insert = &clipboard_text[start_offset..end_offset];
11437 entire_line = clipboard_selection.is_entire_line;
11438 start_offset = end_offset + 1;
11439 original_indent_column = Some(clipboard_selection.first_line_indent);
11440 } else {
11441 to_insert = clipboard_text.as_str();
11442 entire_line = all_selections_were_entire_line;
11443 original_indent_column = first_selection_indent_column
11444 }
11445
11446 // If the corresponding selection was empty when this slice of the
11447 // clipboard text was written, then the entire line containing the
11448 // selection was copied. If this selection is also currently empty,
11449 // then paste the line before the current line of the buffer.
11450 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11451 let column = selection.start.to_point(&snapshot).column as usize;
11452 let line_start = selection.start - column;
11453 line_start..line_start
11454 } else {
11455 selection.range()
11456 };
11457
11458 edits.push((range, to_insert));
11459 original_indent_columns.push(original_indent_column);
11460 }
11461 drop(snapshot);
11462
11463 buffer.edit(
11464 edits,
11465 if auto_indent_on_paste {
11466 Some(AutoindentMode::Block {
11467 original_indent_columns,
11468 })
11469 } else {
11470 None
11471 },
11472 cx,
11473 );
11474 });
11475
11476 let selections = this.selections.all::<usize>(cx);
11477 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11478 s.select(selections)
11479 });
11480 } else {
11481 this.insert(&clipboard_text, window, cx);
11482 }
11483 });
11484 }
11485
11486 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11487 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11488 if let Some(item) = cx.read_from_clipboard() {
11489 let entries = item.entries();
11490
11491 match entries.first() {
11492 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11493 // of all the pasted entries.
11494 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11495 .do_paste(
11496 clipboard_string.text(),
11497 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11498 true,
11499 window,
11500 cx,
11501 ),
11502 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11503 }
11504 }
11505 }
11506
11507 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11508 if self.read_only(cx) {
11509 return;
11510 }
11511
11512 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11513
11514 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11515 if let Some((selections, _)) =
11516 self.selection_history.transaction(transaction_id).cloned()
11517 {
11518 self.change_selections(None, window, cx, |s| {
11519 s.select_anchors(selections.to_vec());
11520 });
11521 } else {
11522 log::error!(
11523 "No entry in selection_history found for undo. \
11524 This may correspond to a bug where undo does not update the selection. \
11525 If this is occurring, please add details to \
11526 https://github.com/zed-industries/zed/issues/22692"
11527 );
11528 }
11529 self.request_autoscroll(Autoscroll::fit(), cx);
11530 self.unmark_text(window, cx);
11531 self.refresh_inline_completion(true, false, window, cx);
11532 cx.emit(EditorEvent::Edited { transaction_id });
11533 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11534 }
11535 }
11536
11537 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11538 if self.read_only(cx) {
11539 return;
11540 }
11541
11542 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11543
11544 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11545 if let Some((_, Some(selections))) =
11546 self.selection_history.transaction(transaction_id).cloned()
11547 {
11548 self.change_selections(None, window, cx, |s| {
11549 s.select_anchors(selections.to_vec());
11550 });
11551 } else {
11552 log::error!(
11553 "No entry in selection_history found for redo. \
11554 This may correspond to a bug where undo does not update the selection. \
11555 If this is occurring, please add details to \
11556 https://github.com/zed-industries/zed/issues/22692"
11557 );
11558 }
11559 self.request_autoscroll(Autoscroll::fit(), cx);
11560 self.unmark_text(window, cx);
11561 self.refresh_inline_completion(true, false, window, cx);
11562 cx.emit(EditorEvent::Edited { transaction_id });
11563 }
11564 }
11565
11566 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11567 self.buffer
11568 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11569 }
11570
11571 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11572 self.buffer
11573 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11574 }
11575
11576 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11577 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11578 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11579 s.move_with(|map, selection| {
11580 let cursor = if selection.is_empty() {
11581 movement::left(map, selection.start)
11582 } else {
11583 selection.start
11584 };
11585 selection.collapse_to(cursor, SelectionGoal::None);
11586 });
11587 })
11588 }
11589
11590 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11591 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11592 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11593 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11594 })
11595 }
11596
11597 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11598 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11599 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11600 s.move_with(|map, selection| {
11601 let cursor = if selection.is_empty() {
11602 movement::right(map, selection.end)
11603 } else {
11604 selection.end
11605 };
11606 selection.collapse_to(cursor, SelectionGoal::None)
11607 });
11608 })
11609 }
11610
11611 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11612 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11613 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11614 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11615 })
11616 }
11617
11618 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11619 if self.take_rename(true, window, cx).is_some() {
11620 return;
11621 }
11622
11623 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11624 cx.propagate();
11625 return;
11626 }
11627
11628 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11629
11630 let text_layout_details = &self.text_layout_details(window);
11631 let selection_count = self.selections.count();
11632 let first_selection = self.selections.first_anchor();
11633
11634 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11635 s.move_with(|map, selection| {
11636 if !selection.is_empty() {
11637 selection.goal = SelectionGoal::None;
11638 }
11639 let (cursor, goal) = movement::up(
11640 map,
11641 selection.start,
11642 selection.goal,
11643 false,
11644 text_layout_details,
11645 );
11646 selection.collapse_to(cursor, goal);
11647 });
11648 });
11649
11650 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11651 {
11652 cx.propagate();
11653 }
11654 }
11655
11656 pub fn move_up_by_lines(
11657 &mut self,
11658 action: &MoveUpByLines,
11659 window: &mut Window,
11660 cx: &mut Context<Self>,
11661 ) {
11662 if self.take_rename(true, window, cx).is_some() {
11663 return;
11664 }
11665
11666 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11667 cx.propagate();
11668 return;
11669 }
11670
11671 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11672
11673 let text_layout_details = &self.text_layout_details(window);
11674
11675 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11676 s.move_with(|map, selection| {
11677 if !selection.is_empty() {
11678 selection.goal = SelectionGoal::None;
11679 }
11680 let (cursor, goal) = movement::up_by_rows(
11681 map,
11682 selection.start,
11683 action.lines,
11684 selection.goal,
11685 false,
11686 text_layout_details,
11687 );
11688 selection.collapse_to(cursor, goal);
11689 });
11690 })
11691 }
11692
11693 pub fn move_down_by_lines(
11694 &mut self,
11695 action: &MoveDownByLines,
11696 window: &mut Window,
11697 cx: &mut Context<Self>,
11698 ) {
11699 if self.take_rename(true, window, cx).is_some() {
11700 return;
11701 }
11702
11703 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11704 cx.propagate();
11705 return;
11706 }
11707
11708 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11709
11710 let text_layout_details = &self.text_layout_details(window);
11711
11712 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11713 s.move_with(|map, selection| {
11714 if !selection.is_empty() {
11715 selection.goal = SelectionGoal::None;
11716 }
11717 let (cursor, goal) = movement::down_by_rows(
11718 map,
11719 selection.start,
11720 action.lines,
11721 selection.goal,
11722 false,
11723 text_layout_details,
11724 );
11725 selection.collapse_to(cursor, goal);
11726 });
11727 })
11728 }
11729
11730 pub fn select_down_by_lines(
11731 &mut self,
11732 action: &SelectDownByLines,
11733 window: &mut Window,
11734 cx: &mut Context<Self>,
11735 ) {
11736 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11737 let text_layout_details = &self.text_layout_details(window);
11738 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11739 s.move_heads_with(|map, head, goal| {
11740 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11741 })
11742 })
11743 }
11744
11745 pub fn select_up_by_lines(
11746 &mut self,
11747 action: &SelectUpByLines,
11748 window: &mut Window,
11749 cx: &mut Context<Self>,
11750 ) {
11751 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11752 let text_layout_details = &self.text_layout_details(window);
11753 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11754 s.move_heads_with(|map, head, goal| {
11755 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11756 })
11757 })
11758 }
11759
11760 pub fn select_page_up(
11761 &mut self,
11762 _: &SelectPageUp,
11763 window: &mut Window,
11764 cx: &mut Context<Self>,
11765 ) {
11766 let Some(row_count) = self.visible_row_count() else {
11767 return;
11768 };
11769
11770 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11771
11772 let text_layout_details = &self.text_layout_details(window);
11773
11774 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11775 s.move_heads_with(|map, head, goal| {
11776 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11777 })
11778 })
11779 }
11780
11781 pub fn move_page_up(
11782 &mut self,
11783 action: &MovePageUp,
11784 window: &mut Window,
11785 cx: &mut Context<Self>,
11786 ) {
11787 if self.take_rename(true, window, cx).is_some() {
11788 return;
11789 }
11790
11791 if self
11792 .context_menu
11793 .borrow_mut()
11794 .as_mut()
11795 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
11796 .unwrap_or(false)
11797 {
11798 return;
11799 }
11800
11801 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11802 cx.propagate();
11803 return;
11804 }
11805
11806 let Some(row_count) = self.visible_row_count() else {
11807 return;
11808 };
11809
11810 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11811
11812 let autoscroll = if action.center_cursor {
11813 Autoscroll::center()
11814 } else {
11815 Autoscroll::fit()
11816 };
11817
11818 let text_layout_details = &self.text_layout_details(window);
11819
11820 self.change_selections(Some(autoscroll), window, cx, |s| {
11821 s.move_with(|map, selection| {
11822 if !selection.is_empty() {
11823 selection.goal = SelectionGoal::None;
11824 }
11825 let (cursor, goal) = movement::up_by_rows(
11826 map,
11827 selection.end,
11828 row_count,
11829 selection.goal,
11830 false,
11831 text_layout_details,
11832 );
11833 selection.collapse_to(cursor, goal);
11834 });
11835 });
11836 }
11837
11838 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11839 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11840 let text_layout_details = &self.text_layout_details(window);
11841 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11842 s.move_heads_with(|map, head, goal| {
11843 movement::up(map, head, goal, false, text_layout_details)
11844 })
11845 })
11846 }
11847
11848 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11849 self.take_rename(true, window, cx);
11850
11851 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11852 cx.propagate();
11853 return;
11854 }
11855
11856 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11857
11858 let text_layout_details = &self.text_layout_details(window);
11859 let selection_count = self.selections.count();
11860 let first_selection = self.selections.first_anchor();
11861
11862 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11863 s.move_with(|map, selection| {
11864 if !selection.is_empty() {
11865 selection.goal = SelectionGoal::None;
11866 }
11867 let (cursor, goal) = movement::down(
11868 map,
11869 selection.end,
11870 selection.goal,
11871 false,
11872 text_layout_details,
11873 );
11874 selection.collapse_to(cursor, goal);
11875 });
11876 });
11877
11878 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11879 {
11880 cx.propagate();
11881 }
11882 }
11883
11884 pub fn select_page_down(
11885 &mut self,
11886 _: &SelectPageDown,
11887 window: &mut Window,
11888 cx: &mut Context<Self>,
11889 ) {
11890 let Some(row_count) = self.visible_row_count() else {
11891 return;
11892 };
11893
11894 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11895
11896 let text_layout_details = &self.text_layout_details(window);
11897
11898 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11899 s.move_heads_with(|map, head, goal| {
11900 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11901 })
11902 })
11903 }
11904
11905 pub fn move_page_down(
11906 &mut self,
11907 action: &MovePageDown,
11908 window: &mut Window,
11909 cx: &mut Context<Self>,
11910 ) {
11911 if self.take_rename(true, window, cx).is_some() {
11912 return;
11913 }
11914
11915 if self
11916 .context_menu
11917 .borrow_mut()
11918 .as_mut()
11919 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
11920 .unwrap_or(false)
11921 {
11922 return;
11923 }
11924
11925 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11926 cx.propagate();
11927 return;
11928 }
11929
11930 let Some(row_count) = self.visible_row_count() else {
11931 return;
11932 };
11933
11934 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11935
11936 let autoscroll = if action.center_cursor {
11937 Autoscroll::center()
11938 } else {
11939 Autoscroll::fit()
11940 };
11941
11942 let text_layout_details = &self.text_layout_details(window);
11943 self.change_selections(Some(autoscroll), window, cx, |s| {
11944 s.move_with(|map, selection| {
11945 if !selection.is_empty() {
11946 selection.goal = SelectionGoal::None;
11947 }
11948 let (cursor, goal) = movement::down_by_rows(
11949 map,
11950 selection.end,
11951 row_count,
11952 selection.goal,
11953 false,
11954 text_layout_details,
11955 );
11956 selection.collapse_to(cursor, goal);
11957 });
11958 });
11959 }
11960
11961 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11962 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11963 let text_layout_details = &self.text_layout_details(window);
11964 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11965 s.move_heads_with(|map, head, goal| {
11966 movement::down(map, head, goal, false, text_layout_details)
11967 })
11968 });
11969 }
11970
11971 pub fn context_menu_first(
11972 &mut self,
11973 _: &ContextMenuFirst,
11974 window: &mut Window,
11975 cx: &mut Context<Self>,
11976 ) {
11977 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11978 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
11979 }
11980 }
11981
11982 pub fn context_menu_prev(
11983 &mut self,
11984 _: &ContextMenuPrevious,
11985 window: &mut Window,
11986 cx: &mut Context<Self>,
11987 ) {
11988 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11989 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
11990 }
11991 }
11992
11993 pub fn context_menu_next(
11994 &mut self,
11995 _: &ContextMenuNext,
11996 window: &mut Window,
11997 cx: &mut Context<Self>,
11998 ) {
11999 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12000 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12001 }
12002 }
12003
12004 pub fn context_menu_last(
12005 &mut self,
12006 _: &ContextMenuLast,
12007 window: &mut Window,
12008 cx: &mut Context<Self>,
12009 ) {
12010 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12011 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12012 }
12013 }
12014
12015 pub fn move_to_previous_word_start(
12016 &mut self,
12017 _: &MoveToPreviousWordStart,
12018 window: &mut Window,
12019 cx: &mut Context<Self>,
12020 ) {
12021 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12022 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12023 s.move_cursors_with(|map, head, _| {
12024 (
12025 movement::previous_word_start(map, head),
12026 SelectionGoal::None,
12027 )
12028 });
12029 })
12030 }
12031
12032 pub fn move_to_previous_subword_start(
12033 &mut self,
12034 _: &MoveToPreviousSubwordStart,
12035 window: &mut Window,
12036 cx: &mut Context<Self>,
12037 ) {
12038 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12039 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12040 s.move_cursors_with(|map, head, _| {
12041 (
12042 movement::previous_subword_start(map, head),
12043 SelectionGoal::None,
12044 )
12045 });
12046 })
12047 }
12048
12049 pub fn select_to_previous_word_start(
12050 &mut self,
12051 _: &SelectToPreviousWordStart,
12052 window: &mut Window,
12053 cx: &mut Context<Self>,
12054 ) {
12055 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12056 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12057 s.move_heads_with(|map, head, _| {
12058 (
12059 movement::previous_word_start(map, head),
12060 SelectionGoal::None,
12061 )
12062 });
12063 })
12064 }
12065
12066 pub fn select_to_previous_subword_start(
12067 &mut self,
12068 _: &SelectToPreviousSubwordStart,
12069 window: &mut Window,
12070 cx: &mut Context<Self>,
12071 ) {
12072 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12073 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12074 s.move_heads_with(|map, head, _| {
12075 (
12076 movement::previous_subword_start(map, head),
12077 SelectionGoal::None,
12078 )
12079 });
12080 })
12081 }
12082
12083 pub fn delete_to_previous_word_start(
12084 &mut self,
12085 action: &DeleteToPreviousWordStart,
12086 window: &mut Window,
12087 cx: &mut Context<Self>,
12088 ) {
12089 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12090 self.transact(window, cx, |this, window, cx| {
12091 this.select_autoclose_pair(window, cx);
12092 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12093 s.move_with(|map, selection| {
12094 if selection.is_empty() {
12095 let cursor = if action.ignore_newlines {
12096 movement::previous_word_start(map, selection.head())
12097 } else {
12098 movement::previous_word_start_or_newline(map, selection.head())
12099 };
12100 selection.set_head(cursor, SelectionGoal::None);
12101 }
12102 });
12103 });
12104 this.insert("", window, cx);
12105 });
12106 }
12107
12108 pub fn delete_to_previous_subword_start(
12109 &mut self,
12110 _: &DeleteToPreviousSubwordStart,
12111 window: &mut Window,
12112 cx: &mut Context<Self>,
12113 ) {
12114 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12115 self.transact(window, cx, |this, window, cx| {
12116 this.select_autoclose_pair(window, cx);
12117 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12118 s.move_with(|map, selection| {
12119 if selection.is_empty() {
12120 let cursor = movement::previous_subword_start(map, selection.head());
12121 selection.set_head(cursor, SelectionGoal::None);
12122 }
12123 });
12124 });
12125 this.insert("", window, cx);
12126 });
12127 }
12128
12129 pub fn move_to_next_word_end(
12130 &mut self,
12131 _: &MoveToNextWordEnd,
12132 window: &mut Window,
12133 cx: &mut Context<Self>,
12134 ) {
12135 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12136 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12137 s.move_cursors_with(|map, head, _| {
12138 (movement::next_word_end(map, head), SelectionGoal::None)
12139 });
12140 })
12141 }
12142
12143 pub fn move_to_next_subword_end(
12144 &mut self,
12145 _: &MoveToNextSubwordEnd,
12146 window: &mut Window,
12147 cx: &mut Context<Self>,
12148 ) {
12149 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12150 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12151 s.move_cursors_with(|map, head, _| {
12152 (movement::next_subword_end(map, head), SelectionGoal::None)
12153 });
12154 })
12155 }
12156
12157 pub fn select_to_next_word_end(
12158 &mut self,
12159 _: &SelectToNextWordEnd,
12160 window: &mut Window,
12161 cx: &mut Context<Self>,
12162 ) {
12163 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12164 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12165 s.move_heads_with(|map, head, _| {
12166 (movement::next_word_end(map, head), SelectionGoal::None)
12167 });
12168 })
12169 }
12170
12171 pub fn select_to_next_subword_end(
12172 &mut self,
12173 _: &SelectToNextSubwordEnd,
12174 window: &mut Window,
12175 cx: &mut Context<Self>,
12176 ) {
12177 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12178 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12179 s.move_heads_with(|map, head, _| {
12180 (movement::next_subword_end(map, head), SelectionGoal::None)
12181 });
12182 })
12183 }
12184
12185 pub fn delete_to_next_word_end(
12186 &mut self,
12187 action: &DeleteToNextWordEnd,
12188 window: &mut Window,
12189 cx: &mut Context<Self>,
12190 ) {
12191 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12192 self.transact(window, cx, |this, window, cx| {
12193 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12194 s.move_with(|map, selection| {
12195 if selection.is_empty() {
12196 let cursor = if action.ignore_newlines {
12197 movement::next_word_end(map, selection.head())
12198 } else {
12199 movement::next_word_end_or_newline(map, selection.head())
12200 };
12201 selection.set_head(cursor, SelectionGoal::None);
12202 }
12203 });
12204 });
12205 this.insert("", window, cx);
12206 });
12207 }
12208
12209 pub fn delete_to_next_subword_end(
12210 &mut self,
12211 _: &DeleteToNextSubwordEnd,
12212 window: &mut Window,
12213 cx: &mut Context<Self>,
12214 ) {
12215 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12216 self.transact(window, cx, |this, window, cx| {
12217 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12218 s.move_with(|map, selection| {
12219 if selection.is_empty() {
12220 let cursor = movement::next_subword_end(map, selection.head());
12221 selection.set_head(cursor, SelectionGoal::None);
12222 }
12223 });
12224 });
12225 this.insert("", window, cx);
12226 });
12227 }
12228
12229 pub fn move_to_beginning_of_line(
12230 &mut self,
12231 action: &MoveToBeginningOfLine,
12232 window: &mut Window,
12233 cx: &mut Context<Self>,
12234 ) {
12235 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12236 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12237 s.move_cursors_with(|map, head, _| {
12238 (
12239 movement::indented_line_beginning(
12240 map,
12241 head,
12242 action.stop_at_soft_wraps,
12243 action.stop_at_indent,
12244 ),
12245 SelectionGoal::None,
12246 )
12247 });
12248 })
12249 }
12250
12251 pub fn select_to_beginning_of_line(
12252 &mut self,
12253 action: &SelectToBeginningOfLine,
12254 window: &mut Window,
12255 cx: &mut Context<Self>,
12256 ) {
12257 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12258 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12259 s.move_heads_with(|map, head, _| {
12260 (
12261 movement::indented_line_beginning(
12262 map,
12263 head,
12264 action.stop_at_soft_wraps,
12265 action.stop_at_indent,
12266 ),
12267 SelectionGoal::None,
12268 )
12269 });
12270 });
12271 }
12272
12273 pub fn delete_to_beginning_of_line(
12274 &mut self,
12275 action: &DeleteToBeginningOfLine,
12276 window: &mut Window,
12277 cx: &mut Context<Self>,
12278 ) {
12279 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12280 self.transact(window, cx, |this, window, cx| {
12281 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12282 s.move_with(|_, selection| {
12283 selection.reversed = true;
12284 });
12285 });
12286
12287 this.select_to_beginning_of_line(
12288 &SelectToBeginningOfLine {
12289 stop_at_soft_wraps: false,
12290 stop_at_indent: action.stop_at_indent,
12291 },
12292 window,
12293 cx,
12294 );
12295 this.backspace(&Backspace, window, cx);
12296 });
12297 }
12298
12299 pub fn move_to_end_of_line(
12300 &mut self,
12301 action: &MoveToEndOfLine,
12302 window: &mut Window,
12303 cx: &mut Context<Self>,
12304 ) {
12305 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12306 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12307 s.move_cursors_with(|map, head, _| {
12308 (
12309 movement::line_end(map, head, action.stop_at_soft_wraps),
12310 SelectionGoal::None,
12311 )
12312 });
12313 })
12314 }
12315
12316 pub fn select_to_end_of_line(
12317 &mut self,
12318 action: &SelectToEndOfLine,
12319 window: &mut Window,
12320 cx: &mut Context<Self>,
12321 ) {
12322 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12323 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12324 s.move_heads_with(|map, head, _| {
12325 (
12326 movement::line_end(map, head, action.stop_at_soft_wraps),
12327 SelectionGoal::None,
12328 )
12329 });
12330 })
12331 }
12332
12333 pub fn delete_to_end_of_line(
12334 &mut self,
12335 _: &DeleteToEndOfLine,
12336 window: &mut Window,
12337 cx: &mut Context<Self>,
12338 ) {
12339 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12340 self.transact(window, cx, |this, window, cx| {
12341 this.select_to_end_of_line(
12342 &SelectToEndOfLine {
12343 stop_at_soft_wraps: false,
12344 },
12345 window,
12346 cx,
12347 );
12348 this.delete(&Delete, window, cx);
12349 });
12350 }
12351
12352 pub fn cut_to_end_of_line(
12353 &mut self,
12354 _: &CutToEndOfLine,
12355 window: &mut Window,
12356 cx: &mut Context<Self>,
12357 ) {
12358 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12359 self.transact(window, cx, |this, window, cx| {
12360 this.select_to_end_of_line(
12361 &SelectToEndOfLine {
12362 stop_at_soft_wraps: false,
12363 },
12364 window,
12365 cx,
12366 );
12367 this.cut(&Cut, window, cx);
12368 });
12369 }
12370
12371 pub fn move_to_start_of_paragraph(
12372 &mut self,
12373 _: &MoveToStartOfParagraph,
12374 window: &mut Window,
12375 cx: &mut Context<Self>,
12376 ) {
12377 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12378 cx.propagate();
12379 return;
12380 }
12381 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12382 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12383 s.move_with(|map, selection| {
12384 selection.collapse_to(
12385 movement::start_of_paragraph(map, selection.head(), 1),
12386 SelectionGoal::None,
12387 )
12388 });
12389 })
12390 }
12391
12392 pub fn move_to_end_of_paragraph(
12393 &mut self,
12394 _: &MoveToEndOfParagraph,
12395 window: &mut Window,
12396 cx: &mut Context<Self>,
12397 ) {
12398 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12399 cx.propagate();
12400 return;
12401 }
12402 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12403 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12404 s.move_with(|map, selection| {
12405 selection.collapse_to(
12406 movement::end_of_paragraph(map, selection.head(), 1),
12407 SelectionGoal::None,
12408 )
12409 });
12410 })
12411 }
12412
12413 pub fn select_to_start_of_paragraph(
12414 &mut self,
12415 _: &SelectToStartOfParagraph,
12416 window: &mut Window,
12417 cx: &mut Context<Self>,
12418 ) {
12419 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12420 cx.propagate();
12421 return;
12422 }
12423 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12424 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12425 s.move_heads_with(|map, head, _| {
12426 (
12427 movement::start_of_paragraph(map, head, 1),
12428 SelectionGoal::None,
12429 )
12430 });
12431 })
12432 }
12433
12434 pub fn select_to_end_of_paragraph(
12435 &mut self,
12436 _: &SelectToEndOfParagraph,
12437 window: &mut Window,
12438 cx: &mut Context<Self>,
12439 ) {
12440 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12441 cx.propagate();
12442 return;
12443 }
12444 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12445 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12446 s.move_heads_with(|map, head, _| {
12447 (
12448 movement::end_of_paragraph(map, head, 1),
12449 SelectionGoal::None,
12450 )
12451 });
12452 })
12453 }
12454
12455 pub fn move_to_start_of_excerpt(
12456 &mut self,
12457 _: &MoveToStartOfExcerpt,
12458 window: &mut Window,
12459 cx: &mut Context<Self>,
12460 ) {
12461 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12462 cx.propagate();
12463 return;
12464 }
12465 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12466 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12467 s.move_with(|map, selection| {
12468 selection.collapse_to(
12469 movement::start_of_excerpt(
12470 map,
12471 selection.head(),
12472 workspace::searchable::Direction::Prev,
12473 ),
12474 SelectionGoal::None,
12475 )
12476 });
12477 })
12478 }
12479
12480 pub fn move_to_start_of_next_excerpt(
12481 &mut self,
12482 _: &MoveToStartOfNextExcerpt,
12483 window: &mut Window,
12484 cx: &mut Context<Self>,
12485 ) {
12486 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12487 cx.propagate();
12488 return;
12489 }
12490
12491 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12492 s.move_with(|map, selection| {
12493 selection.collapse_to(
12494 movement::start_of_excerpt(
12495 map,
12496 selection.head(),
12497 workspace::searchable::Direction::Next,
12498 ),
12499 SelectionGoal::None,
12500 )
12501 });
12502 })
12503 }
12504
12505 pub fn move_to_end_of_excerpt(
12506 &mut self,
12507 _: &MoveToEndOfExcerpt,
12508 window: &mut Window,
12509 cx: &mut Context<Self>,
12510 ) {
12511 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12512 cx.propagate();
12513 return;
12514 }
12515 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12516 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12517 s.move_with(|map, selection| {
12518 selection.collapse_to(
12519 movement::end_of_excerpt(
12520 map,
12521 selection.head(),
12522 workspace::searchable::Direction::Next,
12523 ),
12524 SelectionGoal::None,
12525 )
12526 });
12527 })
12528 }
12529
12530 pub fn move_to_end_of_previous_excerpt(
12531 &mut self,
12532 _: &MoveToEndOfPreviousExcerpt,
12533 window: &mut Window,
12534 cx: &mut Context<Self>,
12535 ) {
12536 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12537 cx.propagate();
12538 return;
12539 }
12540 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12541 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12542 s.move_with(|map, selection| {
12543 selection.collapse_to(
12544 movement::end_of_excerpt(
12545 map,
12546 selection.head(),
12547 workspace::searchable::Direction::Prev,
12548 ),
12549 SelectionGoal::None,
12550 )
12551 });
12552 })
12553 }
12554
12555 pub fn select_to_start_of_excerpt(
12556 &mut self,
12557 _: &SelectToStartOfExcerpt,
12558 window: &mut Window,
12559 cx: &mut Context<Self>,
12560 ) {
12561 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12562 cx.propagate();
12563 return;
12564 }
12565 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12566 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12567 s.move_heads_with(|map, head, _| {
12568 (
12569 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12570 SelectionGoal::None,
12571 )
12572 });
12573 })
12574 }
12575
12576 pub fn select_to_start_of_next_excerpt(
12577 &mut self,
12578 _: &SelectToStartOfNextExcerpt,
12579 window: &mut Window,
12580 cx: &mut Context<Self>,
12581 ) {
12582 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12583 cx.propagate();
12584 return;
12585 }
12586 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12587 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12588 s.move_heads_with(|map, head, _| {
12589 (
12590 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12591 SelectionGoal::None,
12592 )
12593 });
12594 })
12595 }
12596
12597 pub fn select_to_end_of_excerpt(
12598 &mut self,
12599 _: &SelectToEndOfExcerpt,
12600 window: &mut Window,
12601 cx: &mut Context<Self>,
12602 ) {
12603 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12604 cx.propagate();
12605 return;
12606 }
12607 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12608 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12609 s.move_heads_with(|map, head, _| {
12610 (
12611 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12612 SelectionGoal::None,
12613 )
12614 });
12615 })
12616 }
12617
12618 pub fn select_to_end_of_previous_excerpt(
12619 &mut self,
12620 _: &SelectToEndOfPreviousExcerpt,
12621 window: &mut Window,
12622 cx: &mut Context<Self>,
12623 ) {
12624 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12625 cx.propagate();
12626 return;
12627 }
12628 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12629 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12630 s.move_heads_with(|map, head, _| {
12631 (
12632 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12633 SelectionGoal::None,
12634 )
12635 });
12636 })
12637 }
12638
12639 pub fn move_to_beginning(
12640 &mut self,
12641 _: &MoveToBeginning,
12642 window: &mut Window,
12643 cx: &mut Context<Self>,
12644 ) {
12645 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12646 cx.propagate();
12647 return;
12648 }
12649 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12650 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12651 s.select_ranges(vec![0..0]);
12652 });
12653 }
12654
12655 pub fn select_to_beginning(
12656 &mut self,
12657 _: &SelectToBeginning,
12658 window: &mut Window,
12659 cx: &mut Context<Self>,
12660 ) {
12661 let mut selection = self.selections.last::<Point>(cx);
12662 selection.set_head(Point::zero(), SelectionGoal::None);
12663 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12664 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12665 s.select(vec![selection]);
12666 });
12667 }
12668
12669 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12670 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12671 cx.propagate();
12672 return;
12673 }
12674 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12675 let cursor = self.buffer.read(cx).read(cx).len();
12676 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12677 s.select_ranges(vec![cursor..cursor])
12678 });
12679 }
12680
12681 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12682 self.nav_history = nav_history;
12683 }
12684
12685 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12686 self.nav_history.as_ref()
12687 }
12688
12689 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12690 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12691 }
12692
12693 fn push_to_nav_history(
12694 &mut self,
12695 cursor_anchor: Anchor,
12696 new_position: Option<Point>,
12697 is_deactivate: bool,
12698 cx: &mut Context<Self>,
12699 ) {
12700 if let Some(nav_history) = self.nav_history.as_mut() {
12701 let buffer = self.buffer.read(cx).read(cx);
12702 let cursor_position = cursor_anchor.to_point(&buffer);
12703 let scroll_state = self.scroll_manager.anchor();
12704 let scroll_top_row = scroll_state.top_row(&buffer);
12705 drop(buffer);
12706
12707 if let Some(new_position) = new_position {
12708 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12709 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12710 return;
12711 }
12712 }
12713
12714 nav_history.push(
12715 Some(NavigationData {
12716 cursor_anchor,
12717 cursor_position,
12718 scroll_anchor: scroll_state,
12719 scroll_top_row,
12720 }),
12721 cx,
12722 );
12723 cx.emit(EditorEvent::PushedToNavHistory {
12724 anchor: cursor_anchor,
12725 is_deactivate,
12726 })
12727 }
12728 }
12729
12730 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12731 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12732 let buffer = self.buffer.read(cx).snapshot(cx);
12733 let mut selection = self.selections.first::<usize>(cx);
12734 selection.set_head(buffer.len(), SelectionGoal::None);
12735 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12736 s.select(vec![selection]);
12737 });
12738 }
12739
12740 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12741 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12742 let end = self.buffer.read(cx).read(cx).len();
12743 self.change_selections(None, window, cx, |s| {
12744 s.select_ranges(vec![0..end]);
12745 });
12746 }
12747
12748 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12749 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12750 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12751 let mut selections = self.selections.all::<Point>(cx);
12752 let max_point = display_map.buffer_snapshot.max_point();
12753 for selection in &mut selections {
12754 let rows = selection.spanned_rows(true, &display_map);
12755 selection.start = Point::new(rows.start.0, 0);
12756 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12757 selection.reversed = false;
12758 }
12759 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12760 s.select(selections);
12761 });
12762 }
12763
12764 pub fn split_selection_into_lines(
12765 &mut self,
12766 _: &SplitSelectionIntoLines,
12767 window: &mut Window,
12768 cx: &mut Context<Self>,
12769 ) {
12770 let selections = self
12771 .selections
12772 .all::<Point>(cx)
12773 .into_iter()
12774 .map(|selection| selection.start..selection.end)
12775 .collect::<Vec<_>>();
12776 self.unfold_ranges(&selections, true, true, cx);
12777
12778 let mut new_selection_ranges = Vec::new();
12779 {
12780 let buffer = self.buffer.read(cx).read(cx);
12781 for selection in selections {
12782 for row in selection.start.row..selection.end.row {
12783 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12784 new_selection_ranges.push(cursor..cursor);
12785 }
12786
12787 let is_multiline_selection = selection.start.row != selection.end.row;
12788 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12789 // so this action feels more ergonomic when paired with other selection operations
12790 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12791 if !should_skip_last {
12792 new_selection_ranges.push(selection.end..selection.end);
12793 }
12794 }
12795 }
12796 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12797 s.select_ranges(new_selection_ranges);
12798 });
12799 }
12800
12801 pub fn add_selection_above(
12802 &mut self,
12803 _: &AddSelectionAbove,
12804 window: &mut Window,
12805 cx: &mut Context<Self>,
12806 ) {
12807 self.add_selection(true, window, cx);
12808 }
12809
12810 pub fn add_selection_below(
12811 &mut self,
12812 _: &AddSelectionBelow,
12813 window: &mut Window,
12814 cx: &mut Context<Self>,
12815 ) {
12816 self.add_selection(false, window, cx);
12817 }
12818
12819 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12820 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12821
12822 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12823 let all_selections = self.selections.all::<Point>(cx);
12824 let text_layout_details = self.text_layout_details(window);
12825
12826 let (mut columnar_selections, new_selections_to_columnarize) = {
12827 if let Some(state) = self.add_selections_state.as_ref() {
12828 let columnar_selection_ids: HashSet<_> = state
12829 .groups
12830 .iter()
12831 .flat_map(|group| group.stack.iter())
12832 .copied()
12833 .collect();
12834
12835 all_selections
12836 .into_iter()
12837 .partition(|s| columnar_selection_ids.contains(&s.id))
12838 } else {
12839 (Vec::new(), all_selections)
12840 }
12841 };
12842
12843 let mut state = self
12844 .add_selections_state
12845 .take()
12846 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
12847
12848 for selection in new_selections_to_columnarize {
12849 let range = selection.display_range(&display_map).sorted();
12850 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12851 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12852 let positions = start_x.min(end_x)..start_x.max(end_x);
12853 let mut stack = Vec::new();
12854 for row in range.start.row().0..=range.end.row().0 {
12855 if let Some(selection) = self.selections.build_columnar_selection(
12856 &display_map,
12857 DisplayRow(row),
12858 &positions,
12859 selection.reversed,
12860 &text_layout_details,
12861 ) {
12862 stack.push(selection.id);
12863 columnar_selections.push(selection);
12864 }
12865 }
12866 if !stack.is_empty() {
12867 if above {
12868 stack.reverse();
12869 }
12870 state.groups.push(AddSelectionsGroup { above, stack });
12871 }
12872 }
12873
12874 let mut final_selections = Vec::new();
12875 let end_row = if above {
12876 DisplayRow(0)
12877 } else {
12878 display_map.max_point().row()
12879 };
12880
12881 let mut last_added_item_per_group = HashMap::default();
12882 for group in state.groups.iter_mut() {
12883 if let Some(last_id) = group.stack.last() {
12884 last_added_item_per_group.insert(*last_id, group);
12885 }
12886 }
12887
12888 for selection in columnar_selections {
12889 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
12890 if above == group.above {
12891 let range = selection.display_range(&display_map).sorted();
12892 debug_assert_eq!(range.start.row(), range.end.row());
12893 let mut row = range.start.row();
12894 let positions =
12895 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12896 px(start)..px(end)
12897 } else {
12898 let start_x =
12899 display_map.x_for_display_point(range.start, &text_layout_details);
12900 let end_x =
12901 display_map.x_for_display_point(range.end, &text_layout_details);
12902 start_x.min(end_x)..start_x.max(end_x)
12903 };
12904
12905 let mut maybe_new_selection = None;
12906 while row != end_row {
12907 if above {
12908 row.0 -= 1;
12909 } else {
12910 row.0 += 1;
12911 }
12912 if let Some(new_selection) = self.selections.build_columnar_selection(
12913 &display_map,
12914 row,
12915 &positions,
12916 selection.reversed,
12917 &text_layout_details,
12918 ) {
12919 maybe_new_selection = Some(new_selection);
12920 break;
12921 }
12922 }
12923
12924 if let Some(new_selection) = maybe_new_selection {
12925 group.stack.push(new_selection.id);
12926 if above {
12927 final_selections.push(new_selection);
12928 final_selections.push(selection);
12929 } else {
12930 final_selections.push(selection);
12931 final_selections.push(new_selection);
12932 }
12933 } else {
12934 final_selections.push(selection);
12935 }
12936 } else {
12937 group.stack.pop();
12938 }
12939 } else {
12940 final_selections.push(selection);
12941 }
12942 }
12943
12944 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12945 s.select(final_selections);
12946 });
12947
12948 let final_selection_ids: HashSet<_> = self
12949 .selections
12950 .all::<Point>(cx)
12951 .iter()
12952 .map(|s| s.id)
12953 .collect();
12954 state.groups.retain_mut(|group| {
12955 // selections might get merged above so we remove invalid items from stacks
12956 group.stack.retain(|id| final_selection_ids.contains(id));
12957
12958 // single selection in stack can be treated as initial state
12959 group.stack.len() > 1
12960 });
12961
12962 if !state.groups.is_empty() {
12963 self.add_selections_state = Some(state);
12964 }
12965 }
12966
12967 fn select_match_ranges(
12968 &mut self,
12969 range: Range<usize>,
12970 reversed: bool,
12971 replace_newest: bool,
12972 auto_scroll: Option<Autoscroll>,
12973 window: &mut Window,
12974 cx: &mut Context<Editor>,
12975 ) {
12976 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12977 self.change_selections(auto_scroll, window, cx, |s| {
12978 if replace_newest {
12979 s.delete(s.newest_anchor().id);
12980 }
12981 if reversed {
12982 s.insert_range(range.end..range.start);
12983 } else {
12984 s.insert_range(range);
12985 }
12986 });
12987 }
12988
12989 pub fn select_next_match_internal(
12990 &mut self,
12991 display_map: &DisplaySnapshot,
12992 replace_newest: bool,
12993 autoscroll: Option<Autoscroll>,
12994 window: &mut Window,
12995 cx: &mut Context<Self>,
12996 ) -> Result<()> {
12997 let buffer = &display_map.buffer_snapshot;
12998 let mut selections = self.selections.all::<usize>(cx);
12999 if let Some(mut select_next_state) = self.select_next_state.take() {
13000 let query = &select_next_state.query;
13001 if !select_next_state.done {
13002 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13003 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13004 let mut next_selected_range = None;
13005
13006 let bytes_after_last_selection =
13007 buffer.bytes_in_range(last_selection.end..buffer.len());
13008 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13009 let query_matches = query
13010 .stream_find_iter(bytes_after_last_selection)
13011 .map(|result| (last_selection.end, result))
13012 .chain(
13013 query
13014 .stream_find_iter(bytes_before_first_selection)
13015 .map(|result| (0, result)),
13016 );
13017
13018 for (start_offset, query_match) in query_matches {
13019 let query_match = query_match.unwrap(); // can only fail due to I/O
13020 let offset_range =
13021 start_offset + query_match.start()..start_offset + query_match.end();
13022 let display_range = offset_range.start.to_display_point(display_map)
13023 ..offset_range.end.to_display_point(display_map);
13024
13025 if !select_next_state.wordwise
13026 || (!movement::is_inside_word(display_map, display_range.start)
13027 && !movement::is_inside_word(display_map, display_range.end))
13028 {
13029 // TODO: This is n^2, because we might check all the selections
13030 if !selections
13031 .iter()
13032 .any(|selection| selection.range().overlaps(&offset_range))
13033 {
13034 next_selected_range = Some(offset_range);
13035 break;
13036 }
13037 }
13038 }
13039
13040 if let Some(next_selected_range) = next_selected_range {
13041 self.select_match_ranges(
13042 next_selected_range,
13043 last_selection.reversed,
13044 replace_newest,
13045 autoscroll,
13046 window,
13047 cx,
13048 );
13049 } else {
13050 select_next_state.done = true;
13051 }
13052 }
13053
13054 self.select_next_state = Some(select_next_state);
13055 } else {
13056 let mut only_carets = true;
13057 let mut same_text_selected = true;
13058 let mut selected_text = None;
13059
13060 let mut selections_iter = selections.iter().peekable();
13061 while let Some(selection) = selections_iter.next() {
13062 if selection.start != selection.end {
13063 only_carets = false;
13064 }
13065
13066 if same_text_selected {
13067 if selected_text.is_none() {
13068 selected_text =
13069 Some(buffer.text_for_range(selection.range()).collect::<String>());
13070 }
13071
13072 if let Some(next_selection) = selections_iter.peek() {
13073 if next_selection.range().len() == selection.range().len() {
13074 let next_selected_text = buffer
13075 .text_for_range(next_selection.range())
13076 .collect::<String>();
13077 if Some(next_selected_text) != selected_text {
13078 same_text_selected = false;
13079 selected_text = None;
13080 }
13081 } else {
13082 same_text_selected = false;
13083 selected_text = None;
13084 }
13085 }
13086 }
13087 }
13088
13089 if only_carets {
13090 for selection in &mut selections {
13091 let word_range = movement::surrounding_word(
13092 display_map,
13093 selection.start.to_display_point(display_map),
13094 );
13095 selection.start = word_range.start.to_offset(display_map, Bias::Left);
13096 selection.end = word_range.end.to_offset(display_map, Bias::Left);
13097 selection.goal = SelectionGoal::None;
13098 selection.reversed = false;
13099 self.select_match_ranges(
13100 selection.start..selection.end,
13101 selection.reversed,
13102 replace_newest,
13103 autoscroll,
13104 window,
13105 cx,
13106 );
13107 }
13108
13109 if selections.len() == 1 {
13110 let selection = selections
13111 .last()
13112 .expect("ensured that there's only one selection");
13113 let query = buffer
13114 .text_for_range(selection.start..selection.end)
13115 .collect::<String>();
13116 let is_empty = query.is_empty();
13117 let select_state = SelectNextState {
13118 query: AhoCorasick::new(&[query])?,
13119 wordwise: true,
13120 done: is_empty,
13121 };
13122 self.select_next_state = Some(select_state);
13123 } else {
13124 self.select_next_state = None;
13125 }
13126 } else if let Some(selected_text) = selected_text {
13127 self.select_next_state = Some(SelectNextState {
13128 query: AhoCorasick::new(&[selected_text])?,
13129 wordwise: false,
13130 done: false,
13131 });
13132 self.select_next_match_internal(
13133 display_map,
13134 replace_newest,
13135 autoscroll,
13136 window,
13137 cx,
13138 )?;
13139 }
13140 }
13141 Ok(())
13142 }
13143
13144 pub fn select_all_matches(
13145 &mut self,
13146 _action: &SelectAllMatches,
13147 window: &mut Window,
13148 cx: &mut Context<Self>,
13149 ) -> Result<()> {
13150 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13151
13152 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13153
13154 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13155 let Some(select_next_state) = self.select_next_state.as_mut() else {
13156 return Ok(());
13157 };
13158 if select_next_state.done {
13159 return Ok(());
13160 }
13161
13162 let mut new_selections = Vec::new();
13163
13164 let reversed = self.selections.oldest::<usize>(cx).reversed;
13165 let buffer = &display_map.buffer_snapshot;
13166 let query_matches = select_next_state
13167 .query
13168 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13169
13170 for query_match in query_matches.into_iter() {
13171 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13172 let offset_range = if reversed {
13173 query_match.end()..query_match.start()
13174 } else {
13175 query_match.start()..query_match.end()
13176 };
13177 let display_range = offset_range.start.to_display_point(&display_map)
13178 ..offset_range.end.to_display_point(&display_map);
13179
13180 if !select_next_state.wordwise
13181 || (!movement::is_inside_word(&display_map, display_range.start)
13182 && !movement::is_inside_word(&display_map, display_range.end))
13183 {
13184 new_selections.push(offset_range.start..offset_range.end);
13185 }
13186 }
13187
13188 select_next_state.done = true;
13189 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13190 self.change_selections(None, window, cx, |selections| {
13191 selections.select_ranges(new_selections)
13192 });
13193
13194 Ok(())
13195 }
13196
13197 pub fn select_next(
13198 &mut self,
13199 action: &SelectNext,
13200 window: &mut Window,
13201 cx: &mut Context<Self>,
13202 ) -> Result<()> {
13203 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13204 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13205 self.select_next_match_internal(
13206 &display_map,
13207 action.replace_newest,
13208 Some(Autoscroll::newest()),
13209 window,
13210 cx,
13211 )?;
13212 Ok(())
13213 }
13214
13215 pub fn select_previous(
13216 &mut self,
13217 action: &SelectPrevious,
13218 window: &mut Window,
13219 cx: &mut Context<Self>,
13220 ) -> Result<()> {
13221 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13222 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13223 let buffer = &display_map.buffer_snapshot;
13224 let mut selections = self.selections.all::<usize>(cx);
13225 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13226 let query = &select_prev_state.query;
13227 if !select_prev_state.done {
13228 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13229 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13230 let mut next_selected_range = None;
13231 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13232 let bytes_before_last_selection =
13233 buffer.reversed_bytes_in_range(0..last_selection.start);
13234 let bytes_after_first_selection =
13235 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13236 let query_matches = query
13237 .stream_find_iter(bytes_before_last_selection)
13238 .map(|result| (last_selection.start, result))
13239 .chain(
13240 query
13241 .stream_find_iter(bytes_after_first_selection)
13242 .map(|result| (buffer.len(), result)),
13243 );
13244 for (end_offset, query_match) in query_matches {
13245 let query_match = query_match.unwrap(); // can only fail due to I/O
13246 let offset_range =
13247 end_offset - query_match.end()..end_offset - query_match.start();
13248 let display_range = offset_range.start.to_display_point(&display_map)
13249 ..offset_range.end.to_display_point(&display_map);
13250
13251 if !select_prev_state.wordwise
13252 || (!movement::is_inside_word(&display_map, display_range.start)
13253 && !movement::is_inside_word(&display_map, display_range.end))
13254 {
13255 next_selected_range = Some(offset_range);
13256 break;
13257 }
13258 }
13259
13260 if let Some(next_selected_range) = next_selected_range {
13261 self.select_match_ranges(
13262 next_selected_range,
13263 last_selection.reversed,
13264 action.replace_newest,
13265 Some(Autoscroll::newest()),
13266 window,
13267 cx,
13268 );
13269 } else {
13270 select_prev_state.done = true;
13271 }
13272 }
13273
13274 self.select_prev_state = Some(select_prev_state);
13275 } else {
13276 let mut only_carets = true;
13277 let mut same_text_selected = true;
13278 let mut selected_text = None;
13279
13280 let mut selections_iter = selections.iter().peekable();
13281 while let Some(selection) = selections_iter.next() {
13282 if selection.start != selection.end {
13283 only_carets = false;
13284 }
13285
13286 if same_text_selected {
13287 if selected_text.is_none() {
13288 selected_text =
13289 Some(buffer.text_for_range(selection.range()).collect::<String>());
13290 }
13291
13292 if let Some(next_selection) = selections_iter.peek() {
13293 if next_selection.range().len() == selection.range().len() {
13294 let next_selected_text = buffer
13295 .text_for_range(next_selection.range())
13296 .collect::<String>();
13297 if Some(next_selected_text) != selected_text {
13298 same_text_selected = false;
13299 selected_text = None;
13300 }
13301 } else {
13302 same_text_selected = false;
13303 selected_text = None;
13304 }
13305 }
13306 }
13307 }
13308
13309 if only_carets {
13310 for selection in &mut selections {
13311 let word_range = movement::surrounding_word(
13312 &display_map,
13313 selection.start.to_display_point(&display_map),
13314 );
13315 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
13316 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
13317 selection.goal = SelectionGoal::None;
13318 selection.reversed = false;
13319 self.select_match_ranges(
13320 selection.start..selection.end,
13321 selection.reversed,
13322 action.replace_newest,
13323 Some(Autoscroll::newest()),
13324 window,
13325 cx,
13326 );
13327 }
13328 if selections.len() == 1 {
13329 let selection = selections
13330 .last()
13331 .expect("ensured that there's only one selection");
13332 let query = buffer
13333 .text_for_range(selection.start..selection.end)
13334 .collect::<String>();
13335 let is_empty = query.is_empty();
13336 let select_state = SelectNextState {
13337 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13338 wordwise: true,
13339 done: is_empty,
13340 };
13341 self.select_prev_state = Some(select_state);
13342 } else {
13343 self.select_prev_state = None;
13344 }
13345 } else if let Some(selected_text) = selected_text {
13346 self.select_prev_state = Some(SelectNextState {
13347 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13348 wordwise: false,
13349 done: false,
13350 });
13351 self.select_previous(action, window, cx)?;
13352 }
13353 }
13354 Ok(())
13355 }
13356
13357 pub fn find_next_match(
13358 &mut self,
13359 _: &FindNextMatch,
13360 window: &mut Window,
13361 cx: &mut Context<Self>,
13362 ) -> Result<()> {
13363 let selections = self.selections.disjoint_anchors();
13364 match selections.first() {
13365 Some(first) if selections.len() >= 2 => {
13366 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13367 s.select_ranges([first.range()]);
13368 });
13369 }
13370 _ => self.select_next(
13371 &SelectNext {
13372 replace_newest: true,
13373 },
13374 window,
13375 cx,
13376 )?,
13377 }
13378 Ok(())
13379 }
13380
13381 pub fn find_previous_match(
13382 &mut self,
13383 _: &FindPreviousMatch,
13384 window: &mut Window,
13385 cx: &mut Context<Self>,
13386 ) -> Result<()> {
13387 let selections = self.selections.disjoint_anchors();
13388 match selections.last() {
13389 Some(last) if selections.len() >= 2 => {
13390 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13391 s.select_ranges([last.range()]);
13392 });
13393 }
13394 _ => self.select_previous(
13395 &SelectPrevious {
13396 replace_newest: true,
13397 },
13398 window,
13399 cx,
13400 )?,
13401 }
13402 Ok(())
13403 }
13404
13405 pub fn toggle_comments(
13406 &mut self,
13407 action: &ToggleComments,
13408 window: &mut Window,
13409 cx: &mut Context<Self>,
13410 ) {
13411 if self.read_only(cx) {
13412 return;
13413 }
13414 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13415 let text_layout_details = &self.text_layout_details(window);
13416 self.transact(window, cx, |this, window, cx| {
13417 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13418 let mut edits = Vec::new();
13419 let mut selection_edit_ranges = Vec::new();
13420 let mut last_toggled_row = None;
13421 let snapshot = this.buffer.read(cx).read(cx);
13422 let empty_str: Arc<str> = Arc::default();
13423 let mut suffixes_inserted = Vec::new();
13424 let ignore_indent = action.ignore_indent;
13425
13426 fn comment_prefix_range(
13427 snapshot: &MultiBufferSnapshot,
13428 row: MultiBufferRow,
13429 comment_prefix: &str,
13430 comment_prefix_whitespace: &str,
13431 ignore_indent: bool,
13432 ) -> Range<Point> {
13433 let indent_size = if ignore_indent {
13434 0
13435 } else {
13436 snapshot.indent_size_for_line(row).len
13437 };
13438
13439 let start = Point::new(row.0, indent_size);
13440
13441 let mut line_bytes = snapshot
13442 .bytes_in_range(start..snapshot.max_point())
13443 .flatten()
13444 .copied();
13445
13446 // If this line currently begins with the line comment prefix, then record
13447 // the range containing the prefix.
13448 if line_bytes
13449 .by_ref()
13450 .take(comment_prefix.len())
13451 .eq(comment_prefix.bytes())
13452 {
13453 // Include any whitespace that matches the comment prefix.
13454 let matching_whitespace_len = line_bytes
13455 .zip(comment_prefix_whitespace.bytes())
13456 .take_while(|(a, b)| a == b)
13457 .count() as u32;
13458 let end = Point::new(
13459 start.row,
13460 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13461 );
13462 start..end
13463 } else {
13464 start..start
13465 }
13466 }
13467
13468 fn comment_suffix_range(
13469 snapshot: &MultiBufferSnapshot,
13470 row: MultiBufferRow,
13471 comment_suffix: &str,
13472 comment_suffix_has_leading_space: bool,
13473 ) -> Range<Point> {
13474 let end = Point::new(row.0, snapshot.line_len(row));
13475 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13476
13477 let mut line_end_bytes = snapshot
13478 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13479 .flatten()
13480 .copied();
13481
13482 let leading_space_len = if suffix_start_column > 0
13483 && line_end_bytes.next() == Some(b' ')
13484 && comment_suffix_has_leading_space
13485 {
13486 1
13487 } else {
13488 0
13489 };
13490
13491 // If this line currently begins with the line comment prefix, then record
13492 // the range containing the prefix.
13493 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13494 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13495 start..end
13496 } else {
13497 end..end
13498 }
13499 }
13500
13501 // TODO: Handle selections that cross excerpts
13502 for selection in &mut selections {
13503 let start_column = snapshot
13504 .indent_size_for_line(MultiBufferRow(selection.start.row))
13505 .len;
13506 let language = if let Some(language) =
13507 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13508 {
13509 language
13510 } else {
13511 continue;
13512 };
13513
13514 selection_edit_ranges.clear();
13515
13516 // If multiple selections contain a given row, avoid processing that
13517 // row more than once.
13518 let mut start_row = MultiBufferRow(selection.start.row);
13519 if last_toggled_row == Some(start_row) {
13520 start_row = start_row.next_row();
13521 }
13522 let end_row =
13523 if selection.end.row > selection.start.row && selection.end.column == 0 {
13524 MultiBufferRow(selection.end.row - 1)
13525 } else {
13526 MultiBufferRow(selection.end.row)
13527 };
13528 last_toggled_row = Some(end_row);
13529
13530 if start_row > end_row {
13531 continue;
13532 }
13533
13534 // If the language has line comments, toggle those.
13535 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13536
13537 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13538 if ignore_indent {
13539 full_comment_prefixes = full_comment_prefixes
13540 .into_iter()
13541 .map(|s| Arc::from(s.trim_end()))
13542 .collect();
13543 }
13544
13545 if !full_comment_prefixes.is_empty() {
13546 let first_prefix = full_comment_prefixes
13547 .first()
13548 .expect("prefixes is non-empty");
13549 let prefix_trimmed_lengths = full_comment_prefixes
13550 .iter()
13551 .map(|p| p.trim_end_matches(' ').len())
13552 .collect::<SmallVec<[usize; 4]>>();
13553
13554 let mut all_selection_lines_are_comments = true;
13555
13556 for row in start_row.0..=end_row.0 {
13557 let row = MultiBufferRow(row);
13558 if start_row < end_row && snapshot.is_line_blank(row) {
13559 continue;
13560 }
13561
13562 let prefix_range = full_comment_prefixes
13563 .iter()
13564 .zip(prefix_trimmed_lengths.iter().copied())
13565 .map(|(prefix, trimmed_prefix_len)| {
13566 comment_prefix_range(
13567 snapshot.deref(),
13568 row,
13569 &prefix[..trimmed_prefix_len],
13570 &prefix[trimmed_prefix_len..],
13571 ignore_indent,
13572 )
13573 })
13574 .max_by_key(|range| range.end.column - range.start.column)
13575 .expect("prefixes is non-empty");
13576
13577 if prefix_range.is_empty() {
13578 all_selection_lines_are_comments = false;
13579 }
13580
13581 selection_edit_ranges.push(prefix_range);
13582 }
13583
13584 if all_selection_lines_are_comments {
13585 edits.extend(
13586 selection_edit_ranges
13587 .iter()
13588 .cloned()
13589 .map(|range| (range, empty_str.clone())),
13590 );
13591 } else {
13592 let min_column = selection_edit_ranges
13593 .iter()
13594 .map(|range| range.start.column)
13595 .min()
13596 .unwrap_or(0);
13597 edits.extend(selection_edit_ranges.iter().map(|range| {
13598 let position = Point::new(range.start.row, min_column);
13599 (position..position, first_prefix.clone())
13600 }));
13601 }
13602 } else if let Some((full_comment_prefix, comment_suffix)) =
13603 language.block_comment_delimiters()
13604 {
13605 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13606 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13607 let prefix_range = comment_prefix_range(
13608 snapshot.deref(),
13609 start_row,
13610 comment_prefix,
13611 comment_prefix_whitespace,
13612 ignore_indent,
13613 );
13614 let suffix_range = comment_suffix_range(
13615 snapshot.deref(),
13616 end_row,
13617 comment_suffix.trim_start_matches(' '),
13618 comment_suffix.starts_with(' '),
13619 );
13620
13621 if prefix_range.is_empty() || suffix_range.is_empty() {
13622 edits.push((
13623 prefix_range.start..prefix_range.start,
13624 full_comment_prefix.clone(),
13625 ));
13626 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13627 suffixes_inserted.push((end_row, comment_suffix.len()));
13628 } else {
13629 edits.push((prefix_range, empty_str.clone()));
13630 edits.push((suffix_range, empty_str.clone()));
13631 }
13632 } else {
13633 continue;
13634 }
13635 }
13636
13637 drop(snapshot);
13638 this.buffer.update(cx, |buffer, cx| {
13639 buffer.edit(edits, None, cx);
13640 });
13641
13642 // Adjust selections so that they end before any comment suffixes that
13643 // were inserted.
13644 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13645 let mut selections = this.selections.all::<Point>(cx);
13646 let snapshot = this.buffer.read(cx).read(cx);
13647 for selection in &mut selections {
13648 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13649 match row.cmp(&MultiBufferRow(selection.end.row)) {
13650 Ordering::Less => {
13651 suffixes_inserted.next();
13652 continue;
13653 }
13654 Ordering::Greater => break,
13655 Ordering::Equal => {
13656 if selection.end.column == snapshot.line_len(row) {
13657 if selection.is_empty() {
13658 selection.start.column -= suffix_len as u32;
13659 }
13660 selection.end.column -= suffix_len as u32;
13661 }
13662 break;
13663 }
13664 }
13665 }
13666 }
13667
13668 drop(snapshot);
13669 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13670 s.select(selections)
13671 });
13672
13673 let selections = this.selections.all::<Point>(cx);
13674 let selections_on_single_row = selections.windows(2).all(|selections| {
13675 selections[0].start.row == selections[1].start.row
13676 && selections[0].end.row == selections[1].end.row
13677 && selections[0].start.row == selections[0].end.row
13678 });
13679 let selections_selecting = selections
13680 .iter()
13681 .any(|selection| selection.start != selection.end);
13682 let advance_downwards = action.advance_downwards
13683 && selections_on_single_row
13684 && !selections_selecting
13685 && !matches!(this.mode, EditorMode::SingleLine { .. });
13686
13687 if advance_downwards {
13688 let snapshot = this.buffer.read(cx).snapshot(cx);
13689
13690 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13691 s.move_cursors_with(|display_snapshot, display_point, _| {
13692 let mut point = display_point.to_point(display_snapshot);
13693 point.row += 1;
13694 point = snapshot.clip_point(point, Bias::Left);
13695 let display_point = point.to_display_point(display_snapshot);
13696 let goal = SelectionGoal::HorizontalPosition(
13697 display_snapshot
13698 .x_for_display_point(display_point, text_layout_details)
13699 .into(),
13700 );
13701 (display_point, goal)
13702 })
13703 });
13704 }
13705 });
13706 }
13707
13708 pub fn select_enclosing_symbol(
13709 &mut self,
13710 _: &SelectEnclosingSymbol,
13711 window: &mut Window,
13712 cx: &mut Context<Self>,
13713 ) {
13714 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13715
13716 let buffer = self.buffer.read(cx).snapshot(cx);
13717 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13718
13719 fn update_selection(
13720 selection: &Selection<usize>,
13721 buffer_snap: &MultiBufferSnapshot,
13722 ) -> Option<Selection<usize>> {
13723 let cursor = selection.head();
13724 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13725 for symbol in symbols.iter().rev() {
13726 let start = symbol.range.start.to_offset(buffer_snap);
13727 let end = symbol.range.end.to_offset(buffer_snap);
13728 let new_range = start..end;
13729 if start < selection.start || end > selection.end {
13730 return Some(Selection {
13731 id: selection.id,
13732 start: new_range.start,
13733 end: new_range.end,
13734 goal: SelectionGoal::None,
13735 reversed: selection.reversed,
13736 });
13737 }
13738 }
13739 None
13740 }
13741
13742 let mut selected_larger_symbol = false;
13743 let new_selections = old_selections
13744 .iter()
13745 .map(|selection| match update_selection(selection, &buffer) {
13746 Some(new_selection) => {
13747 if new_selection.range() != selection.range() {
13748 selected_larger_symbol = true;
13749 }
13750 new_selection
13751 }
13752 None => selection.clone(),
13753 })
13754 .collect::<Vec<_>>();
13755
13756 if selected_larger_symbol {
13757 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13758 s.select(new_selections);
13759 });
13760 }
13761 }
13762
13763 pub fn select_larger_syntax_node(
13764 &mut self,
13765 _: &SelectLargerSyntaxNode,
13766 window: &mut Window,
13767 cx: &mut Context<Self>,
13768 ) {
13769 let Some(visible_row_count) = self.visible_row_count() else {
13770 return;
13771 };
13772 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13773 if old_selections.is_empty() {
13774 return;
13775 }
13776
13777 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13778
13779 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13780 let buffer = self.buffer.read(cx).snapshot(cx);
13781
13782 let mut selected_larger_node = false;
13783 let mut new_selections = old_selections
13784 .iter()
13785 .map(|selection| {
13786 let old_range = selection.start..selection.end;
13787
13788 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13789 // manually select word at selection
13790 if ["string_content", "inline"].contains(&node.kind()) {
13791 let word_range = {
13792 let display_point = buffer
13793 .offset_to_point(old_range.start)
13794 .to_display_point(&display_map);
13795 let Range { start, end } =
13796 movement::surrounding_word(&display_map, display_point);
13797 start.to_point(&display_map).to_offset(&buffer)
13798 ..end.to_point(&display_map).to_offset(&buffer)
13799 };
13800 // ignore if word is already selected
13801 if !word_range.is_empty() && old_range != word_range {
13802 let last_word_range = {
13803 let display_point = buffer
13804 .offset_to_point(old_range.end)
13805 .to_display_point(&display_map);
13806 let Range { start, end } =
13807 movement::surrounding_word(&display_map, display_point);
13808 start.to_point(&display_map).to_offset(&buffer)
13809 ..end.to_point(&display_map).to_offset(&buffer)
13810 };
13811 // only select word if start and end point belongs to same word
13812 if word_range == last_word_range {
13813 selected_larger_node = true;
13814 return Selection {
13815 id: selection.id,
13816 start: word_range.start,
13817 end: word_range.end,
13818 goal: SelectionGoal::None,
13819 reversed: selection.reversed,
13820 };
13821 }
13822 }
13823 }
13824 }
13825
13826 let mut new_range = old_range.clone();
13827 while let Some((_node, containing_range)) =
13828 buffer.syntax_ancestor(new_range.clone())
13829 {
13830 new_range = match containing_range {
13831 MultiOrSingleBufferOffsetRange::Single(_) => break,
13832 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13833 };
13834 if !display_map.intersects_fold(new_range.start)
13835 && !display_map.intersects_fold(new_range.end)
13836 {
13837 break;
13838 }
13839 }
13840
13841 selected_larger_node |= new_range != old_range;
13842 Selection {
13843 id: selection.id,
13844 start: new_range.start,
13845 end: new_range.end,
13846 goal: SelectionGoal::None,
13847 reversed: selection.reversed,
13848 }
13849 })
13850 .collect::<Vec<_>>();
13851
13852 if !selected_larger_node {
13853 return; // don't put this call in the history
13854 }
13855
13856 // scroll based on transformation done to the last selection created by the user
13857 let (last_old, last_new) = old_selections
13858 .last()
13859 .zip(new_selections.last().cloned())
13860 .expect("old_selections isn't empty");
13861
13862 // revert selection
13863 let is_selection_reversed = {
13864 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13865 new_selections.last_mut().expect("checked above").reversed =
13866 should_newest_selection_be_reversed;
13867 should_newest_selection_be_reversed
13868 };
13869
13870 if selected_larger_node {
13871 self.select_syntax_node_history.disable_clearing = true;
13872 self.change_selections(None, window, cx, |s| {
13873 s.select(new_selections.clone());
13874 });
13875 self.select_syntax_node_history.disable_clearing = false;
13876 }
13877
13878 let start_row = last_new.start.to_display_point(&display_map).row().0;
13879 let end_row = last_new.end.to_display_point(&display_map).row().0;
13880 let selection_height = end_row - start_row + 1;
13881 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13882
13883 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13884 let scroll_behavior = if fits_on_the_screen {
13885 self.request_autoscroll(Autoscroll::fit(), cx);
13886 SelectSyntaxNodeScrollBehavior::FitSelection
13887 } else if is_selection_reversed {
13888 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13889 SelectSyntaxNodeScrollBehavior::CursorTop
13890 } else {
13891 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13892 SelectSyntaxNodeScrollBehavior::CursorBottom
13893 };
13894
13895 self.select_syntax_node_history.push((
13896 old_selections,
13897 scroll_behavior,
13898 is_selection_reversed,
13899 ));
13900 }
13901
13902 pub fn select_smaller_syntax_node(
13903 &mut self,
13904 _: &SelectSmallerSyntaxNode,
13905 window: &mut Window,
13906 cx: &mut Context<Self>,
13907 ) {
13908 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13909
13910 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13911 self.select_syntax_node_history.pop()
13912 {
13913 if let Some(selection) = selections.last_mut() {
13914 selection.reversed = is_selection_reversed;
13915 }
13916
13917 self.select_syntax_node_history.disable_clearing = true;
13918 self.change_selections(None, window, cx, |s| {
13919 s.select(selections.to_vec());
13920 });
13921 self.select_syntax_node_history.disable_clearing = false;
13922
13923 match scroll_behavior {
13924 SelectSyntaxNodeScrollBehavior::CursorTop => {
13925 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13926 }
13927 SelectSyntaxNodeScrollBehavior::FitSelection => {
13928 self.request_autoscroll(Autoscroll::fit(), cx);
13929 }
13930 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13931 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13932 }
13933 }
13934 }
13935 }
13936
13937 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13938 if !EditorSettings::get_global(cx).gutter.runnables {
13939 self.clear_tasks();
13940 return Task::ready(());
13941 }
13942 let project = self.project.as_ref().map(Entity::downgrade);
13943 let task_sources = self.lsp_task_sources(cx);
13944 let multi_buffer = self.buffer.downgrade();
13945 cx.spawn_in(window, async move |editor, cx| {
13946 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13947 let Some(project) = project.and_then(|p| p.upgrade()) else {
13948 return;
13949 };
13950 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13951 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13952 }) else {
13953 return;
13954 };
13955
13956 let hide_runnables = project
13957 .update(cx, |project, cx| {
13958 // Do not display any test indicators in non-dev server remote projects.
13959 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13960 })
13961 .unwrap_or(true);
13962 if hide_runnables {
13963 return;
13964 }
13965 let new_rows =
13966 cx.background_spawn({
13967 let snapshot = display_snapshot.clone();
13968 async move {
13969 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13970 }
13971 })
13972 .await;
13973 let Ok(lsp_tasks) =
13974 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13975 else {
13976 return;
13977 };
13978 let lsp_tasks = lsp_tasks.await;
13979
13980 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13981 lsp_tasks
13982 .into_iter()
13983 .flat_map(|(kind, tasks)| {
13984 tasks.into_iter().filter_map(move |(location, task)| {
13985 Some((kind.clone(), location?, task))
13986 })
13987 })
13988 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13989 let buffer = location.target.buffer;
13990 let buffer_snapshot = buffer.read(cx).snapshot();
13991 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13992 |(excerpt_id, snapshot, _)| {
13993 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13994 display_snapshot
13995 .buffer_snapshot
13996 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13997 } else {
13998 None
13999 }
14000 },
14001 );
14002 if let Some(offset) = offset {
14003 let task_buffer_range =
14004 location.target.range.to_point(&buffer_snapshot);
14005 let context_buffer_range =
14006 task_buffer_range.to_offset(&buffer_snapshot);
14007 let context_range = BufferOffset(context_buffer_range.start)
14008 ..BufferOffset(context_buffer_range.end);
14009
14010 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14011 .or_insert_with(|| RunnableTasks {
14012 templates: Vec::new(),
14013 offset,
14014 column: task_buffer_range.start.column,
14015 extra_variables: HashMap::default(),
14016 context_range,
14017 })
14018 .templates
14019 .push((kind, task.original_task().clone()));
14020 }
14021
14022 acc
14023 })
14024 }) else {
14025 return;
14026 };
14027
14028 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14029 buffer.language_settings(cx).tasks.prefer_lsp
14030 }) else {
14031 return;
14032 };
14033
14034 let rows = Self::runnable_rows(
14035 project,
14036 display_snapshot,
14037 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14038 new_rows,
14039 cx.clone(),
14040 )
14041 .await;
14042 editor
14043 .update(cx, |editor, _| {
14044 editor.clear_tasks();
14045 for (key, mut value) in rows {
14046 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14047 value.templates.extend(lsp_tasks.templates);
14048 }
14049
14050 editor.insert_tasks(key, value);
14051 }
14052 for (key, value) in lsp_tasks_by_rows {
14053 editor.insert_tasks(key, value);
14054 }
14055 })
14056 .ok();
14057 })
14058 }
14059 fn fetch_runnable_ranges(
14060 snapshot: &DisplaySnapshot,
14061 range: Range<Anchor>,
14062 ) -> Vec<language::RunnableRange> {
14063 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14064 }
14065
14066 fn runnable_rows(
14067 project: Entity<Project>,
14068 snapshot: DisplaySnapshot,
14069 prefer_lsp: bool,
14070 runnable_ranges: Vec<RunnableRange>,
14071 cx: AsyncWindowContext,
14072 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14073 cx.spawn(async move |cx| {
14074 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14075 for mut runnable in runnable_ranges {
14076 let Some(tasks) = cx
14077 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14078 .ok()
14079 else {
14080 continue;
14081 };
14082 let mut tasks = tasks.await;
14083
14084 if prefer_lsp {
14085 tasks.retain(|(task_kind, _)| {
14086 !matches!(task_kind, TaskSourceKind::Language { .. })
14087 });
14088 }
14089 if tasks.is_empty() {
14090 continue;
14091 }
14092
14093 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14094 let Some(row) = snapshot
14095 .buffer_snapshot
14096 .buffer_line_for_row(MultiBufferRow(point.row))
14097 .map(|(_, range)| range.start.row)
14098 else {
14099 continue;
14100 };
14101
14102 let context_range =
14103 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14104 runnable_rows.push((
14105 (runnable.buffer_id, row),
14106 RunnableTasks {
14107 templates: tasks,
14108 offset: snapshot
14109 .buffer_snapshot
14110 .anchor_before(runnable.run_range.start),
14111 context_range,
14112 column: point.column,
14113 extra_variables: runnable.extra_captures,
14114 },
14115 ));
14116 }
14117 runnable_rows
14118 })
14119 }
14120
14121 fn templates_with_tags(
14122 project: &Entity<Project>,
14123 runnable: &mut Runnable,
14124 cx: &mut App,
14125 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14126 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14127 let (worktree_id, file) = project
14128 .buffer_for_id(runnable.buffer, cx)
14129 .and_then(|buffer| buffer.read(cx).file())
14130 .map(|file| (file.worktree_id(cx), file.clone()))
14131 .unzip();
14132
14133 (
14134 project.task_store().read(cx).task_inventory().cloned(),
14135 worktree_id,
14136 file,
14137 )
14138 });
14139
14140 let tags = mem::take(&mut runnable.tags);
14141 let language = runnable.language.clone();
14142 cx.spawn(async move |cx| {
14143 let mut templates_with_tags = Vec::new();
14144 if let Some(inventory) = inventory {
14145 for RunnableTag(tag) in tags {
14146 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14147 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14148 }) else {
14149 return templates_with_tags;
14150 };
14151 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14152 move |(_, template)| {
14153 template.tags.iter().any(|source_tag| source_tag == &tag)
14154 },
14155 ));
14156 }
14157 }
14158 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14159
14160 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14161 // Strongest source wins; if we have worktree tag binding, prefer that to
14162 // global and language bindings;
14163 // if we have a global binding, prefer that to language binding.
14164 let first_mismatch = templates_with_tags
14165 .iter()
14166 .position(|(tag_source, _)| tag_source != leading_tag_source);
14167 if let Some(index) = first_mismatch {
14168 templates_with_tags.truncate(index);
14169 }
14170 }
14171
14172 templates_with_tags
14173 })
14174 }
14175
14176 pub fn move_to_enclosing_bracket(
14177 &mut self,
14178 _: &MoveToEnclosingBracket,
14179 window: &mut Window,
14180 cx: &mut Context<Self>,
14181 ) {
14182 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14183 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14184 s.move_offsets_with(|snapshot, selection| {
14185 let Some(enclosing_bracket_ranges) =
14186 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14187 else {
14188 return;
14189 };
14190
14191 let mut best_length = usize::MAX;
14192 let mut best_inside = false;
14193 let mut best_in_bracket_range = false;
14194 let mut best_destination = None;
14195 for (open, close) in enclosing_bracket_ranges {
14196 let close = close.to_inclusive();
14197 let length = close.end() - open.start;
14198 let inside = selection.start >= open.end && selection.end <= *close.start();
14199 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14200 || close.contains(&selection.head());
14201
14202 // If best is next to a bracket and current isn't, skip
14203 if !in_bracket_range && best_in_bracket_range {
14204 continue;
14205 }
14206
14207 // Prefer smaller lengths unless best is inside and current isn't
14208 if length > best_length && (best_inside || !inside) {
14209 continue;
14210 }
14211
14212 best_length = length;
14213 best_inside = inside;
14214 best_in_bracket_range = in_bracket_range;
14215 best_destination = Some(
14216 if close.contains(&selection.start) && close.contains(&selection.end) {
14217 if inside { open.end } else { open.start }
14218 } else if inside {
14219 *close.start()
14220 } else {
14221 *close.end()
14222 },
14223 );
14224 }
14225
14226 if let Some(destination) = best_destination {
14227 selection.collapse_to(destination, SelectionGoal::None);
14228 }
14229 })
14230 });
14231 }
14232
14233 pub fn undo_selection(
14234 &mut self,
14235 _: &UndoSelection,
14236 window: &mut Window,
14237 cx: &mut Context<Self>,
14238 ) {
14239 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14240 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14241 self.selection_history.mode = SelectionHistoryMode::Undoing;
14242 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14243 this.end_selection(window, cx);
14244 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14245 s.select_anchors(entry.selections.to_vec())
14246 });
14247 });
14248 self.selection_history.mode = SelectionHistoryMode::Normal;
14249
14250 self.select_next_state = entry.select_next_state;
14251 self.select_prev_state = entry.select_prev_state;
14252 self.add_selections_state = entry.add_selections_state;
14253 }
14254 }
14255
14256 pub fn redo_selection(
14257 &mut self,
14258 _: &RedoSelection,
14259 window: &mut Window,
14260 cx: &mut Context<Self>,
14261 ) {
14262 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14263 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14264 self.selection_history.mode = SelectionHistoryMode::Redoing;
14265 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14266 this.end_selection(window, cx);
14267 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14268 s.select_anchors(entry.selections.to_vec())
14269 });
14270 });
14271 self.selection_history.mode = SelectionHistoryMode::Normal;
14272
14273 self.select_next_state = entry.select_next_state;
14274 self.select_prev_state = entry.select_prev_state;
14275 self.add_selections_state = entry.add_selections_state;
14276 }
14277 }
14278
14279 pub fn expand_excerpts(
14280 &mut self,
14281 action: &ExpandExcerpts,
14282 _: &mut Window,
14283 cx: &mut Context<Self>,
14284 ) {
14285 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14286 }
14287
14288 pub fn expand_excerpts_down(
14289 &mut self,
14290 action: &ExpandExcerptsDown,
14291 _: &mut Window,
14292 cx: &mut Context<Self>,
14293 ) {
14294 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14295 }
14296
14297 pub fn expand_excerpts_up(
14298 &mut self,
14299 action: &ExpandExcerptsUp,
14300 _: &mut Window,
14301 cx: &mut Context<Self>,
14302 ) {
14303 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14304 }
14305
14306 pub fn expand_excerpts_for_direction(
14307 &mut self,
14308 lines: u32,
14309 direction: ExpandExcerptDirection,
14310
14311 cx: &mut Context<Self>,
14312 ) {
14313 let selections = self.selections.disjoint_anchors();
14314
14315 let lines = if lines == 0 {
14316 EditorSettings::get_global(cx).expand_excerpt_lines
14317 } else {
14318 lines
14319 };
14320
14321 self.buffer.update(cx, |buffer, cx| {
14322 let snapshot = buffer.snapshot(cx);
14323 let mut excerpt_ids = selections
14324 .iter()
14325 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14326 .collect::<Vec<_>>();
14327 excerpt_ids.sort();
14328 excerpt_ids.dedup();
14329 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14330 })
14331 }
14332
14333 pub fn expand_excerpt(
14334 &mut self,
14335 excerpt: ExcerptId,
14336 direction: ExpandExcerptDirection,
14337 window: &mut Window,
14338 cx: &mut Context<Self>,
14339 ) {
14340 let current_scroll_position = self.scroll_position(cx);
14341 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14342 let mut should_scroll_up = false;
14343
14344 if direction == ExpandExcerptDirection::Down {
14345 let multi_buffer = self.buffer.read(cx);
14346 let snapshot = multi_buffer.snapshot(cx);
14347 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14348 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14349 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14350 let buffer_snapshot = buffer.read(cx).snapshot();
14351 let excerpt_end_row =
14352 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14353 let last_row = buffer_snapshot.max_point().row;
14354 let lines_below = last_row.saturating_sub(excerpt_end_row);
14355 should_scroll_up = lines_below >= lines_to_expand;
14356 }
14357 }
14358 }
14359 }
14360
14361 self.buffer.update(cx, |buffer, cx| {
14362 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14363 });
14364
14365 if should_scroll_up {
14366 let new_scroll_position =
14367 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14368 self.set_scroll_position(new_scroll_position, window, cx);
14369 }
14370 }
14371
14372 pub fn go_to_singleton_buffer_point(
14373 &mut self,
14374 point: Point,
14375 window: &mut Window,
14376 cx: &mut Context<Self>,
14377 ) {
14378 self.go_to_singleton_buffer_range(point..point, window, cx);
14379 }
14380
14381 pub fn go_to_singleton_buffer_range(
14382 &mut self,
14383 range: Range<Point>,
14384 window: &mut Window,
14385 cx: &mut Context<Self>,
14386 ) {
14387 let multibuffer = self.buffer().read(cx);
14388 let Some(buffer) = multibuffer.as_singleton() else {
14389 return;
14390 };
14391 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14392 return;
14393 };
14394 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14395 return;
14396 };
14397 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
14398 s.select_anchor_ranges([start..end])
14399 });
14400 }
14401
14402 pub fn go_to_diagnostic(
14403 &mut self,
14404 _: &GoToDiagnostic,
14405 window: &mut Window,
14406 cx: &mut Context<Self>,
14407 ) {
14408 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14409 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14410 }
14411
14412 pub fn go_to_prev_diagnostic(
14413 &mut self,
14414 _: &GoToPreviousDiagnostic,
14415 window: &mut Window,
14416 cx: &mut Context<Self>,
14417 ) {
14418 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14419 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14420 }
14421
14422 pub fn go_to_diagnostic_impl(
14423 &mut self,
14424 direction: Direction,
14425 window: &mut Window,
14426 cx: &mut Context<Self>,
14427 ) {
14428 let buffer = self.buffer.read(cx).snapshot(cx);
14429 let selection = self.selections.newest::<usize>(cx);
14430
14431 let mut active_group_id = None;
14432 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14433 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14434 active_group_id = Some(active_group.group_id);
14435 }
14436 }
14437
14438 fn filtered(
14439 snapshot: EditorSnapshot,
14440 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14441 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14442 diagnostics
14443 .filter(|entry| entry.range.start != entry.range.end)
14444 .filter(|entry| !entry.diagnostic.is_unnecessary)
14445 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14446 }
14447
14448 let snapshot = self.snapshot(window, cx);
14449 let before = filtered(
14450 snapshot.clone(),
14451 buffer
14452 .diagnostics_in_range(0..selection.start)
14453 .filter(|entry| entry.range.start <= selection.start),
14454 );
14455 let after = filtered(
14456 snapshot,
14457 buffer
14458 .diagnostics_in_range(selection.start..buffer.len())
14459 .filter(|entry| entry.range.start >= selection.start),
14460 );
14461
14462 let mut found: Option<DiagnosticEntry<usize>> = None;
14463 if direction == Direction::Prev {
14464 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14465 {
14466 for diagnostic in prev_diagnostics.into_iter().rev() {
14467 if diagnostic.range.start != selection.start
14468 || active_group_id
14469 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14470 {
14471 found = Some(diagnostic);
14472 break 'outer;
14473 }
14474 }
14475 }
14476 } else {
14477 for diagnostic in after.chain(before) {
14478 if diagnostic.range.start != selection.start
14479 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14480 {
14481 found = Some(diagnostic);
14482 break;
14483 }
14484 }
14485 }
14486 let Some(next_diagnostic) = found else {
14487 return;
14488 };
14489
14490 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14491 return;
14492 };
14493 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14494 s.select_ranges(vec![
14495 next_diagnostic.range.start..next_diagnostic.range.start,
14496 ])
14497 });
14498 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14499 self.refresh_inline_completion(false, true, window, cx);
14500 }
14501
14502 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14503 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14504 let snapshot = self.snapshot(window, cx);
14505 let selection = self.selections.newest::<Point>(cx);
14506 self.go_to_hunk_before_or_after_position(
14507 &snapshot,
14508 selection.head(),
14509 Direction::Next,
14510 window,
14511 cx,
14512 );
14513 }
14514
14515 pub fn go_to_hunk_before_or_after_position(
14516 &mut self,
14517 snapshot: &EditorSnapshot,
14518 position: Point,
14519 direction: Direction,
14520 window: &mut Window,
14521 cx: &mut Context<Editor>,
14522 ) {
14523 let row = if direction == Direction::Next {
14524 self.hunk_after_position(snapshot, position)
14525 .map(|hunk| hunk.row_range.start)
14526 } else {
14527 self.hunk_before_position(snapshot, position)
14528 };
14529
14530 if let Some(row) = row {
14531 let destination = Point::new(row.0, 0);
14532 let autoscroll = Autoscroll::center();
14533
14534 self.unfold_ranges(&[destination..destination], false, false, cx);
14535 self.change_selections(Some(autoscroll), window, cx, |s| {
14536 s.select_ranges([destination..destination]);
14537 });
14538 }
14539 }
14540
14541 fn hunk_after_position(
14542 &mut self,
14543 snapshot: &EditorSnapshot,
14544 position: Point,
14545 ) -> Option<MultiBufferDiffHunk> {
14546 snapshot
14547 .buffer_snapshot
14548 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14549 .find(|hunk| hunk.row_range.start.0 > position.row)
14550 .or_else(|| {
14551 snapshot
14552 .buffer_snapshot
14553 .diff_hunks_in_range(Point::zero()..position)
14554 .find(|hunk| hunk.row_range.end.0 < position.row)
14555 })
14556 }
14557
14558 fn go_to_prev_hunk(
14559 &mut self,
14560 _: &GoToPreviousHunk,
14561 window: &mut Window,
14562 cx: &mut Context<Self>,
14563 ) {
14564 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14565 let snapshot = self.snapshot(window, cx);
14566 let selection = self.selections.newest::<Point>(cx);
14567 self.go_to_hunk_before_or_after_position(
14568 &snapshot,
14569 selection.head(),
14570 Direction::Prev,
14571 window,
14572 cx,
14573 );
14574 }
14575
14576 fn hunk_before_position(
14577 &mut self,
14578 snapshot: &EditorSnapshot,
14579 position: Point,
14580 ) -> Option<MultiBufferRow> {
14581 snapshot
14582 .buffer_snapshot
14583 .diff_hunk_before(position)
14584 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14585 }
14586
14587 fn go_to_next_change(
14588 &mut self,
14589 _: &GoToNextChange,
14590 window: &mut Window,
14591 cx: &mut Context<Self>,
14592 ) {
14593 if let Some(selections) = self
14594 .change_list
14595 .next_change(1, Direction::Next)
14596 .map(|s| s.to_vec())
14597 {
14598 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14599 let map = s.display_map();
14600 s.select_display_ranges(selections.iter().map(|a| {
14601 let point = a.to_display_point(&map);
14602 point..point
14603 }))
14604 })
14605 }
14606 }
14607
14608 fn go_to_previous_change(
14609 &mut self,
14610 _: &GoToPreviousChange,
14611 window: &mut Window,
14612 cx: &mut Context<Self>,
14613 ) {
14614 if let Some(selections) = self
14615 .change_list
14616 .next_change(1, Direction::Prev)
14617 .map(|s| s.to_vec())
14618 {
14619 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14620 let map = s.display_map();
14621 s.select_display_ranges(selections.iter().map(|a| {
14622 let point = a.to_display_point(&map);
14623 point..point
14624 }))
14625 })
14626 }
14627 }
14628
14629 fn go_to_line<T: 'static>(
14630 &mut self,
14631 position: Anchor,
14632 highlight_color: Option<Hsla>,
14633 window: &mut Window,
14634 cx: &mut Context<Self>,
14635 ) {
14636 let snapshot = self.snapshot(window, cx).display_snapshot;
14637 let position = position.to_point(&snapshot.buffer_snapshot);
14638 let start = snapshot
14639 .buffer_snapshot
14640 .clip_point(Point::new(position.row, 0), Bias::Left);
14641 let end = start + Point::new(1, 0);
14642 let start = snapshot.buffer_snapshot.anchor_before(start);
14643 let end = snapshot.buffer_snapshot.anchor_before(end);
14644
14645 self.highlight_rows::<T>(
14646 start..end,
14647 highlight_color
14648 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14649 Default::default(),
14650 cx,
14651 );
14652
14653 if self.buffer.read(cx).is_singleton() {
14654 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14655 }
14656 }
14657
14658 pub fn go_to_definition(
14659 &mut self,
14660 _: &GoToDefinition,
14661 window: &mut Window,
14662 cx: &mut Context<Self>,
14663 ) -> Task<Result<Navigated>> {
14664 let definition =
14665 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14666 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14667 cx.spawn_in(window, async move |editor, cx| {
14668 if definition.await? == Navigated::Yes {
14669 return Ok(Navigated::Yes);
14670 }
14671 match fallback_strategy {
14672 GoToDefinitionFallback::None => Ok(Navigated::No),
14673 GoToDefinitionFallback::FindAllReferences => {
14674 match editor.update_in(cx, |editor, window, cx| {
14675 editor.find_all_references(&FindAllReferences, window, cx)
14676 })? {
14677 Some(references) => references.await,
14678 None => Ok(Navigated::No),
14679 }
14680 }
14681 }
14682 })
14683 }
14684
14685 pub fn go_to_declaration(
14686 &mut self,
14687 _: &GoToDeclaration,
14688 window: &mut Window,
14689 cx: &mut Context<Self>,
14690 ) -> Task<Result<Navigated>> {
14691 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14692 }
14693
14694 pub fn go_to_declaration_split(
14695 &mut self,
14696 _: &GoToDeclaration,
14697 window: &mut Window,
14698 cx: &mut Context<Self>,
14699 ) -> Task<Result<Navigated>> {
14700 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14701 }
14702
14703 pub fn go_to_implementation(
14704 &mut self,
14705 _: &GoToImplementation,
14706 window: &mut Window,
14707 cx: &mut Context<Self>,
14708 ) -> Task<Result<Navigated>> {
14709 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14710 }
14711
14712 pub fn go_to_implementation_split(
14713 &mut self,
14714 _: &GoToImplementationSplit,
14715 window: &mut Window,
14716 cx: &mut Context<Self>,
14717 ) -> Task<Result<Navigated>> {
14718 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14719 }
14720
14721 pub fn go_to_type_definition(
14722 &mut self,
14723 _: &GoToTypeDefinition,
14724 window: &mut Window,
14725 cx: &mut Context<Self>,
14726 ) -> Task<Result<Navigated>> {
14727 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14728 }
14729
14730 pub fn go_to_definition_split(
14731 &mut self,
14732 _: &GoToDefinitionSplit,
14733 window: &mut Window,
14734 cx: &mut Context<Self>,
14735 ) -> Task<Result<Navigated>> {
14736 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14737 }
14738
14739 pub fn go_to_type_definition_split(
14740 &mut self,
14741 _: &GoToTypeDefinitionSplit,
14742 window: &mut Window,
14743 cx: &mut Context<Self>,
14744 ) -> Task<Result<Navigated>> {
14745 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14746 }
14747
14748 fn go_to_definition_of_kind(
14749 &mut self,
14750 kind: GotoDefinitionKind,
14751 split: bool,
14752 window: &mut Window,
14753 cx: &mut Context<Self>,
14754 ) -> Task<Result<Navigated>> {
14755 let Some(provider) = self.semantics_provider.clone() else {
14756 return Task::ready(Ok(Navigated::No));
14757 };
14758 let head = self.selections.newest::<usize>(cx).head();
14759 let buffer = self.buffer.read(cx);
14760 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14761 text_anchor
14762 } else {
14763 return Task::ready(Ok(Navigated::No));
14764 };
14765
14766 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14767 return Task::ready(Ok(Navigated::No));
14768 };
14769
14770 cx.spawn_in(window, async move |editor, cx| {
14771 let definitions = definitions.await?;
14772 let navigated = editor
14773 .update_in(cx, |editor, window, cx| {
14774 editor.navigate_to_hover_links(
14775 Some(kind),
14776 definitions
14777 .into_iter()
14778 .filter(|location| {
14779 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14780 })
14781 .map(HoverLink::Text)
14782 .collect::<Vec<_>>(),
14783 split,
14784 window,
14785 cx,
14786 )
14787 })?
14788 .await?;
14789 anyhow::Ok(navigated)
14790 })
14791 }
14792
14793 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14794 let selection = self.selections.newest_anchor();
14795 let head = selection.head();
14796 let tail = selection.tail();
14797
14798 let Some((buffer, start_position)) =
14799 self.buffer.read(cx).text_anchor_for_position(head, cx)
14800 else {
14801 return;
14802 };
14803
14804 let end_position = if head != tail {
14805 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14806 return;
14807 };
14808 Some(pos)
14809 } else {
14810 None
14811 };
14812
14813 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14814 let url = if let Some(end_pos) = end_position {
14815 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14816 } else {
14817 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14818 };
14819
14820 if let Some(url) = url {
14821 editor.update(cx, |_, cx| {
14822 cx.open_url(&url);
14823 })
14824 } else {
14825 Ok(())
14826 }
14827 });
14828
14829 url_finder.detach();
14830 }
14831
14832 pub fn open_selected_filename(
14833 &mut self,
14834 _: &OpenSelectedFilename,
14835 window: &mut Window,
14836 cx: &mut Context<Self>,
14837 ) {
14838 let Some(workspace) = self.workspace() else {
14839 return;
14840 };
14841
14842 let position = self.selections.newest_anchor().head();
14843
14844 let Some((buffer, buffer_position)) =
14845 self.buffer.read(cx).text_anchor_for_position(position, cx)
14846 else {
14847 return;
14848 };
14849
14850 let project = self.project.clone();
14851
14852 cx.spawn_in(window, async move |_, cx| {
14853 let result = find_file(&buffer, project, buffer_position, cx).await;
14854
14855 if let Some((_, path)) = result {
14856 workspace
14857 .update_in(cx, |workspace, window, cx| {
14858 workspace.open_resolved_path(path, window, cx)
14859 })?
14860 .await?;
14861 }
14862 anyhow::Ok(())
14863 })
14864 .detach();
14865 }
14866
14867 pub(crate) fn navigate_to_hover_links(
14868 &mut self,
14869 kind: Option<GotoDefinitionKind>,
14870 mut definitions: Vec<HoverLink>,
14871 split: bool,
14872 window: &mut Window,
14873 cx: &mut Context<Editor>,
14874 ) -> Task<Result<Navigated>> {
14875 // If there is one definition, just open it directly
14876 if definitions.len() == 1 {
14877 let definition = definitions.pop().unwrap();
14878
14879 enum TargetTaskResult {
14880 Location(Option<Location>),
14881 AlreadyNavigated,
14882 }
14883
14884 let target_task = match definition {
14885 HoverLink::Text(link) => {
14886 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14887 }
14888 HoverLink::InlayHint(lsp_location, server_id) => {
14889 let computation =
14890 self.compute_target_location(lsp_location, server_id, window, cx);
14891 cx.background_spawn(async move {
14892 let location = computation.await?;
14893 Ok(TargetTaskResult::Location(location))
14894 })
14895 }
14896 HoverLink::Url(url) => {
14897 cx.open_url(&url);
14898 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14899 }
14900 HoverLink::File(path) => {
14901 if let Some(workspace) = self.workspace() {
14902 cx.spawn_in(window, async move |_, cx| {
14903 workspace
14904 .update_in(cx, |workspace, window, cx| {
14905 workspace.open_resolved_path(path, window, cx)
14906 })?
14907 .await
14908 .map(|_| TargetTaskResult::AlreadyNavigated)
14909 })
14910 } else {
14911 Task::ready(Ok(TargetTaskResult::Location(None)))
14912 }
14913 }
14914 };
14915 cx.spawn_in(window, async move |editor, cx| {
14916 let target = match target_task.await.context("target resolution task")? {
14917 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14918 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14919 TargetTaskResult::Location(Some(target)) => target,
14920 };
14921
14922 editor.update_in(cx, |editor, window, cx| {
14923 let Some(workspace) = editor.workspace() else {
14924 return Navigated::No;
14925 };
14926 let pane = workspace.read(cx).active_pane().clone();
14927
14928 let range = target.range.to_point(target.buffer.read(cx));
14929 let range = editor.range_for_match(&range);
14930 let range = collapse_multiline_range(range);
14931
14932 if !split
14933 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14934 {
14935 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14936 } else {
14937 window.defer(cx, move |window, cx| {
14938 let target_editor: Entity<Self> =
14939 workspace.update(cx, |workspace, cx| {
14940 let pane = if split {
14941 workspace.adjacent_pane(window, cx)
14942 } else {
14943 workspace.active_pane().clone()
14944 };
14945
14946 workspace.open_project_item(
14947 pane,
14948 target.buffer.clone(),
14949 true,
14950 true,
14951 window,
14952 cx,
14953 )
14954 });
14955 target_editor.update(cx, |target_editor, cx| {
14956 // When selecting a definition in a different buffer, disable the nav history
14957 // to avoid creating a history entry at the previous cursor location.
14958 pane.update(cx, |pane, _| pane.disable_history());
14959 target_editor.go_to_singleton_buffer_range(range, window, cx);
14960 pane.update(cx, |pane, _| pane.enable_history());
14961 });
14962 });
14963 }
14964 Navigated::Yes
14965 })
14966 })
14967 } else if !definitions.is_empty() {
14968 cx.spawn_in(window, async move |editor, cx| {
14969 let (title, location_tasks, workspace) = editor
14970 .update_in(cx, |editor, window, cx| {
14971 let tab_kind = match kind {
14972 Some(GotoDefinitionKind::Implementation) => "Implementations",
14973 _ => "Definitions",
14974 };
14975 let title = definitions
14976 .iter()
14977 .find_map(|definition| match definition {
14978 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14979 let buffer = origin.buffer.read(cx);
14980 format!(
14981 "{} for {}",
14982 tab_kind,
14983 buffer
14984 .text_for_range(origin.range.clone())
14985 .collect::<String>()
14986 )
14987 }),
14988 HoverLink::InlayHint(_, _) => None,
14989 HoverLink::Url(_) => None,
14990 HoverLink::File(_) => None,
14991 })
14992 .unwrap_or(tab_kind.to_string());
14993 let location_tasks = definitions
14994 .into_iter()
14995 .map(|definition| match definition {
14996 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14997 HoverLink::InlayHint(lsp_location, server_id) => editor
14998 .compute_target_location(lsp_location, server_id, window, cx),
14999 HoverLink::Url(_) => Task::ready(Ok(None)),
15000 HoverLink::File(_) => Task::ready(Ok(None)),
15001 })
15002 .collect::<Vec<_>>();
15003 (title, location_tasks, editor.workspace().clone())
15004 })
15005 .context("location tasks preparation")?;
15006
15007 let locations = future::join_all(location_tasks)
15008 .await
15009 .into_iter()
15010 .filter_map(|location| location.transpose())
15011 .collect::<Result<_>>()
15012 .context("location tasks")?;
15013
15014 let Some(workspace) = workspace else {
15015 return Ok(Navigated::No);
15016 };
15017 let opened = workspace
15018 .update_in(cx, |workspace, window, cx| {
15019 Self::open_locations_in_multibuffer(
15020 workspace,
15021 locations,
15022 title,
15023 split,
15024 MultibufferSelectionMode::First,
15025 window,
15026 cx,
15027 )
15028 })
15029 .ok();
15030
15031 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15032 })
15033 } else {
15034 Task::ready(Ok(Navigated::No))
15035 }
15036 }
15037
15038 fn compute_target_location(
15039 &self,
15040 lsp_location: lsp::Location,
15041 server_id: LanguageServerId,
15042 window: &mut Window,
15043 cx: &mut Context<Self>,
15044 ) -> Task<anyhow::Result<Option<Location>>> {
15045 let Some(project) = self.project.clone() else {
15046 return Task::ready(Ok(None));
15047 };
15048
15049 cx.spawn_in(window, async move |editor, cx| {
15050 let location_task = editor.update(cx, |_, cx| {
15051 project.update(cx, |project, cx| {
15052 let language_server_name = project
15053 .language_server_statuses(cx)
15054 .find(|(id, _)| server_id == *id)
15055 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15056 language_server_name.map(|language_server_name| {
15057 project.open_local_buffer_via_lsp(
15058 lsp_location.uri.clone(),
15059 server_id,
15060 language_server_name,
15061 cx,
15062 )
15063 })
15064 })
15065 })?;
15066 let location = match location_task {
15067 Some(task) => Some({
15068 let target_buffer_handle = task.await.context("open local buffer")?;
15069 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15070 let target_start = target_buffer
15071 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15072 let target_end = target_buffer
15073 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15074 target_buffer.anchor_after(target_start)
15075 ..target_buffer.anchor_before(target_end)
15076 })?;
15077 Location {
15078 buffer: target_buffer_handle,
15079 range,
15080 }
15081 }),
15082 None => None,
15083 };
15084 Ok(location)
15085 })
15086 }
15087
15088 pub fn find_all_references(
15089 &mut self,
15090 _: &FindAllReferences,
15091 window: &mut Window,
15092 cx: &mut Context<Self>,
15093 ) -> Option<Task<Result<Navigated>>> {
15094 let selection = self.selections.newest::<usize>(cx);
15095 let multi_buffer = self.buffer.read(cx);
15096 let head = selection.head();
15097
15098 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15099 let head_anchor = multi_buffer_snapshot.anchor_at(
15100 head,
15101 if head < selection.tail() {
15102 Bias::Right
15103 } else {
15104 Bias::Left
15105 },
15106 );
15107
15108 match self
15109 .find_all_references_task_sources
15110 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15111 {
15112 Ok(_) => {
15113 log::info!(
15114 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15115 );
15116 return None;
15117 }
15118 Err(i) => {
15119 self.find_all_references_task_sources.insert(i, head_anchor);
15120 }
15121 }
15122
15123 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15124 let workspace = self.workspace()?;
15125 let project = workspace.read(cx).project().clone();
15126 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15127 Some(cx.spawn_in(window, async move |editor, cx| {
15128 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15129 if let Ok(i) = editor
15130 .find_all_references_task_sources
15131 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15132 {
15133 editor.find_all_references_task_sources.remove(i);
15134 }
15135 });
15136
15137 let locations = references.await?;
15138 if locations.is_empty() {
15139 return anyhow::Ok(Navigated::No);
15140 }
15141
15142 workspace.update_in(cx, |workspace, window, cx| {
15143 let title = locations
15144 .first()
15145 .as_ref()
15146 .map(|location| {
15147 let buffer = location.buffer.read(cx);
15148 format!(
15149 "References to `{}`",
15150 buffer
15151 .text_for_range(location.range.clone())
15152 .collect::<String>()
15153 )
15154 })
15155 .unwrap();
15156 Self::open_locations_in_multibuffer(
15157 workspace,
15158 locations,
15159 title,
15160 false,
15161 MultibufferSelectionMode::First,
15162 window,
15163 cx,
15164 );
15165 Navigated::Yes
15166 })
15167 }))
15168 }
15169
15170 /// Opens a multibuffer with the given project locations in it
15171 pub fn open_locations_in_multibuffer(
15172 workspace: &mut Workspace,
15173 mut locations: Vec<Location>,
15174 title: String,
15175 split: bool,
15176 multibuffer_selection_mode: MultibufferSelectionMode,
15177 window: &mut Window,
15178 cx: &mut Context<Workspace>,
15179 ) {
15180 // If there are multiple definitions, open them in a multibuffer
15181 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15182 let mut locations = locations.into_iter().peekable();
15183 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15184 let capability = workspace.project().read(cx).capability();
15185
15186 let excerpt_buffer = cx.new(|cx| {
15187 let mut multibuffer = MultiBuffer::new(capability);
15188 while let Some(location) = locations.next() {
15189 let buffer = location.buffer.read(cx);
15190 let mut ranges_for_buffer = Vec::new();
15191 let range = location.range.to_point(buffer);
15192 ranges_for_buffer.push(range.clone());
15193
15194 while let Some(next_location) = locations.peek() {
15195 if next_location.buffer == location.buffer {
15196 ranges_for_buffer.push(next_location.range.to_point(buffer));
15197 locations.next();
15198 } else {
15199 break;
15200 }
15201 }
15202
15203 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15204 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15205 PathKey::for_buffer(&location.buffer, cx),
15206 location.buffer.clone(),
15207 ranges_for_buffer,
15208 DEFAULT_MULTIBUFFER_CONTEXT,
15209 cx,
15210 );
15211 ranges.extend(new_ranges)
15212 }
15213
15214 multibuffer.with_title(title)
15215 });
15216
15217 let editor = cx.new(|cx| {
15218 Editor::for_multibuffer(
15219 excerpt_buffer,
15220 Some(workspace.project().clone()),
15221 window,
15222 cx,
15223 )
15224 });
15225 editor.update(cx, |editor, cx| {
15226 match multibuffer_selection_mode {
15227 MultibufferSelectionMode::First => {
15228 if let Some(first_range) = ranges.first() {
15229 editor.change_selections(None, window, cx, |selections| {
15230 selections.clear_disjoint();
15231 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
15232 });
15233 }
15234 editor.highlight_background::<Self>(
15235 &ranges,
15236 |theme| theme.editor_highlighted_line_background,
15237 cx,
15238 );
15239 }
15240 MultibufferSelectionMode::All => {
15241 editor.change_selections(None, window, cx, |selections| {
15242 selections.clear_disjoint();
15243 selections.select_anchor_ranges(ranges);
15244 });
15245 }
15246 }
15247 editor.register_buffers_with_language_servers(cx);
15248 });
15249
15250 let item = Box::new(editor);
15251 let item_id = item.item_id();
15252
15253 if split {
15254 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15255 } else {
15256 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15257 let (preview_item_id, preview_item_idx) =
15258 workspace.active_pane().read_with(cx, |pane, _| {
15259 (pane.preview_item_id(), pane.preview_item_idx())
15260 });
15261
15262 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15263
15264 if let Some(preview_item_id) = preview_item_id {
15265 workspace.active_pane().update(cx, |pane, cx| {
15266 pane.remove_item(preview_item_id, false, false, window, cx);
15267 });
15268 }
15269 } else {
15270 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15271 }
15272 }
15273 workspace.active_pane().update(cx, |pane, cx| {
15274 pane.set_preview_item_id(Some(item_id), cx);
15275 });
15276 }
15277
15278 pub fn rename(
15279 &mut self,
15280 _: &Rename,
15281 window: &mut Window,
15282 cx: &mut Context<Self>,
15283 ) -> Option<Task<Result<()>>> {
15284 use language::ToOffset as _;
15285
15286 let provider = self.semantics_provider.clone()?;
15287 let selection = self.selections.newest_anchor().clone();
15288 let (cursor_buffer, cursor_buffer_position) = self
15289 .buffer
15290 .read(cx)
15291 .text_anchor_for_position(selection.head(), cx)?;
15292 let (tail_buffer, cursor_buffer_position_end) = self
15293 .buffer
15294 .read(cx)
15295 .text_anchor_for_position(selection.tail(), cx)?;
15296 if tail_buffer != cursor_buffer {
15297 return None;
15298 }
15299
15300 let snapshot = cursor_buffer.read(cx).snapshot();
15301 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15302 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15303 let prepare_rename = provider
15304 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15305 .unwrap_or_else(|| Task::ready(Ok(None)));
15306 drop(snapshot);
15307
15308 Some(cx.spawn_in(window, async move |this, cx| {
15309 let rename_range = if let Some(range) = prepare_rename.await? {
15310 Some(range)
15311 } else {
15312 this.update(cx, |this, cx| {
15313 let buffer = this.buffer.read(cx).snapshot(cx);
15314 let mut buffer_highlights = this
15315 .document_highlights_for_position(selection.head(), &buffer)
15316 .filter(|highlight| {
15317 highlight.start.excerpt_id == selection.head().excerpt_id
15318 && highlight.end.excerpt_id == selection.head().excerpt_id
15319 });
15320 buffer_highlights
15321 .next()
15322 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15323 })?
15324 };
15325 if let Some(rename_range) = rename_range {
15326 this.update_in(cx, |this, window, cx| {
15327 let snapshot = cursor_buffer.read(cx).snapshot();
15328 let rename_buffer_range = rename_range.to_offset(&snapshot);
15329 let cursor_offset_in_rename_range =
15330 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15331 let cursor_offset_in_rename_range_end =
15332 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15333
15334 this.take_rename(false, window, cx);
15335 let buffer = this.buffer.read(cx).read(cx);
15336 let cursor_offset = selection.head().to_offset(&buffer);
15337 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15338 let rename_end = rename_start + rename_buffer_range.len();
15339 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15340 let mut old_highlight_id = None;
15341 let old_name: Arc<str> = buffer
15342 .chunks(rename_start..rename_end, true)
15343 .map(|chunk| {
15344 if old_highlight_id.is_none() {
15345 old_highlight_id = chunk.syntax_highlight_id;
15346 }
15347 chunk.text
15348 })
15349 .collect::<String>()
15350 .into();
15351
15352 drop(buffer);
15353
15354 // Position the selection in the rename editor so that it matches the current selection.
15355 this.show_local_selections = false;
15356 let rename_editor = cx.new(|cx| {
15357 let mut editor = Editor::single_line(window, cx);
15358 editor.buffer.update(cx, |buffer, cx| {
15359 buffer.edit([(0..0, old_name.clone())], None, cx)
15360 });
15361 let rename_selection_range = match cursor_offset_in_rename_range
15362 .cmp(&cursor_offset_in_rename_range_end)
15363 {
15364 Ordering::Equal => {
15365 editor.select_all(&SelectAll, window, cx);
15366 return editor;
15367 }
15368 Ordering::Less => {
15369 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15370 }
15371 Ordering::Greater => {
15372 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15373 }
15374 };
15375 if rename_selection_range.end > old_name.len() {
15376 editor.select_all(&SelectAll, window, cx);
15377 } else {
15378 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15379 s.select_ranges([rename_selection_range]);
15380 });
15381 }
15382 editor
15383 });
15384 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15385 if e == &EditorEvent::Focused {
15386 cx.emit(EditorEvent::FocusedIn)
15387 }
15388 })
15389 .detach();
15390
15391 let write_highlights =
15392 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
15393 let read_highlights =
15394 this.clear_background_highlights::<DocumentHighlightRead>(cx);
15395 let ranges = write_highlights
15396 .iter()
15397 .flat_map(|(_, ranges)| ranges.iter())
15398 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
15399 .cloned()
15400 .collect();
15401
15402 this.highlight_text::<Rename>(
15403 ranges,
15404 HighlightStyle {
15405 fade_out: Some(0.6),
15406 ..Default::default()
15407 },
15408 cx,
15409 );
15410 let rename_focus_handle = rename_editor.focus_handle(cx);
15411 window.focus(&rename_focus_handle);
15412 let block_id = this.insert_blocks(
15413 [BlockProperties {
15414 style: BlockStyle::Flex,
15415 placement: BlockPlacement::Below(range.start),
15416 height: Some(1),
15417 render: Arc::new({
15418 let rename_editor = rename_editor.clone();
15419 move |cx: &mut BlockContext| {
15420 let mut text_style = cx.editor_style.text.clone();
15421 if let Some(highlight_style) = old_highlight_id
15422 .and_then(|h| h.style(&cx.editor_style.syntax))
15423 {
15424 text_style = text_style.highlight(highlight_style);
15425 }
15426 div()
15427 .block_mouse_except_scroll()
15428 .pl(cx.anchor_x)
15429 .child(EditorElement::new(
15430 &rename_editor,
15431 EditorStyle {
15432 background: cx.theme().system().transparent,
15433 local_player: cx.editor_style.local_player,
15434 text: text_style,
15435 scrollbar_width: cx.editor_style.scrollbar_width,
15436 syntax: cx.editor_style.syntax.clone(),
15437 status: cx.editor_style.status.clone(),
15438 inlay_hints_style: HighlightStyle {
15439 font_weight: Some(FontWeight::BOLD),
15440 ..make_inlay_hints_style(cx.app)
15441 },
15442 inline_completion_styles: make_suggestion_styles(
15443 cx.app,
15444 ),
15445 ..EditorStyle::default()
15446 },
15447 ))
15448 .into_any_element()
15449 }
15450 }),
15451 priority: 0,
15452 render_in_minimap: true,
15453 }],
15454 Some(Autoscroll::fit()),
15455 cx,
15456 )[0];
15457 this.pending_rename = Some(RenameState {
15458 range,
15459 old_name,
15460 editor: rename_editor,
15461 block_id,
15462 });
15463 })?;
15464 }
15465
15466 Ok(())
15467 }))
15468 }
15469
15470 pub fn confirm_rename(
15471 &mut self,
15472 _: &ConfirmRename,
15473 window: &mut Window,
15474 cx: &mut Context<Self>,
15475 ) -> Option<Task<Result<()>>> {
15476 let rename = self.take_rename(false, window, cx)?;
15477 let workspace = self.workspace()?.downgrade();
15478 let (buffer, start) = self
15479 .buffer
15480 .read(cx)
15481 .text_anchor_for_position(rename.range.start, cx)?;
15482 let (end_buffer, _) = self
15483 .buffer
15484 .read(cx)
15485 .text_anchor_for_position(rename.range.end, cx)?;
15486 if buffer != end_buffer {
15487 return None;
15488 }
15489
15490 let old_name = rename.old_name;
15491 let new_name = rename.editor.read(cx).text(cx);
15492
15493 let rename = self.semantics_provider.as_ref()?.perform_rename(
15494 &buffer,
15495 start,
15496 new_name.clone(),
15497 cx,
15498 )?;
15499
15500 Some(cx.spawn_in(window, async move |editor, cx| {
15501 let project_transaction = rename.await?;
15502 Self::open_project_transaction(
15503 &editor,
15504 workspace,
15505 project_transaction,
15506 format!("Rename: {} → {}", old_name, new_name),
15507 cx,
15508 )
15509 .await?;
15510
15511 editor.update(cx, |editor, cx| {
15512 editor.refresh_document_highlights(cx);
15513 })?;
15514 Ok(())
15515 }))
15516 }
15517
15518 fn take_rename(
15519 &mut self,
15520 moving_cursor: bool,
15521 window: &mut Window,
15522 cx: &mut Context<Self>,
15523 ) -> Option<RenameState> {
15524 let rename = self.pending_rename.take()?;
15525 if rename.editor.focus_handle(cx).is_focused(window) {
15526 window.focus(&self.focus_handle);
15527 }
15528
15529 self.remove_blocks(
15530 [rename.block_id].into_iter().collect(),
15531 Some(Autoscroll::fit()),
15532 cx,
15533 );
15534 self.clear_highlights::<Rename>(cx);
15535 self.show_local_selections = true;
15536
15537 if moving_cursor {
15538 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15539 editor.selections.newest::<usize>(cx).head()
15540 });
15541
15542 // Update the selection to match the position of the selection inside
15543 // the rename editor.
15544 let snapshot = self.buffer.read(cx).read(cx);
15545 let rename_range = rename.range.to_offset(&snapshot);
15546 let cursor_in_editor = snapshot
15547 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15548 .min(rename_range.end);
15549 drop(snapshot);
15550
15551 self.change_selections(None, window, cx, |s| {
15552 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15553 });
15554 } else {
15555 self.refresh_document_highlights(cx);
15556 }
15557
15558 Some(rename)
15559 }
15560
15561 pub fn pending_rename(&self) -> Option<&RenameState> {
15562 self.pending_rename.as_ref()
15563 }
15564
15565 fn format(
15566 &mut self,
15567 _: &Format,
15568 window: &mut Window,
15569 cx: &mut Context<Self>,
15570 ) -> Option<Task<Result<()>>> {
15571 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15572
15573 let project = match &self.project {
15574 Some(project) => project.clone(),
15575 None => return None,
15576 };
15577
15578 Some(self.perform_format(
15579 project,
15580 FormatTrigger::Manual,
15581 FormatTarget::Buffers,
15582 window,
15583 cx,
15584 ))
15585 }
15586
15587 fn format_selections(
15588 &mut self,
15589 _: &FormatSelections,
15590 window: &mut Window,
15591 cx: &mut Context<Self>,
15592 ) -> Option<Task<Result<()>>> {
15593 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15594
15595 let project = match &self.project {
15596 Some(project) => project.clone(),
15597 None => return None,
15598 };
15599
15600 let ranges = self
15601 .selections
15602 .all_adjusted(cx)
15603 .into_iter()
15604 .map(|selection| selection.range())
15605 .collect_vec();
15606
15607 Some(self.perform_format(
15608 project,
15609 FormatTrigger::Manual,
15610 FormatTarget::Ranges(ranges),
15611 window,
15612 cx,
15613 ))
15614 }
15615
15616 fn perform_format(
15617 &mut self,
15618 project: Entity<Project>,
15619 trigger: FormatTrigger,
15620 target: FormatTarget,
15621 window: &mut Window,
15622 cx: &mut Context<Self>,
15623 ) -> Task<Result<()>> {
15624 let buffer = self.buffer.clone();
15625 let (buffers, target) = match target {
15626 FormatTarget::Buffers => {
15627 let mut buffers = buffer.read(cx).all_buffers();
15628 if trigger == FormatTrigger::Save {
15629 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15630 }
15631 (buffers, LspFormatTarget::Buffers)
15632 }
15633 FormatTarget::Ranges(selection_ranges) => {
15634 let multi_buffer = buffer.read(cx);
15635 let snapshot = multi_buffer.read(cx);
15636 let mut buffers = HashSet::default();
15637 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15638 BTreeMap::new();
15639 for selection_range in selection_ranges {
15640 for (buffer, buffer_range, _) in
15641 snapshot.range_to_buffer_ranges(selection_range)
15642 {
15643 let buffer_id = buffer.remote_id();
15644 let start = buffer.anchor_before(buffer_range.start);
15645 let end = buffer.anchor_after(buffer_range.end);
15646 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15647 buffer_id_to_ranges
15648 .entry(buffer_id)
15649 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15650 .or_insert_with(|| vec![start..end]);
15651 }
15652 }
15653 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15654 }
15655 };
15656
15657 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15658 let selections_prev = transaction_id_prev
15659 .and_then(|transaction_id_prev| {
15660 // default to selections as they were after the last edit, if we have them,
15661 // instead of how they are now.
15662 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15663 // will take you back to where you made the last edit, instead of staying where you scrolled
15664 self.selection_history
15665 .transaction(transaction_id_prev)
15666 .map(|t| t.0.clone())
15667 })
15668 .unwrap_or_else(|| {
15669 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15670 self.selections.disjoint_anchors()
15671 });
15672
15673 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15674 let format = project.update(cx, |project, cx| {
15675 project.format(buffers, target, true, trigger, cx)
15676 });
15677
15678 cx.spawn_in(window, async move |editor, cx| {
15679 let transaction = futures::select_biased! {
15680 transaction = format.log_err().fuse() => transaction,
15681 () = timeout => {
15682 log::warn!("timed out waiting for formatting");
15683 None
15684 }
15685 };
15686
15687 buffer
15688 .update(cx, |buffer, cx| {
15689 if let Some(transaction) = transaction {
15690 if !buffer.is_singleton() {
15691 buffer.push_transaction(&transaction.0, cx);
15692 }
15693 }
15694 cx.notify();
15695 })
15696 .ok();
15697
15698 if let Some(transaction_id_now) =
15699 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15700 {
15701 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15702 if has_new_transaction {
15703 _ = editor.update(cx, |editor, _| {
15704 editor
15705 .selection_history
15706 .insert_transaction(transaction_id_now, selections_prev);
15707 });
15708 }
15709 }
15710
15711 Ok(())
15712 })
15713 }
15714
15715 fn organize_imports(
15716 &mut self,
15717 _: &OrganizeImports,
15718 window: &mut Window,
15719 cx: &mut Context<Self>,
15720 ) -> Option<Task<Result<()>>> {
15721 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15722 let project = match &self.project {
15723 Some(project) => project.clone(),
15724 None => return None,
15725 };
15726 Some(self.perform_code_action_kind(
15727 project,
15728 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15729 window,
15730 cx,
15731 ))
15732 }
15733
15734 fn perform_code_action_kind(
15735 &mut self,
15736 project: Entity<Project>,
15737 kind: CodeActionKind,
15738 window: &mut Window,
15739 cx: &mut Context<Self>,
15740 ) -> Task<Result<()>> {
15741 let buffer = self.buffer.clone();
15742 let buffers = buffer.read(cx).all_buffers();
15743 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15744 let apply_action = project.update(cx, |project, cx| {
15745 project.apply_code_action_kind(buffers, kind, true, cx)
15746 });
15747 cx.spawn_in(window, async move |_, cx| {
15748 let transaction = futures::select_biased! {
15749 () = timeout => {
15750 log::warn!("timed out waiting for executing code action");
15751 None
15752 }
15753 transaction = apply_action.log_err().fuse() => transaction,
15754 };
15755 buffer
15756 .update(cx, |buffer, cx| {
15757 // check if we need this
15758 if let Some(transaction) = transaction {
15759 if !buffer.is_singleton() {
15760 buffer.push_transaction(&transaction.0, cx);
15761 }
15762 }
15763 cx.notify();
15764 })
15765 .ok();
15766 Ok(())
15767 })
15768 }
15769
15770 fn restart_language_server(
15771 &mut self,
15772 _: &RestartLanguageServer,
15773 _: &mut Window,
15774 cx: &mut Context<Self>,
15775 ) {
15776 if let Some(project) = self.project.clone() {
15777 self.buffer.update(cx, |multi_buffer, cx| {
15778 project.update(cx, |project, cx| {
15779 project.restart_language_servers_for_buffers(
15780 multi_buffer.all_buffers().into_iter().collect(),
15781 cx,
15782 );
15783 });
15784 })
15785 }
15786 }
15787
15788 fn stop_language_server(
15789 &mut self,
15790 _: &StopLanguageServer,
15791 _: &mut Window,
15792 cx: &mut Context<Self>,
15793 ) {
15794 if let Some(project) = self.project.clone() {
15795 self.buffer.update(cx, |multi_buffer, cx| {
15796 project.update(cx, |project, cx| {
15797 project.stop_language_servers_for_buffers(
15798 multi_buffer.all_buffers().into_iter().collect(),
15799 cx,
15800 );
15801 cx.emit(project::Event::RefreshInlayHints);
15802 });
15803 });
15804 }
15805 }
15806
15807 fn cancel_language_server_work(
15808 workspace: &mut Workspace,
15809 _: &actions::CancelLanguageServerWork,
15810 _: &mut Window,
15811 cx: &mut Context<Workspace>,
15812 ) {
15813 let project = workspace.project();
15814 let buffers = workspace
15815 .active_item(cx)
15816 .and_then(|item| item.act_as::<Editor>(cx))
15817 .map_or(HashSet::default(), |editor| {
15818 editor.read(cx).buffer.read(cx).all_buffers()
15819 });
15820 project.update(cx, |project, cx| {
15821 project.cancel_language_server_work_for_buffers(buffers, cx);
15822 });
15823 }
15824
15825 fn show_character_palette(
15826 &mut self,
15827 _: &ShowCharacterPalette,
15828 window: &mut Window,
15829 _: &mut Context<Self>,
15830 ) {
15831 window.show_character_palette();
15832 }
15833
15834 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15835 if self.mode.is_minimap() {
15836 return;
15837 }
15838
15839 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15840 let buffer = self.buffer.read(cx).snapshot(cx);
15841 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15842 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15843 let is_valid = buffer
15844 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15845 .any(|entry| {
15846 entry.diagnostic.is_primary
15847 && !entry.range.is_empty()
15848 && entry.range.start == primary_range_start
15849 && entry.diagnostic.message == active_diagnostics.active_message
15850 });
15851
15852 if !is_valid {
15853 self.dismiss_diagnostics(cx);
15854 }
15855 }
15856 }
15857
15858 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15859 match &self.active_diagnostics {
15860 ActiveDiagnostic::Group(group) => Some(group),
15861 _ => None,
15862 }
15863 }
15864
15865 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15866 self.dismiss_diagnostics(cx);
15867 self.active_diagnostics = ActiveDiagnostic::All;
15868 }
15869
15870 fn activate_diagnostics(
15871 &mut self,
15872 buffer_id: BufferId,
15873 diagnostic: DiagnosticEntry<usize>,
15874 window: &mut Window,
15875 cx: &mut Context<Self>,
15876 ) {
15877 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15878 return;
15879 }
15880 self.dismiss_diagnostics(cx);
15881 let snapshot = self.snapshot(window, cx);
15882 let buffer = self.buffer.read(cx).snapshot(cx);
15883 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15884 return;
15885 };
15886
15887 let diagnostic_group = buffer
15888 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15889 .collect::<Vec<_>>();
15890
15891 let blocks =
15892 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15893
15894 let blocks = self.display_map.update(cx, |display_map, cx| {
15895 display_map.insert_blocks(blocks, cx).into_iter().collect()
15896 });
15897 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15898 active_range: buffer.anchor_before(diagnostic.range.start)
15899 ..buffer.anchor_after(diagnostic.range.end),
15900 active_message: diagnostic.diagnostic.message.clone(),
15901 group_id: diagnostic.diagnostic.group_id,
15902 blocks,
15903 });
15904 cx.notify();
15905 }
15906
15907 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15908 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15909 return;
15910 };
15911
15912 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15913 if let ActiveDiagnostic::Group(group) = prev {
15914 self.display_map.update(cx, |display_map, cx| {
15915 display_map.remove_blocks(group.blocks, cx);
15916 });
15917 cx.notify();
15918 }
15919 }
15920
15921 /// Disable inline diagnostics rendering for this editor.
15922 pub fn disable_inline_diagnostics(&mut self) {
15923 self.inline_diagnostics_enabled = false;
15924 self.inline_diagnostics_update = Task::ready(());
15925 self.inline_diagnostics.clear();
15926 }
15927
15928 pub fn diagnostics_enabled(&self) -> bool {
15929 self.mode.is_full()
15930 }
15931
15932 pub fn inline_diagnostics_enabled(&self) -> bool {
15933 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15934 }
15935
15936 pub fn show_inline_diagnostics(&self) -> bool {
15937 self.show_inline_diagnostics
15938 }
15939
15940 pub fn toggle_inline_diagnostics(
15941 &mut self,
15942 _: &ToggleInlineDiagnostics,
15943 window: &mut Window,
15944 cx: &mut Context<Editor>,
15945 ) {
15946 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15947 self.refresh_inline_diagnostics(false, window, cx);
15948 }
15949
15950 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15951 self.diagnostics_max_severity = severity;
15952 self.display_map.update(cx, |display_map, _| {
15953 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15954 });
15955 }
15956
15957 pub fn toggle_diagnostics(
15958 &mut self,
15959 _: &ToggleDiagnostics,
15960 window: &mut Window,
15961 cx: &mut Context<Editor>,
15962 ) {
15963 if !self.diagnostics_enabled() {
15964 return;
15965 }
15966
15967 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15968 EditorSettings::get_global(cx)
15969 .diagnostics_max_severity
15970 .filter(|severity| severity != &DiagnosticSeverity::Off)
15971 .unwrap_or(DiagnosticSeverity::Hint)
15972 } else {
15973 DiagnosticSeverity::Off
15974 };
15975 self.set_max_diagnostics_severity(new_severity, cx);
15976 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15977 self.active_diagnostics = ActiveDiagnostic::None;
15978 self.inline_diagnostics_update = Task::ready(());
15979 self.inline_diagnostics.clear();
15980 } else {
15981 self.refresh_inline_diagnostics(false, window, cx);
15982 }
15983
15984 cx.notify();
15985 }
15986
15987 pub fn toggle_minimap(
15988 &mut self,
15989 _: &ToggleMinimap,
15990 window: &mut Window,
15991 cx: &mut Context<Editor>,
15992 ) {
15993 if self.supports_minimap(cx) {
15994 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
15995 }
15996 }
15997
15998 fn refresh_inline_diagnostics(
15999 &mut self,
16000 debounce: bool,
16001 window: &mut Window,
16002 cx: &mut Context<Self>,
16003 ) {
16004 let max_severity = ProjectSettings::get_global(cx)
16005 .diagnostics
16006 .inline
16007 .max_severity
16008 .unwrap_or(self.diagnostics_max_severity);
16009
16010 if !self.inline_diagnostics_enabled()
16011 || !self.show_inline_diagnostics
16012 || max_severity == DiagnosticSeverity::Off
16013 {
16014 self.inline_diagnostics_update = Task::ready(());
16015 self.inline_diagnostics.clear();
16016 return;
16017 }
16018
16019 let debounce_ms = ProjectSettings::get_global(cx)
16020 .diagnostics
16021 .inline
16022 .update_debounce_ms;
16023 let debounce = if debounce && debounce_ms > 0 {
16024 Some(Duration::from_millis(debounce_ms))
16025 } else {
16026 None
16027 };
16028 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16029 if let Some(debounce) = debounce {
16030 cx.background_executor().timer(debounce).await;
16031 }
16032 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16033 editor
16034 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16035 .ok()
16036 }) else {
16037 return;
16038 };
16039
16040 let new_inline_diagnostics = cx
16041 .background_spawn(async move {
16042 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16043 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16044 let message = diagnostic_entry
16045 .diagnostic
16046 .message
16047 .split_once('\n')
16048 .map(|(line, _)| line)
16049 .map(SharedString::new)
16050 .unwrap_or_else(|| {
16051 SharedString::from(diagnostic_entry.diagnostic.message)
16052 });
16053 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16054 let (Ok(i) | Err(i)) = inline_diagnostics
16055 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16056 inline_diagnostics.insert(
16057 i,
16058 (
16059 start_anchor,
16060 InlineDiagnostic {
16061 message,
16062 group_id: diagnostic_entry.diagnostic.group_id,
16063 start: diagnostic_entry.range.start.to_point(&snapshot),
16064 is_primary: diagnostic_entry.diagnostic.is_primary,
16065 severity: diagnostic_entry.diagnostic.severity,
16066 },
16067 ),
16068 );
16069 }
16070 inline_diagnostics
16071 })
16072 .await;
16073
16074 editor
16075 .update(cx, |editor, cx| {
16076 editor.inline_diagnostics = new_inline_diagnostics;
16077 cx.notify();
16078 })
16079 .ok();
16080 });
16081 }
16082
16083 fn pull_diagnostics(
16084 &mut self,
16085 buffer_id: Option<BufferId>,
16086 window: &Window,
16087 cx: &mut Context<Self>,
16088 ) -> Option<()> {
16089 if !self.mode().is_full() {
16090 return None;
16091 }
16092 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16093 .diagnostics
16094 .lsp_pull_diagnostics;
16095 if !pull_diagnostics_settings.enabled {
16096 return None;
16097 }
16098 let project = self.project.as_ref()?.downgrade();
16099 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16100 let mut buffers = self.buffer.read(cx).all_buffers();
16101 if let Some(buffer_id) = buffer_id {
16102 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16103 }
16104
16105 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16106 cx.background_executor().timer(debounce).await;
16107
16108 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16109 buffers
16110 .into_iter()
16111 .flat_map(|buffer| {
16112 Some(project.upgrade()?.pull_diagnostics_for_buffer(buffer, cx))
16113 })
16114 .collect::<FuturesUnordered<_>>()
16115 }) else {
16116 return;
16117 };
16118
16119 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16120 match pull_task {
16121 Ok(()) => {
16122 if editor
16123 .update_in(cx, |editor, window, cx| {
16124 editor.update_diagnostics_state(window, cx);
16125 })
16126 .is_err()
16127 {
16128 return;
16129 }
16130 }
16131 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16132 }
16133 }
16134 });
16135
16136 Some(())
16137 }
16138
16139 pub fn set_selections_from_remote(
16140 &mut self,
16141 selections: Vec<Selection<Anchor>>,
16142 pending_selection: Option<Selection<Anchor>>,
16143 window: &mut Window,
16144 cx: &mut Context<Self>,
16145 ) {
16146 let old_cursor_position = self.selections.newest_anchor().head();
16147 self.selections.change_with(cx, |s| {
16148 s.select_anchors(selections);
16149 if let Some(pending_selection) = pending_selection {
16150 s.set_pending(pending_selection, SelectMode::Character);
16151 } else {
16152 s.clear_pending();
16153 }
16154 });
16155 self.selections_did_change(false, &old_cursor_position, true, window, cx);
16156 }
16157
16158 pub fn transact(
16159 &mut self,
16160 window: &mut Window,
16161 cx: &mut Context<Self>,
16162 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16163 ) -> Option<TransactionId> {
16164 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16165 this.start_transaction_at(Instant::now(), window, cx);
16166 update(this, window, cx);
16167 this.end_transaction_at(Instant::now(), cx)
16168 })
16169 }
16170
16171 pub fn start_transaction_at(
16172 &mut self,
16173 now: Instant,
16174 window: &mut Window,
16175 cx: &mut Context<Self>,
16176 ) {
16177 self.end_selection(window, cx);
16178 if let Some(tx_id) = self
16179 .buffer
16180 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16181 {
16182 self.selection_history
16183 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16184 cx.emit(EditorEvent::TransactionBegun {
16185 transaction_id: tx_id,
16186 })
16187 }
16188 }
16189
16190 pub fn end_transaction_at(
16191 &mut self,
16192 now: Instant,
16193 cx: &mut Context<Self>,
16194 ) -> Option<TransactionId> {
16195 if let Some(transaction_id) = self
16196 .buffer
16197 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16198 {
16199 if let Some((_, end_selections)) =
16200 self.selection_history.transaction_mut(transaction_id)
16201 {
16202 *end_selections = Some(self.selections.disjoint_anchors());
16203 } else {
16204 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16205 }
16206
16207 cx.emit(EditorEvent::Edited { transaction_id });
16208 Some(transaction_id)
16209 } else {
16210 None
16211 }
16212 }
16213
16214 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16215 if self.selection_mark_mode {
16216 self.change_selections(None, window, cx, |s| {
16217 s.move_with(|_, sel| {
16218 sel.collapse_to(sel.head(), SelectionGoal::None);
16219 });
16220 })
16221 }
16222 self.selection_mark_mode = true;
16223 cx.notify();
16224 }
16225
16226 pub fn swap_selection_ends(
16227 &mut self,
16228 _: &actions::SwapSelectionEnds,
16229 window: &mut Window,
16230 cx: &mut Context<Self>,
16231 ) {
16232 self.change_selections(None, window, cx, |s| {
16233 s.move_with(|_, sel| {
16234 if sel.start != sel.end {
16235 sel.reversed = !sel.reversed
16236 }
16237 });
16238 });
16239 self.request_autoscroll(Autoscroll::newest(), cx);
16240 cx.notify();
16241 }
16242
16243 pub fn toggle_fold(
16244 &mut self,
16245 _: &actions::ToggleFold,
16246 window: &mut Window,
16247 cx: &mut Context<Self>,
16248 ) {
16249 if self.is_singleton(cx) {
16250 let selection = self.selections.newest::<Point>(cx);
16251
16252 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16253 let range = if selection.is_empty() {
16254 let point = selection.head().to_display_point(&display_map);
16255 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16256 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16257 .to_point(&display_map);
16258 start..end
16259 } else {
16260 selection.range()
16261 };
16262 if display_map.folds_in_range(range).next().is_some() {
16263 self.unfold_lines(&Default::default(), window, cx)
16264 } else {
16265 self.fold(&Default::default(), window, cx)
16266 }
16267 } else {
16268 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16269 let buffer_ids: HashSet<_> = self
16270 .selections
16271 .disjoint_anchor_ranges()
16272 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16273 .collect();
16274
16275 let should_unfold = buffer_ids
16276 .iter()
16277 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16278
16279 for buffer_id in buffer_ids {
16280 if should_unfold {
16281 self.unfold_buffer(buffer_id, cx);
16282 } else {
16283 self.fold_buffer(buffer_id, cx);
16284 }
16285 }
16286 }
16287 }
16288
16289 pub fn toggle_fold_recursive(
16290 &mut self,
16291 _: &actions::ToggleFoldRecursive,
16292 window: &mut Window,
16293 cx: &mut Context<Self>,
16294 ) {
16295 let selection = self.selections.newest::<Point>(cx);
16296
16297 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16298 let range = if selection.is_empty() {
16299 let point = selection.head().to_display_point(&display_map);
16300 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16301 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16302 .to_point(&display_map);
16303 start..end
16304 } else {
16305 selection.range()
16306 };
16307 if display_map.folds_in_range(range).next().is_some() {
16308 self.unfold_recursive(&Default::default(), window, cx)
16309 } else {
16310 self.fold_recursive(&Default::default(), window, cx)
16311 }
16312 }
16313
16314 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16315 if self.is_singleton(cx) {
16316 let mut to_fold = Vec::new();
16317 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16318 let selections = self.selections.all_adjusted(cx);
16319
16320 for selection in selections {
16321 let range = selection.range().sorted();
16322 let buffer_start_row = range.start.row;
16323
16324 if range.start.row != range.end.row {
16325 let mut found = false;
16326 let mut row = range.start.row;
16327 while row <= range.end.row {
16328 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16329 {
16330 found = true;
16331 row = crease.range().end.row + 1;
16332 to_fold.push(crease);
16333 } else {
16334 row += 1
16335 }
16336 }
16337 if found {
16338 continue;
16339 }
16340 }
16341
16342 for row in (0..=range.start.row).rev() {
16343 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16344 if crease.range().end.row >= buffer_start_row {
16345 to_fold.push(crease);
16346 if row <= range.start.row {
16347 break;
16348 }
16349 }
16350 }
16351 }
16352 }
16353
16354 self.fold_creases(to_fold, true, window, cx);
16355 } else {
16356 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16357 let buffer_ids = self
16358 .selections
16359 .disjoint_anchor_ranges()
16360 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16361 .collect::<HashSet<_>>();
16362 for buffer_id in buffer_ids {
16363 self.fold_buffer(buffer_id, cx);
16364 }
16365 }
16366 }
16367
16368 fn fold_at_level(
16369 &mut self,
16370 fold_at: &FoldAtLevel,
16371 window: &mut Window,
16372 cx: &mut Context<Self>,
16373 ) {
16374 if !self.buffer.read(cx).is_singleton() {
16375 return;
16376 }
16377
16378 let fold_at_level = fold_at.0;
16379 let snapshot = self.buffer.read(cx).snapshot(cx);
16380 let mut to_fold = Vec::new();
16381 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16382
16383 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16384 while start_row < end_row {
16385 match self
16386 .snapshot(window, cx)
16387 .crease_for_buffer_row(MultiBufferRow(start_row))
16388 {
16389 Some(crease) => {
16390 let nested_start_row = crease.range().start.row + 1;
16391 let nested_end_row = crease.range().end.row;
16392
16393 if current_level < fold_at_level {
16394 stack.push((nested_start_row, nested_end_row, current_level + 1));
16395 } else if current_level == fold_at_level {
16396 to_fold.push(crease);
16397 }
16398
16399 start_row = nested_end_row + 1;
16400 }
16401 None => start_row += 1,
16402 }
16403 }
16404 }
16405
16406 self.fold_creases(to_fold, true, window, cx);
16407 }
16408
16409 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16410 if self.buffer.read(cx).is_singleton() {
16411 let mut fold_ranges = Vec::new();
16412 let snapshot = self.buffer.read(cx).snapshot(cx);
16413
16414 for row in 0..snapshot.max_row().0 {
16415 if let Some(foldable_range) = self
16416 .snapshot(window, cx)
16417 .crease_for_buffer_row(MultiBufferRow(row))
16418 {
16419 fold_ranges.push(foldable_range);
16420 }
16421 }
16422
16423 self.fold_creases(fold_ranges, true, window, cx);
16424 } else {
16425 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16426 editor
16427 .update_in(cx, |editor, _, cx| {
16428 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16429 editor.fold_buffer(buffer_id, cx);
16430 }
16431 })
16432 .ok();
16433 });
16434 }
16435 }
16436
16437 pub fn fold_function_bodies(
16438 &mut self,
16439 _: &actions::FoldFunctionBodies,
16440 window: &mut Window,
16441 cx: &mut Context<Self>,
16442 ) {
16443 let snapshot = self.buffer.read(cx).snapshot(cx);
16444
16445 let ranges = snapshot
16446 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16447 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16448 .collect::<Vec<_>>();
16449
16450 let creases = ranges
16451 .into_iter()
16452 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16453 .collect();
16454
16455 self.fold_creases(creases, true, window, cx);
16456 }
16457
16458 pub fn fold_recursive(
16459 &mut self,
16460 _: &actions::FoldRecursive,
16461 window: &mut Window,
16462 cx: &mut Context<Self>,
16463 ) {
16464 let mut to_fold = Vec::new();
16465 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16466 let selections = self.selections.all_adjusted(cx);
16467
16468 for selection in selections {
16469 let range = selection.range().sorted();
16470 let buffer_start_row = range.start.row;
16471
16472 if range.start.row != range.end.row {
16473 let mut found = false;
16474 for row in range.start.row..=range.end.row {
16475 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16476 found = true;
16477 to_fold.push(crease);
16478 }
16479 }
16480 if found {
16481 continue;
16482 }
16483 }
16484
16485 for row in (0..=range.start.row).rev() {
16486 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16487 if crease.range().end.row >= buffer_start_row {
16488 to_fold.push(crease);
16489 } else {
16490 break;
16491 }
16492 }
16493 }
16494 }
16495
16496 self.fold_creases(to_fold, true, window, cx);
16497 }
16498
16499 pub fn fold_at(
16500 &mut self,
16501 buffer_row: MultiBufferRow,
16502 window: &mut Window,
16503 cx: &mut Context<Self>,
16504 ) {
16505 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16506
16507 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16508 let autoscroll = self
16509 .selections
16510 .all::<Point>(cx)
16511 .iter()
16512 .any(|selection| crease.range().overlaps(&selection.range()));
16513
16514 self.fold_creases(vec![crease], autoscroll, window, cx);
16515 }
16516 }
16517
16518 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16519 if self.is_singleton(cx) {
16520 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16521 let buffer = &display_map.buffer_snapshot;
16522 let selections = self.selections.all::<Point>(cx);
16523 let ranges = selections
16524 .iter()
16525 .map(|s| {
16526 let range = s.display_range(&display_map).sorted();
16527 let mut start = range.start.to_point(&display_map);
16528 let mut end = range.end.to_point(&display_map);
16529 start.column = 0;
16530 end.column = buffer.line_len(MultiBufferRow(end.row));
16531 start..end
16532 })
16533 .collect::<Vec<_>>();
16534
16535 self.unfold_ranges(&ranges, true, true, cx);
16536 } else {
16537 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16538 let buffer_ids = self
16539 .selections
16540 .disjoint_anchor_ranges()
16541 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16542 .collect::<HashSet<_>>();
16543 for buffer_id in buffer_ids {
16544 self.unfold_buffer(buffer_id, cx);
16545 }
16546 }
16547 }
16548
16549 pub fn unfold_recursive(
16550 &mut self,
16551 _: &UnfoldRecursive,
16552 _window: &mut Window,
16553 cx: &mut Context<Self>,
16554 ) {
16555 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16556 let selections = self.selections.all::<Point>(cx);
16557 let ranges = selections
16558 .iter()
16559 .map(|s| {
16560 let mut range = s.display_range(&display_map).sorted();
16561 *range.start.column_mut() = 0;
16562 *range.end.column_mut() = display_map.line_len(range.end.row());
16563 let start = range.start.to_point(&display_map);
16564 let end = range.end.to_point(&display_map);
16565 start..end
16566 })
16567 .collect::<Vec<_>>();
16568
16569 self.unfold_ranges(&ranges, true, true, cx);
16570 }
16571
16572 pub fn unfold_at(
16573 &mut self,
16574 buffer_row: MultiBufferRow,
16575 _window: &mut Window,
16576 cx: &mut Context<Self>,
16577 ) {
16578 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16579
16580 let intersection_range = Point::new(buffer_row.0, 0)
16581 ..Point::new(
16582 buffer_row.0,
16583 display_map.buffer_snapshot.line_len(buffer_row),
16584 );
16585
16586 let autoscroll = self
16587 .selections
16588 .all::<Point>(cx)
16589 .iter()
16590 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16591
16592 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16593 }
16594
16595 pub fn unfold_all(
16596 &mut self,
16597 _: &actions::UnfoldAll,
16598 _window: &mut Window,
16599 cx: &mut Context<Self>,
16600 ) {
16601 if self.buffer.read(cx).is_singleton() {
16602 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16603 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16604 } else {
16605 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16606 editor
16607 .update(cx, |editor, cx| {
16608 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16609 editor.unfold_buffer(buffer_id, cx);
16610 }
16611 })
16612 .ok();
16613 });
16614 }
16615 }
16616
16617 pub fn fold_selected_ranges(
16618 &mut self,
16619 _: &FoldSelectedRanges,
16620 window: &mut Window,
16621 cx: &mut Context<Self>,
16622 ) {
16623 let selections = self.selections.all_adjusted(cx);
16624 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16625 let ranges = selections
16626 .into_iter()
16627 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16628 .collect::<Vec<_>>();
16629 self.fold_creases(ranges, true, window, cx);
16630 }
16631
16632 pub fn fold_ranges<T: ToOffset + Clone>(
16633 &mut self,
16634 ranges: Vec<Range<T>>,
16635 auto_scroll: bool,
16636 window: &mut Window,
16637 cx: &mut Context<Self>,
16638 ) {
16639 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16640 let ranges = ranges
16641 .into_iter()
16642 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16643 .collect::<Vec<_>>();
16644 self.fold_creases(ranges, auto_scroll, window, cx);
16645 }
16646
16647 pub fn fold_creases<T: ToOffset + Clone>(
16648 &mut self,
16649 creases: Vec<Crease<T>>,
16650 auto_scroll: bool,
16651 _window: &mut Window,
16652 cx: &mut Context<Self>,
16653 ) {
16654 if creases.is_empty() {
16655 return;
16656 }
16657
16658 let mut buffers_affected = HashSet::default();
16659 let multi_buffer = self.buffer().read(cx);
16660 for crease in &creases {
16661 if let Some((_, buffer, _)) =
16662 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16663 {
16664 buffers_affected.insert(buffer.read(cx).remote_id());
16665 };
16666 }
16667
16668 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16669
16670 if auto_scroll {
16671 self.request_autoscroll(Autoscroll::fit(), cx);
16672 }
16673
16674 cx.notify();
16675
16676 self.scrollbar_marker_state.dirty = true;
16677 self.folds_did_change(cx);
16678 }
16679
16680 /// Removes any folds whose ranges intersect any of the given ranges.
16681 pub fn unfold_ranges<T: ToOffset + Clone>(
16682 &mut self,
16683 ranges: &[Range<T>],
16684 inclusive: bool,
16685 auto_scroll: bool,
16686 cx: &mut Context<Self>,
16687 ) {
16688 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16689 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16690 });
16691 self.folds_did_change(cx);
16692 }
16693
16694 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16695 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16696 return;
16697 }
16698 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16699 self.display_map.update(cx, |display_map, cx| {
16700 display_map.fold_buffers([buffer_id], cx)
16701 });
16702 cx.emit(EditorEvent::BufferFoldToggled {
16703 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16704 folded: true,
16705 });
16706 cx.notify();
16707 }
16708
16709 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16710 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16711 return;
16712 }
16713 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16714 self.display_map.update(cx, |display_map, cx| {
16715 display_map.unfold_buffers([buffer_id], cx);
16716 });
16717 cx.emit(EditorEvent::BufferFoldToggled {
16718 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16719 folded: false,
16720 });
16721 cx.notify();
16722 }
16723
16724 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16725 self.display_map.read(cx).is_buffer_folded(buffer)
16726 }
16727
16728 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16729 self.display_map.read(cx).folded_buffers()
16730 }
16731
16732 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16733 self.display_map.update(cx, |display_map, cx| {
16734 display_map.disable_header_for_buffer(buffer_id, cx);
16735 });
16736 cx.notify();
16737 }
16738
16739 /// Removes any folds with the given ranges.
16740 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16741 &mut self,
16742 ranges: &[Range<T>],
16743 type_id: TypeId,
16744 auto_scroll: bool,
16745 cx: &mut Context<Self>,
16746 ) {
16747 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16748 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16749 });
16750 self.folds_did_change(cx);
16751 }
16752
16753 fn remove_folds_with<T: ToOffset + Clone>(
16754 &mut self,
16755 ranges: &[Range<T>],
16756 auto_scroll: bool,
16757 cx: &mut Context<Self>,
16758 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16759 ) {
16760 if ranges.is_empty() {
16761 return;
16762 }
16763
16764 let mut buffers_affected = HashSet::default();
16765 let multi_buffer = self.buffer().read(cx);
16766 for range in ranges {
16767 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16768 buffers_affected.insert(buffer.read(cx).remote_id());
16769 };
16770 }
16771
16772 self.display_map.update(cx, update);
16773
16774 if auto_scroll {
16775 self.request_autoscroll(Autoscroll::fit(), cx);
16776 }
16777
16778 cx.notify();
16779 self.scrollbar_marker_state.dirty = true;
16780 self.active_indent_guides_state.dirty = true;
16781 }
16782
16783 pub fn update_fold_widths(
16784 &mut self,
16785 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16786 cx: &mut Context<Self>,
16787 ) -> bool {
16788 self.display_map
16789 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16790 }
16791
16792 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16793 self.display_map.read(cx).fold_placeholder.clone()
16794 }
16795
16796 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16797 self.buffer.update(cx, |buffer, cx| {
16798 buffer.set_all_diff_hunks_expanded(cx);
16799 });
16800 }
16801
16802 pub fn expand_all_diff_hunks(
16803 &mut self,
16804 _: &ExpandAllDiffHunks,
16805 _window: &mut Window,
16806 cx: &mut Context<Self>,
16807 ) {
16808 self.buffer.update(cx, |buffer, cx| {
16809 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16810 });
16811 }
16812
16813 pub fn toggle_selected_diff_hunks(
16814 &mut self,
16815 _: &ToggleSelectedDiffHunks,
16816 _window: &mut Window,
16817 cx: &mut Context<Self>,
16818 ) {
16819 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16820 self.toggle_diff_hunks_in_ranges(ranges, cx);
16821 }
16822
16823 pub fn diff_hunks_in_ranges<'a>(
16824 &'a self,
16825 ranges: &'a [Range<Anchor>],
16826 buffer: &'a MultiBufferSnapshot,
16827 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16828 ranges.iter().flat_map(move |range| {
16829 let end_excerpt_id = range.end.excerpt_id;
16830 let range = range.to_point(buffer);
16831 let mut peek_end = range.end;
16832 if range.end.row < buffer.max_row().0 {
16833 peek_end = Point::new(range.end.row + 1, 0);
16834 }
16835 buffer
16836 .diff_hunks_in_range(range.start..peek_end)
16837 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16838 })
16839 }
16840
16841 pub fn has_stageable_diff_hunks_in_ranges(
16842 &self,
16843 ranges: &[Range<Anchor>],
16844 snapshot: &MultiBufferSnapshot,
16845 ) -> bool {
16846 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16847 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16848 }
16849
16850 pub fn toggle_staged_selected_diff_hunks(
16851 &mut self,
16852 _: &::git::ToggleStaged,
16853 _: &mut Window,
16854 cx: &mut Context<Self>,
16855 ) {
16856 let snapshot = self.buffer.read(cx).snapshot(cx);
16857 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16858 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16859 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16860 }
16861
16862 pub fn set_render_diff_hunk_controls(
16863 &mut self,
16864 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16865 cx: &mut Context<Self>,
16866 ) {
16867 self.render_diff_hunk_controls = render_diff_hunk_controls;
16868 cx.notify();
16869 }
16870
16871 pub fn stage_and_next(
16872 &mut self,
16873 _: &::git::StageAndNext,
16874 window: &mut Window,
16875 cx: &mut Context<Self>,
16876 ) {
16877 self.do_stage_or_unstage_and_next(true, window, cx);
16878 }
16879
16880 pub fn unstage_and_next(
16881 &mut self,
16882 _: &::git::UnstageAndNext,
16883 window: &mut Window,
16884 cx: &mut Context<Self>,
16885 ) {
16886 self.do_stage_or_unstage_and_next(false, window, cx);
16887 }
16888
16889 pub fn stage_or_unstage_diff_hunks(
16890 &mut self,
16891 stage: bool,
16892 ranges: Vec<Range<Anchor>>,
16893 cx: &mut Context<Self>,
16894 ) {
16895 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16896 cx.spawn(async move |this, cx| {
16897 task.await?;
16898 this.update(cx, |this, cx| {
16899 let snapshot = this.buffer.read(cx).snapshot(cx);
16900 let chunk_by = this
16901 .diff_hunks_in_ranges(&ranges, &snapshot)
16902 .chunk_by(|hunk| hunk.buffer_id);
16903 for (buffer_id, hunks) in &chunk_by {
16904 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16905 }
16906 })
16907 })
16908 .detach_and_log_err(cx);
16909 }
16910
16911 fn save_buffers_for_ranges_if_needed(
16912 &mut self,
16913 ranges: &[Range<Anchor>],
16914 cx: &mut Context<Editor>,
16915 ) -> Task<Result<()>> {
16916 let multibuffer = self.buffer.read(cx);
16917 let snapshot = multibuffer.read(cx);
16918 let buffer_ids: HashSet<_> = ranges
16919 .iter()
16920 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16921 .collect();
16922 drop(snapshot);
16923
16924 let mut buffers = HashSet::default();
16925 for buffer_id in buffer_ids {
16926 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16927 let buffer = buffer_entity.read(cx);
16928 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16929 {
16930 buffers.insert(buffer_entity);
16931 }
16932 }
16933 }
16934
16935 if let Some(project) = &self.project {
16936 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16937 } else {
16938 Task::ready(Ok(()))
16939 }
16940 }
16941
16942 fn do_stage_or_unstage_and_next(
16943 &mut self,
16944 stage: bool,
16945 window: &mut Window,
16946 cx: &mut Context<Self>,
16947 ) {
16948 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16949
16950 if ranges.iter().any(|range| range.start != range.end) {
16951 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16952 return;
16953 }
16954
16955 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16956 let snapshot = self.snapshot(window, cx);
16957 let position = self.selections.newest::<Point>(cx).head();
16958 let mut row = snapshot
16959 .buffer_snapshot
16960 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16961 .find(|hunk| hunk.row_range.start.0 > position.row)
16962 .map(|hunk| hunk.row_range.start);
16963
16964 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16965 // Outside of the project diff editor, wrap around to the beginning.
16966 if !all_diff_hunks_expanded {
16967 row = row.or_else(|| {
16968 snapshot
16969 .buffer_snapshot
16970 .diff_hunks_in_range(Point::zero()..position)
16971 .find(|hunk| hunk.row_range.end.0 < position.row)
16972 .map(|hunk| hunk.row_range.start)
16973 });
16974 }
16975
16976 if let Some(row) = row {
16977 let destination = Point::new(row.0, 0);
16978 let autoscroll = Autoscroll::center();
16979
16980 self.unfold_ranges(&[destination..destination], false, false, cx);
16981 self.change_selections(Some(autoscroll), window, cx, |s| {
16982 s.select_ranges([destination..destination]);
16983 });
16984 }
16985 }
16986
16987 fn do_stage_or_unstage(
16988 &self,
16989 stage: bool,
16990 buffer_id: BufferId,
16991 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16992 cx: &mut App,
16993 ) -> Option<()> {
16994 let project = self.project.as_ref()?;
16995 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16996 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16997 let buffer_snapshot = buffer.read(cx).snapshot();
16998 let file_exists = buffer_snapshot
16999 .file()
17000 .is_some_and(|file| file.disk_state().exists());
17001 diff.update(cx, |diff, cx| {
17002 diff.stage_or_unstage_hunks(
17003 stage,
17004 &hunks
17005 .map(|hunk| buffer_diff::DiffHunk {
17006 buffer_range: hunk.buffer_range,
17007 diff_base_byte_range: hunk.diff_base_byte_range,
17008 secondary_status: hunk.secondary_status,
17009 range: Point::zero()..Point::zero(), // unused
17010 })
17011 .collect::<Vec<_>>(),
17012 &buffer_snapshot,
17013 file_exists,
17014 cx,
17015 )
17016 });
17017 None
17018 }
17019
17020 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17021 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17022 self.buffer
17023 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17024 }
17025
17026 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17027 self.buffer.update(cx, |buffer, cx| {
17028 let ranges = vec![Anchor::min()..Anchor::max()];
17029 if !buffer.all_diff_hunks_expanded()
17030 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17031 {
17032 buffer.collapse_diff_hunks(ranges, cx);
17033 true
17034 } else {
17035 false
17036 }
17037 })
17038 }
17039
17040 fn toggle_diff_hunks_in_ranges(
17041 &mut self,
17042 ranges: Vec<Range<Anchor>>,
17043 cx: &mut Context<Editor>,
17044 ) {
17045 self.buffer.update(cx, |buffer, cx| {
17046 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17047 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17048 })
17049 }
17050
17051 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17052 self.buffer.update(cx, |buffer, cx| {
17053 let snapshot = buffer.snapshot(cx);
17054 let excerpt_id = range.end.excerpt_id;
17055 let point_range = range.to_point(&snapshot);
17056 let expand = !buffer.single_hunk_is_expanded(range, cx);
17057 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17058 })
17059 }
17060
17061 pub(crate) fn apply_all_diff_hunks(
17062 &mut self,
17063 _: &ApplyAllDiffHunks,
17064 window: &mut Window,
17065 cx: &mut Context<Self>,
17066 ) {
17067 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17068
17069 let buffers = self.buffer.read(cx).all_buffers();
17070 for branch_buffer in buffers {
17071 branch_buffer.update(cx, |branch_buffer, cx| {
17072 branch_buffer.merge_into_base(Vec::new(), cx);
17073 });
17074 }
17075
17076 if let Some(project) = self.project.clone() {
17077 self.save(true, project, window, cx).detach_and_log_err(cx);
17078 }
17079 }
17080
17081 pub(crate) fn apply_selected_diff_hunks(
17082 &mut self,
17083 _: &ApplyDiffHunk,
17084 window: &mut Window,
17085 cx: &mut Context<Self>,
17086 ) {
17087 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17088 let snapshot = self.snapshot(window, cx);
17089 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17090 let mut ranges_by_buffer = HashMap::default();
17091 self.transact(window, cx, |editor, _window, cx| {
17092 for hunk in hunks {
17093 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17094 ranges_by_buffer
17095 .entry(buffer.clone())
17096 .or_insert_with(Vec::new)
17097 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17098 }
17099 }
17100
17101 for (buffer, ranges) in ranges_by_buffer {
17102 buffer.update(cx, |buffer, cx| {
17103 buffer.merge_into_base(ranges, cx);
17104 });
17105 }
17106 });
17107
17108 if let Some(project) = self.project.clone() {
17109 self.save(true, project, window, cx).detach_and_log_err(cx);
17110 }
17111 }
17112
17113 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17114 if hovered != self.gutter_hovered {
17115 self.gutter_hovered = hovered;
17116 cx.notify();
17117 }
17118 }
17119
17120 pub fn insert_blocks(
17121 &mut self,
17122 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17123 autoscroll: Option<Autoscroll>,
17124 cx: &mut Context<Self>,
17125 ) -> Vec<CustomBlockId> {
17126 let blocks = self
17127 .display_map
17128 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17129 if let Some(autoscroll) = autoscroll {
17130 self.request_autoscroll(autoscroll, cx);
17131 }
17132 cx.notify();
17133 blocks
17134 }
17135
17136 pub fn resize_blocks(
17137 &mut self,
17138 heights: HashMap<CustomBlockId, u32>,
17139 autoscroll: Option<Autoscroll>,
17140 cx: &mut Context<Self>,
17141 ) {
17142 self.display_map
17143 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17144 if let Some(autoscroll) = autoscroll {
17145 self.request_autoscroll(autoscroll, cx);
17146 }
17147 cx.notify();
17148 }
17149
17150 pub fn replace_blocks(
17151 &mut self,
17152 renderers: HashMap<CustomBlockId, RenderBlock>,
17153 autoscroll: Option<Autoscroll>,
17154 cx: &mut Context<Self>,
17155 ) {
17156 self.display_map
17157 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17158 if let Some(autoscroll) = autoscroll {
17159 self.request_autoscroll(autoscroll, cx);
17160 }
17161 cx.notify();
17162 }
17163
17164 pub fn remove_blocks(
17165 &mut self,
17166 block_ids: HashSet<CustomBlockId>,
17167 autoscroll: Option<Autoscroll>,
17168 cx: &mut Context<Self>,
17169 ) {
17170 self.display_map.update(cx, |display_map, cx| {
17171 display_map.remove_blocks(block_ids, cx)
17172 });
17173 if let Some(autoscroll) = autoscroll {
17174 self.request_autoscroll(autoscroll, cx);
17175 }
17176 cx.notify();
17177 }
17178
17179 pub fn row_for_block(
17180 &self,
17181 block_id: CustomBlockId,
17182 cx: &mut Context<Self>,
17183 ) -> Option<DisplayRow> {
17184 self.display_map
17185 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17186 }
17187
17188 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17189 self.focused_block = Some(focused_block);
17190 }
17191
17192 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17193 self.focused_block.take()
17194 }
17195
17196 pub fn insert_creases(
17197 &mut self,
17198 creases: impl IntoIterator<Item = Crease<Anchor>>,
17199 cx: &mut Context<Self>,
17200 ) -> Vec<CreaseId> {
17201 self.display_map
17202 .update(cx, |map, cx| map.insert_creases(creases, cx))
17203 }
17204
17205 pub fn remove_creases(
17206 &mut self,
17207 ids: impl IntoIterator<Item = CreaseId>,
17208 cx: &mut Context<Self>,
17209 ) -> Vec<(CreaseId, Range<Anchor>)> {
17210 self.display_map
17211 .update(cx, |map, cx| map.remove_creases(ids, cx))
17212 }
17213
17214 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17215 self.display_map
17216 .update(cx, |map, cx| map.snapshot(cx))
17217 .longest_row()
17218 }
17219
17220 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17221 self.display_map
17222 .update(cx, |map, cx| map.snapshot(cx))
17223 .max_point()
17224 }
17225
17226 pub fn text(&self, cx: &App) -> String {
17227 self.buffer.read(cx).read(cx).text()
17228 }
17229
17230 pub fn is_empty(&self, cx: &App) -> bool {
17231 self.buffer.read(cx).read(cx).is_empty()
17232 }
17233
17234 pub fn text_option(&self, cx: &App) -> Option<String> {
17235 let text = self.text(cx);
17236 let text = text.trim();
17237
17238 if text.is_empty() {
17239 return None;
17240 }
17241
17242 Some(text.to_string())
17243 }
17244
17245 pub fn set_text(
17246 &mut self,
17247 text: impl Into<Arc<str>>,
17248 window: &mut Window,
17249 cx: &mut Context<Self>,
17250 ) {
17251 self.transact(window, cx, |this, _, cx| {
17252 this.buffer
17253 .read(cx)
17254 .as_singleton()
17255 .expect("you can only call set_text on editors for singleton buffers")
17256 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17257 });
17258 }
17259
17260 pub fn display_text(&self, cx: &mut App) -> String {
17261 self.display_map
17262 .update(cx, |map, cx| map.snapshot(cx))
17263 .text()
17264 }
17265
17266 fn create_minimap(
17267 &self,
17268 minimap_settings: MinimapSettings,
17269 window: &mut Window,
17270 cx: &mut Context<Self>,
17271 ) -> Option<Entity<Self>> {
17272 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17273 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17274 }
17275
17276 fn initialize_new_minimap(
17277 &self,
17278 minimap_settings: MinimapSettings,
17279 window: &mut Window,
17280 cx: &mut Context<Self>,
17281 ) -> Entity<Self> {
17282 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17283
17284 let mut minimap = Editor::new_internal(
17285 EditorMode::Minimap {
17286 parent: cx.weak_entity(),
17287 },
17288 self.buffer.clone(),
17289 self.project.clone(),
17290 Some(self.display_map.clone()),
17291 window,
17292 cx,
17293 );
17294 minimap.scroll_manager.clone_state(&self.scroll_manager);
17295 minimap.set_text_style_refinement(TextStyleRefinement {
17296 font_size: Some(MINIMAP_FONT_SIZE),
17297 font_weight: Some(MINIMAP_FONT_WEIGHT),
17298 ..Default::default()
17299 });
17300 minimap.update_minimap_configuration(minimap_settings, cx);
17301 cx.new(|_| minimap)
17302 }
17303
17304 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17305 let current_line_highlight = minimap_settings
17306 .current_line_highlight
17307 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17308 self.set_current_line_highlight(Some(current_line_highlight));
17309 }
17310
17311 pub fn minimap(&self) -> Option<&Entity<Self>> {
17312 self.minimap
17313 .as_ref()
17314 .filter(|_| self.minimap_visibility.visible())
17315 }
17316
17317 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17318 let mut wrap_guides = smallvec![];
17319
17320 if self.show_wrap_guides == Some(false) {
17321 return wrap_guides;
17322 }
17323
17324 let settings = self.buffer.read(cx).language_settings(cx);
17325 if settings.show_wrap_guides {
17326 match self.soft_wrap_mode(cx) {
17327 SoftWrap::Column(soft_wrap) => {
17328 wrap_guides.push((soft_wrap as usize, true));
17329 }
17330 SoftWrap::Bounded(soft_wrap) => {
17331 wrap_guides.push((soft_wrap as usize, true));
17332 }
17333 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17334 }
17335 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17336 }
17337
17338 wrap_guides
17339 }
17340
17341 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17342 let settings = self.buffer.read(cx).language_settings(cx);
17343 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17344 match mode {
17345 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17346 SoftWrap::None
17347 }
17348 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17349 language_settings::SoftWrap::PreferredLineLength => {
17350 SoftWrap::Column(settings.preferred_line_length)
17351 }
17352 language_settings::SoftWrap::Bounded => {
17353 SoftWrap::Bounded(settings.preferred_line_length)
17354 }
17355 }
17356 }
17357
17358 pub fn set_soft_wrap_mode(
17359 &mut self,
17360 mode: language_settings::SoftWrap,
17361
17362 cx: &mut Context<Self>,
17363 ) {
17364 self.soft_wrap_mode_override = Some(mode);
17365 cx.notify();
17366 }
17367
17368 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17369 self.hard_wrap = hard_wrap;
17370 cx.notify();
17371 }
17372
17373 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17374 self.text_style_refinement = Some(style);
17375 }
17376
17377 /// called by the Element so we know what style we were most recently rendered with.
17378 pub(crate) fn set_style(
17379 &mut self,
17380 style: EditorStyle,
17381 window: &mut Window,
17382 cx: &mut Context<Self>,
17383 ) {
17384 // We intentionally do not inform the display map about the minimap style
17385 // so that wrapping is not recalculated and stays consistent for the editor
17386 // and its linked minimap.
17387 if !self.mode.is_minimap() {
17388 let rem_size = window.rem_size();
17389 self.display_map.update(cx, |map, cx| {
17390 map.set_font(
17391 style.text.font(),
17392 style.text.font_size.to_pixels(rem_size),
17393 cx,
17394 )
17395 });
17396 }
17397 self.style = Some(style);
17398 }
17399
17400 pub fn style(&self) -> Option<&EditorStyle> {
17401 self.style.as_ref()
17402 }
17403
17404 // Called by the element. This method is not designed to be called outside of the editor
17405 // element's layout code because it does not notify when rewrapping is computed synchronously.
17406 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17407 self.display_map
17408 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17409 }
17410
17411 pub fn set_soft_wrap(&mut self) {
17412 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17413 }
17414
17415 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17416 if self.soft_wrap_mode_override.is_some() {
17417 self.soft_wrap_mode_override.take();
17418 } else {
17419 let soft_wrap = match self.soft_wrap_mode(cx) {
17420 SoftWrap::GitDiff => return,
17421 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17422 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17423 language_settings::SoftWrap::None
17424 }
17425 };
17426 self.soft_wrap_mode_override = Some(soft_wrap);
17427 }
17428 cx.notify();
17429 }
17430
17431 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17432 let Some(workspace) = self.workspace() else {
17433 return;
17434 };
17435 let fs = workspace.read(cx).app_state().fs.clone();
17436 let current_show = TabBarSettings::get_global(cx).show;
17437 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17438 setting.show = Some(!current_show);
17439 });
17440 }
17441
17442 pub fn toggle_indent_guides(
17443 &mut self,
17444 _: &ToggleIndentGuides,
17445 _: &mut Window,
17446 cx: &mut Context<Self>,
17447 ) {
17448 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17449 self.buffer
17450 .read(cx)
17451 .language_settings(cx)
17452 .indent_guides
17453 .enabled
17454 });
17455 self.show_indent_guides = Some(!currently_enabled);
17456 cx.notify();
17457 }
17458
17459 fn should_show_indent_guides(&self) -> Option<bool> {
17460 self.show_indent_guides
17461 }
17462
17463 pub fn toggle_line_numbers(
17464 &mut self,
17465 _: &ToggleLineNumbers,
17466 _: &mut Window,
17467 cx: &mut Context<Self>,
17468 ) {
17469 let mut editor_settings = EditorSettings::get_global(cx).clone();
17470 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17471 EditorSettings::override_global(editor_settings, cx);
17472 }
17473
17474 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17475 if let Some(show_line_numbers) = self.show_line_numbers {
17476 return show_line_numbers;
17477 }
17478 EditorSettings::get_global(cx).gutter.line_numbers
17479 }
17480
17481 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17482 self.use_relative_line_numbers
17483 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17484 }
17485
17486 pub fn toggle_relative_line_numbers(
17487 &mut self,
17488 _: &ToggleRelativeLineNumbers,
17489 _: &mut Window,
17490 cx: &mut Context<Self>,
17491 ) {
17492 let is_relative = self.should_use_relative_line_numbers(cx);
17493 self.set_relative_line_number(Some(!is_relative), cx)
17494 }
17495
17496 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17497 self.use_relative_line_numbers = is_relative;
17498 cx.notify();
17499 }
17500
17501 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17502 self.show_gutter = show_gutter;
17503 cx.notify();
17504 }
17505
17506 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17507 self.show_scrollbars = ScrollbarAxes {
17508 horizontal: show,
17509 vertical: show,
17510 };
17511 cx.notify();
17512 }
17513
17514 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17515 self.show_scrollbars.vertical = show;
17516 cx.notify();
17517 }
17518
17519 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17520 self.show_scrollbars.horizontal = show;
17521 cx.notify();
17522 }
17523
17524 pub fn set_minimap_visibility(
17525 &mut self,
17526 minimap_visibility: MinimapVisibility,
17527 window: &mut Window,
17528 cx: &mut Context<Self>,
17529 ) {
17530 if self.minimap_visibility != minimap_visibility {
17531 if minimap_visibility.visible() && self.minimap.is_none() {
17532 let minimap_settings = EditorSettings::get_global(cx).minimap;
17533 self.minimap =
17534 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17535 }
17536 self.minimap_visibility = minimap_visibility;
17537 cx.notify();
17538 }
17539 }
17540
17541 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17542 self.set_show_scrollbars(false, cx);
17543 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17544 }
17545
17546 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17547 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17548 }
17549
17550 /// Normally the text in full mode and auto height editors is padded on the
17551 /// left side by roughly half a character width for improved hit testing.
17552 ///
17553 /// Use this method to disable this for cases where this is not wanted (e.g.
17554 /// if you want to align the editor text with some other text above or below)
17555 /// or if you want to add this padding to single-line editors.
17556 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17557 self.offset_content = offset_content;
17558 cx.notify();
17559 }
17560
17561 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17562 self.show_line_numbers = Some(show_line_numbers);
17563 cx.notify();
17564 }
17565
17566 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17567 self.disable_expand_excerpt_buttons = true;
17568 cx.notify();
17569 }
17570
17571 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17572 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17573 cx.notify();
17574 }
17575
17576 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17577 self.show_code_actions = Some(show_code_actions);
17578 cx.notify();
17579 }
17580
17581 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17582 self.show_runnables = Some(show_runnables);
17583 cx.notify();
17584 }
17585
17586 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17587 self.show_breakpoints = Some(show_breakpoints);
17588 cx.notify();
17589 }
17590
17591 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17592 if self.display_map.read(cx).masked != masked {
17593 self.display_map.update(cx, |map, _| map.masked = masked);
17594 }
17595 cx.notify()
17596 }
17597
17598 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17599 self.show_wrap_guides = Some(show_wrap_guides);
17600 cx.notify();
17601 }
17602
17603 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17604 self.show_indent_guides = Some(show_indent_guides);
17605 cx.notify();
17606 }
17607
17608 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17609 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17610 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17611 if let Some(dir) = file.abs_path(cx).parent() {
17612 return Some(dir.to_owned());
17613 }
17614 }
17615
17616 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17617 return Some(project_path.path.to_path_buf());
17618 }
17619 }
17620
17621 None
17622 }
17623
17624 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17625 self.active_excerpt(cx)?
17626 .1
17627 .read(cx)
17628 .file()
17629 .and_then(|f| f.as_local())
17630 }
17631
17632 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17633 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17634 let buffer = buffer.read(cx);
17635 if let Some(project_path) = buffer.project_path(cx) {
17636 let project = self.project.as_ref()?.read(cx);
17637 project.absolute_path(&project_path, cx)
17638 } else {
17639 buffer
17640 .file()
17641 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17642 }
17643 })
17644 }
17645
17646 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17647 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17648 let project_path = buffer.read(cx).project_path(cx)?;
17649 let project = self.project.as_ref()?.read(cx);
17650 let entry = project.entry_for_path(&project_path, cx)?;
17651 let path = entry.path.to_path_buf();
17652 Some(path)
17653 })
17654 }
17655
17656 pub fn reveal_in_finder(
17657 &mut self,
17658 _: &RevealInFileManager,
17659 _window: &mut Window,
17660 cx: &mut Context<Self>,
17661 ) {
17662 if let Some(target) = self.target_file(cx) {
17663 cx.reveal_path(&target.abs_path(cx));
17664 }
17665 }
17666
17667 pub fn copy_path(
17668 &mut self,
17669 _: &zed_actions::workspace::CopyPath,
17670 _window: &mut Window,
17671 cx: &mut Context<Self>,
17672 ) {
17673 if let Some(path) = self.target_file_abs_path(cx) {
17674 if let Some(path) = path.to_str() {
17675 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17676 }
17677 }
17678 }
17679
17680 pub fn copy_relative_path(
17681 &mut self,
17682 _: &zed_actions::workspace::CopyRelativePath,
17683 _window: &mut Window,
17684 cx: &mut Context<Self>,
17685 ) {
17686 if let Some(path) = self.target_file_path(cx) {
17687 if let Some(path) = path.to_str() {
17688 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17689 }
17690 }
17691 }
17692
17693 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17694 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17695 buffer.read(cx).project_path(cx)
17696 } else {
17697 None
17698 }
17699 }
17700
17701 // Returns true if the editor handled a go-to-line request
17702 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17703 maybe!({
17704 let breakpoint_store = self.breakpoint_store.as_ref()?;
17705
17706 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17707 else {
17708 self.clear_row_highlights::<ActiveDebugLine>();
17709 return None;
17710 };
17711
17712 let position = active_stack_frame.position;
17713 let buffer_id = position.buffer_id?;
17714 let snapshot = self
17715 .project
17716 .as_ref()?
17717 .read(cx)
17718 .buffer_for_id(buffer_id, cx)?
17719 .read(cx)
17720 .snapshot();
17721
17722 let mut handled = false;
17723 for (id, ExcerptRange { context, .. }) in
17724 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17725 {
17726 if context.start.cmp(&position, &snapshot).is_ge()
17727 || context.end.cmp(&position, &snapshot).is_lt()
17728 {
17729 continue;
17730 }
17731 let snapshot = self.buffer.read(cx).snapshot(cx);
17732 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17733
17734 handled = true;
17735 self.clear_row_highlights::<ActiveDebugLine>();
17736
17737 self.go_to_line::<ActiveDebugLine>(
17738 multibuffer_anchor,
17739 Some(cx.theme().colors().editor_debugger_active_line_background),
17740 window,
17741 cx,
17742 );
17743
17744 cx.notify();
17745 }
17746
17747 handled.then_some(())
17748 })
17749 .is_some()
17750 }
17751
17752 pub fn copy_file_name_without_extension(
17753 &mut self,
17754 _: &CopyFileNameWithoutExtension,
17755 _: &mut Window,
17756 cx: &mut Context<Self>,
17757 ) {
17758 if let Some(file) = self.target_file(cx) {
17759 if let Some(file_stem) = file.path().file_stem() {
17760 if let Some(name) = file_stem.to_str() {
17761 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17762 }
17763 }
17764 }
17765 }
17766
17767 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17768 if let Some(file) = self.target_file(cx) {
17769 if let Some(file_name) = file.path().file_name() {
17770 if let Some(name) = file_name.to_str() {
17771 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17772 }
17773 }
17774 }
17775 }
17776
17777 pub fn toggle_git_blame(
17778 &mut self,
17779 _: &::git::Blame,
17780 window: &mut Window,
17781 cx: &mut Context<Self>,
17782 ) {
17783 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17784
17785 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17786 self.start_git_blame(true, window, cx);
17787 }
17788
17789 cx.notify();
17790 }
17791
17792 pub fn toggle_git_blame_inline(
17793 &mut self,
17794 _: &ToggleGitBlameInline,
17795 window: &mut Window,
17796 cx: &mut Context<Self>,
17797 ) {
17798 self.toggle_git_blame_inline_internal(true, window, cx);
17799 cx.notify();
17800 }
17801
17802 pub fn open_git_blame_commit(
17803 &mut self,
17804 _: &OpenGitBlameCommit,
17805 window: &mut Window,
17806 cx: &mut Context<Self>,
17807 ) {
17808 self.open_git_blame_commit_internal(window, cx);
17809 }
17810
17811 fn open_git_blame_commit_internal(
17812 &mut self,
17813 window: &mut Window,
17814 cx: &mut Context<Self>,
17815 ) -> Option<()> {
17816 let blame = self.blame.as_ref()?;
17817 let snapshot = self.snapshot(window, cx);
17818 let cursor = self.selections.newest::<Point>(cx).head();
17819 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17820 let blame_entry = blame
17821 .update(cx, |blame, cx| {
17822 blame
17823 .blame_for_rows(
17824 &[RowInfo {
17825 buffer_id: Some(buffer.remote_id()),
17826 buffer_row: Some(point.row),
17827 ..Default::default()
17828 }],
17829 cx,
17830 )
17831 .next()
17832 })
17833 .flatten()?;
17834 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17835 let repo = blame.read(cx).repository(cx)?;
17836 let workspace = self.workspace()?.downgrade();
17837 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17838 None
17839 }
17840
17841 pub fn git_blame_inline_enabled(&self) -> bool {
17842 self.git_blame_inline_enabled
17843 }
17844
17845 pub fn toggle_selection_menu(
17846 &mut self,
17847 _: &ToggleSelectionMenu,
17848 _: &mut Window,
17849 cx: &mut Context<Self>,
17850 ) {
17851 self.show_selection_menu = self
17852 .show_selection_menu
17853 .map(|show_selections_menu| !show_selections_menu)
17854 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17855
17856 cx.notify();
17857 }
17858
17859 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17860 self.show_selection_menu
17861 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17862 }
17863
17864 fn start_git_blame(
17865 &mut self,
17866 user_triggered: bool,
17867 window: &mut Window,
17868 cx: &mut Context<Self>,
17869 ) {
17870 if let Some(project) = self.project.as_ref() {
17871 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17872 return;
17873 };
17874
17875 if buffer.read(cx).file().is_none() {
17876 return;
17877 }
17878
17879 let focused = self.focus_handle(cx).contains_focused(window, cx);
17880
17881 let project = project.clone();
17882 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17883 self.blame_subscription =
17884 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17885 self.blame = Some(blame);
17886 }
17887 }
17888
17889 fn toggle_git_blame_inline_internal(
17890 &mut self,
17891 user_triggered: bool,
17892 window: &mut Window,
17893 cx: &mut Context<Self>,
17894 ) {
17895 if self.git_blame_inline_enabled {
17896 self.git_blame_inline_enabled = false;
17897 self.show_git_blame_inline = false;
17898 self.show_git_blame_inline_delay_task.take();
17899 } else {
17900 self.git_blame_inline_enabled = true;
17901 self.start_git_blame_inline(user_triggered, window, cx);
17902 }
17903
17904 cx.notify();
17905 }
17906
17907 fn start_git_blame_inline(
17908 &mut self,
17909 user_triggered: bool,
17910 window: &mut Window,
17911 cx: &mut Context<Self>,
17912 ) {
17913 self.start_git_blame(user_triggered, window, cx);
17914
17915 if ProjectSettings::get_global(cx)
17916 .git
17917 .inline_blame_delay()
17918 .is_some()
17919 {
17920 self.start_inline_blame_timer(window, cx);
17921 } else {
17922 self.show_git_blame_inline = true
17923 }
17924 }
17925
17926 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17927 self.blame.as_ref()
17928 }
17929
17930 pub fn show_git_blame_gutter(&self) -> bool {
17931 self.show_git_blame_gutter
17932 }
17933
17934 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17935 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17936 }
17937
17938 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17939 self.show_git_blame_inline
17940 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17941 && !self.newest_selection_head_on_empty_line(cx)
17942 && self.has_blame_entries(cx)
17943 }
17944
17945 fn has_blame_entries(&self, cx: &App) -> bool {
17946 self.blame()
17947 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17948 }
17949
17950 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17951 let cursor_anchor = self.selections.newest_anchor().head();
17952
17953 let snapshot = self.buffer.read(cx).snapshot(cx);
17954 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17955
17956 snapshot.line_len(buffer_row) == 0
17957 }
17958
17959 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17960 let buffer_and_selection = maybe!({
17961 let selection = self.selections.newest::<Point>(cx);
17962 let selection_range = selection.range();
17963
17964 let multi_buffer = self.buffer().read(cx);
17965 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17966 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17967
17968 let (buffer, range, _) = if selection.reversed {
17969 buffer_ranges.first()
17970 } else {
17971 buffer_ranges.last()
17972 }?;
17973
17974 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17975 ..text::ToPoint::to_point(&range.end, &buffer).row;
17976 Some((
17977 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
17978 selection,
17979 ))
17980 });
17981
17982 let Some((buffer, selection)) = buffer_and_selection else {
17983 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
17984 };
17985
17986 let Some(project) = self.project.as_ref() else {
17987 return Task::ready(Err(anyhow!("editor does not have project")));
17988 };
17989
17990 project.update(cx, |project, cx| {
17991 project.get_permalink_to_line(&buffer, selection, cx)
17992 })
17993 }
17994
17995 pub fn copy_permalink_to_line(
17996 &mut self,
17997 _: &CopyPermalinkToLine,
17998 window: &mut Window,
17999 cx: &mut Context<Self>,
18000 ) {
18001 let permalink_task = self.get_permalink_to_line(cx);
18002 let workspace = self.workspace();
18003
18004 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18005 Ok(permalink) => {
18006 cx.update(|_, cx| {
18007 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18008 })
18009 .ok();
18010 }
18011 Err(err) => {
18012 let message = format!("Failed to copy permalink: {err}");
18013
18014 anyhow::Result::<()>::Err(err).log_err();
18015
18016 if let Some(workspace) = workspace {
18017 workspace
18018 .update_in(cx, |workspace, _, cx| {
18019 struct CopyPermalinkToLine;
18020
18021 workspace.show_toast(
18022 Toast::new(
18023 NotificationId::unique::<CopyPermalinkToLine>(),
18024 message,
18025 ),
18026 cx,
18027 )
18028 })
18029 .ok();
18030 }
18031 }
18032 })
18033 .detach();
18034 }
18035
18036 pub fn copy_file_location(
18037 &mut self,
18038 _: &CopyFileLocation,
18039 _: &mut Window,
18040 cx: &mut Context<Self>,
18041 ) {
18042 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18043 if let Some(file) = self.target_file(cx) {
18044 if let Some(path) = file.path().to_str() {
18045 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18046 }
18047 }
18048 }
18049
18050 pub fn open_permalink_to_line(
18051 &mut self,
18052 _: &OpenPermalinkToLine,
18053 window: &mut Window,
18054 cx: &mut Context<Self>,
18055 ) {
18056 let permalink_task = self.get_permalink_to_line(cx);
18057 let workspace = self.workspace();
18058
18059 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18060 Ok(permalink) => {
18061 cx.update(|_, cx| {
18062 cx.open_url(permalink.as_ref());
18063 })
18064 .ok();
18065 }
18066 Err(err) => {
18067 let message = format!("Failed to open permalink: {err}");
18068
18069 anyhow::Result::<()>::Err(err).log_err();
18070
18071 if let Some(workspace) = workspace {
18072 workspace
18073 .update(cx, |workspace, cx| {
18074 struct OpenPermalinkToLine;
18075
18076 workspace.show_toast(
18077 Toast::new(
18078 NotificationId::unique::<OpenPermalinkToLine>(),
18079 message,
18080 ),
18081 cx,
18082 )
18083 })
18084 .ok();
18085 }
18086 }
18087 })
18088 .detach();
18089 }
18090
18091 pub fn insert_uuid_v4(
18092 &mut self,
18093 _: &InsertUuidV4,
18094 window: &mut Window,
18095 cx: &mut Context<Self>,
18096 ) {
18097 self.insert_uuid(UuidVersion::V4, window, cx);
18098 }
18099
18100 pub fn insert_uuid_v7(
18101 &mut self,
18102 _: &InsertUuidV7,
18103 window: &mut Window,
18104 cx: &mut Context<Self>,
18105 ) {
18106 self.insert_uuid(UuidVersion::V7, window, cx);
18107 }
18108
18109 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18110 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
18111 self.transact(window, cx, |this, window, cx| {
18112 let edits = this
18113 .selections
18114 .all::<Point>(cx)
18115 .into_iter()
18116 .map(|selection| {
18117 let uuid = match version {
18118 UuidVersion::V4 => uuid::Uuid::new_v4(),
18119 UuidVersion::V7 => uuid::Uuid::now_v7(),
18120 };
18121
18122 (selection.range(), uuid.to_string())
18123 });
18124 this.edit(edits, cx);
18125 this.refresh_inline_completion(true, false, window, cx);
18126 });
18127 }
18128
18129 pub fn open_selections_in_multibuffer(
18130 &mut self,
18131 _: &OpenSelectionsInMultibuffer,
18132 window: &mut Window,
18133 cx: &mut Context<Self>,
18134 ) {
18135 let multibuffer = self.buffer.read(cx);
18136
18137 let Some(buffer) = multibuffer.as_singleton() else {
18138 return;
18139 };
18140
18141 let Some(workspace) = self.workspace() else {
18142 return;
18143 };
18144
18145 let locations = self
18146 .selections
18147 .disjoint_anchors()
18148 .iter()
18149 .map(|range| Location {
18150 buffer: buffer.clone(),
18151 range: range.start.text_anchor..range.end.text_anchor,
18152 })
18153 .collect::<Vec<_>>();
18154
18155 let title = multibuffer.title(cx).to_string();
18156
18157 cx.spawn_in(window, async move |_, cx| {
18158 workspace.update_in(cx, |workspace, window, cx| {
18159 Self::open_locations_in_multibuffer(
18160 workspace,
18161 locations,
18162 format!("Selections for '{title}'"),
18163 false,
18164 MultibufferSelectionMode::All,
18165 window,
18166 cx,
18167 );
18168 })
18169 })
18170 .detach();
18171 }
18172
18173 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18174 /// last highlight added will be used.
18175 ///
18176 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18177 pub fn highlight_rows<T: 'static>(
18178 &mut self,
18179 range: Range<Anchor>,
18180 color: Hsla,
18181 options: RowHighlightOptions,
18182 cx: &mut Context<Self>,
18183 ) {
18184 let snapshot = self.buffer().read(cx).snapshot(cx);
18185 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18186 let ix = row_highlights.binary_search_by(|highlight| {
18187 Ordering::Equal
18188 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18189 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18190 });
18191
18192 if let Err(mut ix) = ix {
18193 let index = post_inc(&mut self.highlight_order);
18194
18195 // If this range intersects with the preceding highlight, then merge it with
18196 // the preceding highlight. Otherwise insert a new highlight.
18197 let mut merged = false;
18198 if ix > 0 {
18199 let prev_highlight = &mut row_highlights[ix - 1];
18200 if prev_highlight
18201 .range
18202 .end
18203 .cmp(&range.start, &snapshot)
18204 .is_ge()
18205 {
18206 ix -= 1;
18207 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18208 prev_highlight.range.end = range.end;
18209 }
18210 merged = true;
18211 prev_highlight.index = index;
18212 prev_highlight.color = color;
18213 prev_highlight.options = options;
18214 }
18215 }
18216
18217 if !merged {
18218 row_highlights.insert(
18219 ix,
18220 RowHighlight {
18221 range: range.clone(),
18222 index,
18223 color,
18224 options,
18225 type_id: TypeId::of::<T>(),
18226 },
18227 );
18228 }
18229
18230 // If any of the following highlights intersect with this one, merge them.
18231 while let Some(next_highlight) = row_highlights.get(ix + 1) {
18232 let highlight = &row_highlights[ix];
18233 if next_highlight
18234 .range
18235 .start
18236 .cmp(&highlight.range.end, &snapshot)
18237 .is_le()
18238 {
18239 if next_highlight
18240 .range
18241 .end
18242 .cmp(&highlight.range.end, &snapshot)
18243 .is_gt()
18244 {
18245 row_highlights[ix].range.end = next_highlight.range.end;
18246 }
18247 row_highlights.remove(ix + 1);
18248 } else {
18249 break;
18250 }
18251 }
18252 }
18253 }
18254
18255 /// Remove any highlighted row ranges of the given type that intersect the
18256 /// given ranges.
18257 pub fn remove_highlighted_rows<T: 'static>(
18258 &mut self,
18259 ranges_to_remove: Vec<Range<Anchor>>,
18260 cx: &mut Context<Self>,
18261 ) {
18262 let snapshot = self.buffer().read(cx).snapshot(cx);
18263 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18264 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18265 row_highlights.retain(|highlight| {
18266 while let Some(range_to_remove) = ranges_to_remove.peek() {
18267 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18268 Ordering::Less | Ordering::Equal => {
18269 ranges_to_remove.next();
18270 }
18271 Ordering::Greater => {
18272 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18273 Ordering::Less | Ordering::Equal => {
18274 return false;
18275 }
18276 Ordering::Greater => break,
18277 }
18278 }
18279 }
18280 }
18281
18282 true
18283 })
18284 }
18285
18286 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18287 pub fn clear_row_highlights<T: 'static>(&mut self) {
18288 self.highlighted_rows.remove(&TypeId::of::<T>());
18289 }
18290
18291 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18292 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18293 self.highlighted_rows
18294 .get(&TypeId::of::<T>())
18295 .map_or(&[] as &[_], |vec| vec.as_slice())
18296 .iter()
18297 .map(|highlight| (highlight.range.clone(), highlight.color))
18298 }
18299
18300 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18301 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18302 /// Allows to ignore certain kinds of highlights.
18303 pub fn highlighted_display_rows(
18304 &self,
18305 window: &mut Window,
18306 cx: &mut App,
18307 ) -> BTreeMap<DisplayRow, LineHighlight> {
18308 let snapshot = self.snapshot(window, cx);
18309 let mut used_highlight_orders = HashMap::default();
18310 self.highlighted_rows
18311 .iter()
18312 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18313 .fold(
18314 BTreeMap::<DisplayRow, LineHighlight>::new(),
18315 |mut unique_rows, highlight| {
18316 let start = highlight.range.start.to_display_point(&snapshot);
18317 let end = highlight.range.end.to_display_point(&snapshot);
18318 let start_row = start.row().0;
18319 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18320 && end.column() == 0
18321 {
18322 end.row().0.saturating_sub(1)
18323 } else {
18324 end.row().0
18325 };
18326 for row in start_row..=end_row {
18327 let used_index =
18328 used_highlight_orders.entry(row).or_insert(highlight.index);
18329 if highlight.index >= *used_index {
18330 *used_index = highlight.index;
18331 unique_rows.insert(
18332 DisplayRow(row),
18333 LineHighlight {
18334 include_gutter: highlight.options.include_gutter,
18335 border: None,
18336 background: highlight.color.into(),
18337 type_id: Some(highlight.type_id),
18338 },
18339 );
18340 }
18341 }
18342 unique_rows
18343 },
18344 )
18345 }
18346
18347 pub fn highlighted_display_row_for_autoscroll(
18348 &self,
18349 snapshot: &DisplaySnapshot,
18350 ) -> Option<DisplayRow> {
18351 self.highlighted_rows
18352 .values()
18353 .flat_map(|highlighted_rows| highlighted_rows.iter())
18354 .filter_map(|highlight| {
18355 if highlight.options.autoscroll {
18356 Some(highlight.range.start.to_display_point(snapshot).row())
18357 } else {
18358 None
18359 }
18360 })
18361 .min()
18362 }
18363
18364 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18365 self.highlight_background::<SearchWithinRange>(
18366 ranges,
18367 |colors| colors.editor_document_highlight_read_background,
18368 cx,
18369 )
18370 }
18371
18372 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18373 self.breadcrumb_header = Some(new_header);
18374 }
18375
18376 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18377 self.clear_background_highlights::<SearchWithinRange>(cx);
18378 }
18379
18380 pub fn highlight_background<T: 'static>(
18381 &mut self,
18382 ranges: &[Range<Anchor>],
18383 color_fetcher: fn(&ThemeColors) -> Hsla,
18384 cx: &mut Context<Self>,
18385 ) {
18386 self.background_highlights
18387 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
18388 self.scrollbar_marker_state.dirty = true;
18389 cx.notify();
18390 }
18391
18392 pub fn clear_background_highlights<T: 'static>(
18393 &mut self,
18394 cx: &mut Context<Self>,
18395 ) -> Option<BackgroundHighlight> {
18396 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
18397 if !text_highlights.1.is_empty() {
18398 self.scrollbar_marker_state.dirty = true;
18399 cx.notify();
18400 }
18401 Some(text_highlights)
18402 }
18403
18404 pub fn highlight_gutter<T: 'static>(
18405 &mut self,
18406 ranges: impl Into<Vec<Range<Anchor>>>,
18407 color_fetcher: fn(&App) -> Hsla,
18408 cx: &mut Context<Self>,
18409 ) {
18410 self.gutter_highlights
18411 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
18412 cx.notify();
18413 }
18414
18415 pub fn clear_gutter_highlights<T: 'static>(
18416 &mut self,
18417 cx: &mut Context<Self>,
18418 ) -> Option<GutterHighlight> {
18419 cx.notify();
18420 self.gutter_highlights.remove(&TypeId::of::<T>())
18421 }
18422
18423 pub fn insert_gutter_highlight<T: 'static>(
18424 &mut self,
18425 range: Range<Anchor>,
18426 color_fetcher: fn(&App) -> Hsla,
18427 cx: &mut Context<Self>,
18428 ) {
18429 let snapshot = self.buffer().read(cx).snapshot(cx);
18430 let mut highlights = self
18431 .gutter_highlights
18432 .remove(&TypeId::of::<T>())
18433 .map(|(_, highlights)| highlights)
18434 .unwrap_or_default();
18435 let ix = highlights.binary_search_by(|highlight| {
18436 Ordering::Equal
18437 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
18438 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
18439 });
18440 if let Err(ix) = ix {
18441 highlights.insert(ix, range);
18442 }
18443 self.gutter_highlights
18444 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
18445 }
18446
18447 pub fn remove_gutter_highlights<T: 'static>(
18448 &mut self,
18449 ranges_to_remove: Vec<Range<Anchor>>,
18450 cx: &mut Context<Self>,
18451 ) {
18452 let snapshot = self.buffer().read(cx).snapshot(cx);
18453 let Some((color_fetcher, mut gutter_highlights)) =
18454 self.gutter_highlights.remove(&TypeId::of::<T>())
18455 else {
18456 return;
18457 };
18458 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18459 gutter_highlights.retain(|highlight| {
18460 while let Some(range_to_remove) = ranges_to_remove.peek() {
18461 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
18462 Ordering::Less | Ordering::Equal => {
18463 ranges_to_remove.next();
18464 }
18465 Ordering::Greater => {
18466 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
18467 Ordering::Less | Ordering::Equal => {
18468 return false;
18469 }
18470 Ordering::Greater => break,
18471 }
18472 }
18473 }
18474 }
18475
18476 true
18477 });
18478 self.gutter_highlights
18479 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
18480 }
18481
18482 #[cfg(feature = "test-support")]
18483 pub fn all_text_background_highlights(
18484 &self,
18485 window: &mut Window,
18486 cx: &mut Context<Self>,
18487 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18488 let snapshot = self.snapshot(window, cx);
18489 let buffer = &snapshot.buffer_snapshot;
18490 let start = buffer.anchor_before(0);
18491 let end = buffer.anchor_after(buffer.len());
18492 let theme = cx.theme().colors();
18493 self.background_highlights_in_range(start..end, &snapshot, theme)
18494 }
18495
18496 #[cfg(feature = "test-support")]
18497 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18498 let snapshot = self.buffer().read(cx).snapshot(cx);
18499
18500 let highlights = self
18501 .background_highlights
18502 .get(&TypeId::of::<items::BufferSearchHighlights>());
18503
18504 if let Some((_color, ranges)) = highlights {
18505 ranges
18506 .iter()
18507 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
18508 .collect_vec()
18509 } else {
18510 vec![]
18511 }
18512 }
18513
18514 fn document_highlights_for_position<'a>(
18515 &'a self,
18516 position: Anchor,
18517 buffer: &'a MultiBufferSnapshot,
18518 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
18519 let read_highlights = self
18520 .background_highlights
18521 .get(&TypeId::of::<DocumentHighlightRead>())
18522 .map(|h| &h.1);
18523 let write_highlights = self
18524 .background_highlights
18525 .get(&TypeId::of::<DocumentHighlightWrite>())
18526 .map(|h| &h.1);
18527 let left_position = position.bias_left(buffer);
18528 let right_position = position.bias_right(buffer);
18529 read_highlights
18530 .into_iter()
18531 .chain(write_highlights)
18532 .flat_map(move |ranges| {
18533 let start_ix = match ranges.binary_search_by(|probe| {
18534 let cmp = probe.end.cmp(&left_position, buffer);
18535 if cmp.is_ge() {
18536 Ordering::Greater
18537 } else {
18538 Ordering::Less
18539 }
18540 }) {
18541 Ok(i) | Err(i) => i,
18542 };
18543
18544 ranges[start_ix..]
18545 .iter()
18546 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
18547 })
18548 }
18549
18550 pub fn has_background_highlights<T: 'static>(&self) -> bool {
18551 self.background_highlights
18552 .get(&TypeId::of::<T>())
18553 .map_or(false, |(_, highlights)| !highlights.is_empty())
18554 }
18555
18556 pub fn background_highlights_in_range(
18557 &self,
18558 search_range: Range<Anchor>,
18559 display_snapshot: &DisplaySnapshot,
18560 theme: &ThemeColors,
18561 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18562 let mut results = Vec::new();
18563 for (color_fetcher, ranges) in self.background_highlights.values() {
18564 let color = color_fetcher(theme);
18565 let start_ix = match ranges.binary_search_by(|probe| {
18566 let cmp = probe
18567 .end
18568 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18569 if cmp.is_gt() {
18570 Ordering::Greater
18571 } else {
18572 Ordering::Less
18573 }
18574 }) {
18575 Ok(i) | Err(i) => i,
18576 };
18577 for range in &ranges[start_ix..] {
18578 if range
18579 .start
18580 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18581 .is_ge()
18582 {
18583 break;
18584 }
18585
18586 let start = range.start.to_display_point(display_snapshot);
18587 let end = range.end.to_display_point(display_snapshot);
18588 results.push((start..end, color))
18589 }
18590 }
18591 results
18592 }
18593
18594 pub fn background_highlight_row_ranges<T: 'static>(
18595 &self,
18596 search_range: Range<Anchor>,
18597 display_snapshot: &DisplaySnapshot,
18598 count: usize,
18599 ) -> Vec<RangeInclusive<DisplayPoint>> {
18600 let mut results = Vec::new();
18601 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
18602 return vec![];
18603 };
18604
18605 let start_ix = match ranges.binary_search_by(|probe| {
18606 let cmp = probe
18607 .end
18608 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18609 if cmp.is_gt() {
18610 Ordering::Greater
18611 } else {
18612 Ordering::Less
18613 }
18614 }) {
18615 Ok(i) | Err(i) => i,
18616 };
18617 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18618 if let (Some(start_display), Some(end_display)) = (start, end) {
18619 results.push(
18620 start_display.to_display_point(display_snapshot)
18621 ..=end_display.to_display_point(display_snapshot),
18622 );
18623 }
18624 };
18625 let mut start_row: Option<Point> = None;
18626 let mut end_row: Option<Point> = None;
18627 if ranges.len() > count {
18628 return Vec::new();
18629 }
18630 for range in &ranges[start_ix..] {
18631 if range
18632 .start
18633 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18634 .is_ge()
18635 {
18636 break;
18637 }
18638 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
18639 if let Some(current_row) = &end_row {
18640 if end.row == current_row.row {
18641 continue;
18642 }
18643 }
18644 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
18645 if start_row.is_none() {
18646 assert_eq!(end_row, None);
18647 start_row = Some(start);
18648 end_row = Some(end);
18649 continue;
18650 }
18651 if let Some(current_end) = end_row.as_mut() {
18652 if start.row > current_end.row + 1 {
18653 push_region(start_row, end_row);
18654 start_row = Some(start);
18655 end_row = Some(end);
18656 } else {
18657 // Merge two hunks.
18658 *current_end = end;
18659 }
18660 } else {
18661 unreachable!();
18662 }
18663 }
18664 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18665 push_region(start_row, end_row);
18666 results
18667 }
18668
18669 pub fn gutter_highlights_in_range(
18670 &self,
18671 search_range: Range<Anchor>,
18672 display_snapshot: &DisplaySnapshot,
18673 cx: &App,
18674 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18675 let mut results = Vec::new();
18676 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18677 let color = color_fetcher(cx);
18678 let start_ix = match ranges.binary_search_by(|probe| {
18679 let cmp = probe
18680 .end
18681 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18682 if cmp.is_gt() {
18683 Ordering::Greater
18684 } else {
18685 Ordering::Less
18686 }
18687 }) {
18688 Ok(i) | Err(i) => i,
18689 };
18690 for range in &ranges[start_ix..] {
18691 if range
18692 .start
18693 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18694 .is_ge()
18695 {
18696 break;
18697 }
18698
18699 let start = range.start.to_display_point(display_snapshot);
18700 let end = range.end.to_display_point(display_snapshot);
18701 results.push((start..end, color))
18702 }
18703 }
18704 results
18705 }
18706
18707 /// Get the text ranges corresponding to the redaction query
18708 pub fn redacted_ranges(
18709 &self,
18710 search_range: Range<Anchor>,
18711 display_snapshot: &DisplaySnapshot,
18712 cx: &App,
18713 ) -> Vec<Range<DisplayPoint>> {
18714 display_snapshot
18715 .buffer_snapshot
18716 .redacted_ranges(search_range, |file| {
18717 if let Some(file) = file {
18718 file.is_private()
18719 && EditorSettings::get(
18720 Some(SettingsLocation {
18721 worktree_id: file.worktree_id(cx),
18722 path: file.path().as_ref(),
18723 }),
18724 cx,
18725 )
18726 .redact_private_values
18727 } else {
18728 false
18729 }
18730 })
18731 .map(|range| {
18732 range.start.to_display_point(display_snapshot)
18733 ..range.end.to_display_point(display_snapshot)
18734 })
18735 .collect()
18736 }
18737
18738 pub fn highlight_text<T: 'static>(
18739 &mut self,
18740 ranges: Vec<Range<Anchor>>,
18741 style: HighlightStyle,
18742 cx: &mut Context<Self>,
18743 ) {
18744 self.display_map.update(cx, |map, _| {
18745 map.highlight_text(TypeId::of::<T>(), ranges, style)
18746 });
18747 cx.notify();
18748 }
18749
18750 pub(crate) fn highlight_inlays<T: 'static>(
18751 &mut self,
18752 highlights: Vec<InlayHighlight>,
18753 style: HighlightStyle,
18754 cx: &mut Context<Self>,
18755 ) {
18756 self.display_map.update(cx, |map, _| {
18757 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18758 });
18759 cx.notify();
18760 }
18761
18762 pub fn text_highlights<'a, T: 'static>(
18763 &'a self,
18764 cx: &'a App,
18765 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18766 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18767 }
18768
18769 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18770 let cleared = self
18771 .display_map
18772 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18773 if cleared {
18774 cx.notify();
18775 }
18776 }
18777
18778 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18779 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18780 && self.focus_handle.is_focused(window)
18781 }
18782
18783 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18784 self.show_cursor_when_unfocused = is_enabled;
18785 cx.notify();
18786 }
18787
18788 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18789 cx.notify();
18790 }
18791
18792 fn on_debug_session_event(
18793 &mut self,
18794 _session: Entity<Session>,
18795 event: &SessionEvent,
18796 cx: &mut Context<Self>,
18797 ) {
18798 match event {
18799 SessionEvent::InvalidateInlineValue => {
18800 self.refresh_inline_values(cx);
18801 }
18802 _ => {}
18803 }
18804 }
18805
18806 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18807 let Some(project) = self.project.clone() else {
18808 return;
18809 };
18810
18811 if !self.inline_value_cache.enabled {
18812 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18813 self.splice_inlays(&inlays, Vec::new(), cx);
18814 return;
18815 }
18816
18817 let current_execution_position = self
18818 .highlighted_rows
18819 .get(&TypeId::of::<ActiveDebugLine>())
18820 .and_then(|lines| lines.last().map(|line| line.range.start));
18821
18822 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18823 let inline_values = editor
18824 .update(cx, |editor, cx| {
18825 let Some(current_execution_position) = current_execution_position else {
18826 return Some(Task::ready(Ok(Vec::new())));
18827 };
18828
18829 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18830 let snapshot = buffer.snapshot(cx);
18831
18832 let excerpt = snapshot.excerpt_containing(
18833 current_execution_position..current_execution_position,
18834 )?;
18835
18836 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18837 })?;
18838
18839 let range =
18840 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18841
18842 project.inline_values(buffer, range, cx)
18843 })
18844 .ok()
18845 .flatten()?
18846 .await
18847 .context("refreshing debugger inlays")
18848 .log_err()?;
18849
18850 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18851
18852 for (buffer_id, inline_value) in inline_values
18853 .into_iter()
18854 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18855 {
18856 buffer_inline_values
18857 .entry(buffer_id)
18858 .or_default()
18859 .push(inline_value);
18860 }
18861
18862 editor
18863 .update(cx, |editor, cx| {
18864 let snapshot = editor.buffer.read(cx).snapshot(cx);
18865 let mut new_inlays = Vec::default();
18866
18867 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18868 let buffer_id = buffer_snapshot.remote_id();
18869 buffer_inline_values
18870 .get(&buffer_id)
18871 .into_iter()
18872 .flatten()
18873 .for_each(|hint| {
18874 let inlay = Inlay::debugger_hint(
18875 post_inc(&mut editor.next_inlay_id),
18876 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18877 hint.text(),
18878 );
18879
18880 new_inlays.push(inlay);
18881 });
18882 }
18883
18884 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18885 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18886
18887 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18888 })
18889 .ok()?;
18890 Some(())
18891 });
18892 }
18893
18894 fn on_buffer_event(
18895 &mut self,
18896 multibuffer: &Entity<MultiBuffer>,
18897 event: &multi_buffer::Event,
18898 window: &mut Window,
18899 cx: &mut Context<Self>,
18900 ) {
18901 match event {
18902 multi_buffer::Event::Edited {
18903 singleton_buffer_edited,
18904 edited_buffer,
18905 } => {
18906 self.scrollbar_marker_state.dirty = true;
18907 self.active_indent_guides_state.dirty = true;
18908 self.refresh_active_diagnostics(cx);
18909 self.refresh_code_actions(window, cx);
18910 self.refresh_selected_text_highlights(true, window, cx);
18911 refresh_matching_bracket_highlights(self, window, cx);
18912 if self.has_active_inline_completion() {
18913 self.update_visible_inline_completion(window, cx);
18914 }
18915 if let Some(project) = self.project.as_ref() {
18916 if let Some(edited_buffer) = edited_buffer {
18917 project.update(cx, |project, cx| {
18918 self.registered_buffers
18919 .entry(edited_buffer.read(cx).remote_id())
18920 .or_insert_with(|| {
18921 project
18922 .register_buffer_with_language_servers(&edited_buffer, cx)
18923 });
18924 });
18925 if edited_buffer.read(cx).file().is_some() {
18926 self.pull_diagnostics(
18927 Some(edited_buffer.read(cx).remote_id()),
18928 window,
18929 cx,
18930 );
18931 }
18932 }
18933 }
18934 cx.emit(EditorEvent::BufferEdited);
18935 cx.emit(SearchEvent::MatchesInvalidated);
18936 if *singleton_buffer_edited {
18937 if let Some(buffer) = edited_buffer {
18938 if buffer.read(cx).file().is_none() {
18939 cx.emit(EditorEvent::TitleChanged);
18940 }
18941 }
18942 if let Some(project) = &self.project {
18943 #[allow(clippy::mutable_key_type)]
18944 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18945 multibuffer
18946 .all_buffers()
18947 .into_iter()
18948 .filter_map(|buffer| {
18949 buffer.update(cx, |buffer, cx| {
18950 let language = buffer.language()?;
18951 let should_discard = project.update(cx, |project, cx| {
18952 project.is_local()
18953 && !project.has_language_servers_for(buffer, cx)
18954 });
18955 should_discard.not().then_some(language.clone())
18956 })
18957 })
18958 .collect::<HashSet<_>>()
18959 });
18960 if !languages_affected.is_empty() {
18961 self.refresh_inlay_hints(
18962 InlayHintRefreshReason::BufferEdited(languages_affected),
18963 cx,
18964 );
18965 }
18966 }
18967 }
18968
18969 let Some(project) = &self.project else { return };
18970 let (telemetry, is_via_ssh) = {
18971 let project = project.read(cx);
18972 let telemetry = project.client().telemetry().clone();
18973 let is_via_ssh = project.is_via_ssh();
18974 (telemetry, is_via_ssh)
18975 };
18976 refresh_linked_ranges(self, window, cx);
18977 telemetry.log_edit_event("editor", is_via_ssh);
18978 }
18979 multi_buffer::Event::ExcerptsAdded {
18980 buffer,
18981 predecessor,
18982 excerpts,
18983 } => {
18984 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18985 let buffer_id = buffer.read(cx).remote_id();
18986 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
18987 if let Some(project) = &self.project {
18988 update_uncommitted_diff_for_buffer(
18989 cx.entity(),
18990 project,
18991 [buffer.clone()],
18992 self.buffer.clone(),
18993 cx,
18994 )
18995 .detach();
18996 }
18997 }
18998 cx.emit(EditorEvent::ExcerptsAdded {
18999 buffer: buffer.clone(),
19000 predecessor: *predecessor,
19001 excerpts: excerpts.clone(),
19002 });
19003 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19004 }
19005 multi_buffer::Event::ExcerptsRemoved {
19006 ids,
19007 removed_buffer_ids,
19008 } => {
19009 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19010 let buffer = self.buffer.read(cx);
19011 self.registered_buffers
19012 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19013 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19014 cx.emit(EditorEvent::ExcerptsRemoved {
19015 ids: ids.clone(),
19016 removed_buffer_ids: removed_buffer_ids.clone(),
19017 })
19018 }
19019 multi_buffer::Event::ExcerptsEdited {
19020 excerpt_ids,
19021 buffer_ids,
19022 } => {
19023 self.display_map.update(cx, |map, cx| {
19024 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19025 });
19026 cx.emit(EditorEvent::ExcerptsEdited {
19027 ids: excerpt_ids.clone(),
19028 })
19029 }
19030 multi_buffer::Event::ExcerptsExpanded { ids } => {
19031 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19032 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19033 }
19034 multi_buffer::Event::Reparsed(buffer_id) => {
19035 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19036 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19037
19038 cx.emit(EditorEvent::Reparsed(*buffer_id));
19039 }
19040 multi_buffer::Event::DiffHunksToggled => {
19041 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19042 }
19043 multi_buffer::Event::LanguageChanged(buffer_id) => {
19044 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19045 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19046 cx.emit(EditorEvent::Reparsed(*buffer_id));
19047 cx.notify();
19048 }
19049 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19050 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19051 multi_buffer::Event::FileHandleChanged
19052 | multi_buffer::Event::Reloaded
19053 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19054 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19055 multi_buffer::Event::DiagnosticsUpdated => {
19056 self.update_diagnostics_state(window, cx);
19057 }
19058 _ => {}
19059 };
19060 }
19061
19062 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19063 self.refresh_active_diagnostics(cx);
19064 self.refresh_inline_diagnostics(true, window, cx);
19065 self.scrollbar_marker_state.dirty = true;
19066 cx.notify();
19067 }
19068
19069 pub fn start_temporary_diff_override(&mut self) {
19070 self.load_diff_task.take();
19071 self.temporary_diff_override = true;
19072 }
19073
19074 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19075 self.temporary_diff_override = false;
19076 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19077 self.buffer.update(cx, |buffer, cx| {
19078 buffer.set_all_diff_hunks_collapsed(cx);
19079 });
19080
19081 if let Some(project) = self.project.clone() {
19082 self.load_diff_task = Some(
19083 update_uncommitted_diff_for_buffer(
19084 cx.entity(),
19085 &project,
19086 self.buffer.read(cx).all_buffers(),
19087 self.buffer.clone(),
19088 cx,
19089 )
19090 .shared(),
19091 );
19092 }
19093 }
19094
19095 fn on_display_map_changed(
19096 &mut self,
19097 _: Entity<DisplayMap>,
19098 _: &mut Window,
19099 cx: &mut Context<Self>,
19100 ) {
19101 cx.notify();
19102 }
19103
19104 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19105 let new_severity = if self.diagnostics_enabled() {
19106 EditorSettings::get_global(cx)
19107 .diagnostics_max_severity
19108 .unwrap_or(DiagnosticSeverity::Hint)
19109 } else {
19110 DiagnosticSeverity::Off
19111 };
19112 self.set_max_diagnostics_severity(new_severity, cx);
19113 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19114 self.update_edit_prediction_settings(cx);
19115 self.refresh_inline_completion(true, false, window, cx);
19116 self.refresh_inlay_hints(
19117 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19118 self.selections.newest_anchor().head(),
19119 &self.buffer.read(cx).snapshot(cx),
19120 cx,
19121 )),
19122 cx,
19123 );
19124
19125 let old_cursor_shape = self.cursor_shape;
19126
19127 {
19128 let editor_settings = EditorSettings::get_global(cx);
19129 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19130 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19131 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19132 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19133 self.drag_and_drop_selection_enabled = editor_settings.drag_and_drop_selection;
19134 }
19135
19136 if old_cursor_shape != self.cursor_shape {
19137 cx.emit(EditorEvent::CursorShapeChanged);
19138 }
19139
19140 let project_settings = ProjectSettings::get_global(cx);
19141 self.serialize_dirty_buffers =
19142 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19143
19144 if self.mode.is_full() {
19145 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19146 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19147 if self.show_inline_diagnostics != show_inline_diagnostics {
19148 self.show_inline_diagnostics = show_inline_diagnostics;
19149 self.refresh_inline_diagnostics(false, window, cx);
19150 }
19151
19152 if self.git_blame_inline_enabled != inline_blame_enabled {
19153 self.toggle_git_blame_inline_internal(false, window, cx);
19154 }
19155
19156 let minimap_settings = EditorSettings::get_global(cx).minimap;
19157 if self.minimap_visibility != MinimapVisibility::Disabled {
19158 if self.minimap_visibility.settings_visibility()
19159 != minimap_settings.minimap_enabled()
19160 {
19161 self.set_minimap_visibility(
19162 MinimapVisibility::for_mode(self.mode(), cx),
19163 window,
19164 cx,
19165 );
19166 } else if let Some(minimap_entity) = self.minimap.as_ref() {
19167 minimap_entity.update(cx, |minimap_editor, cx| {
19168 minimap_editor.update_minimap_configuration(minimap_settings, cx)
19169 })
19170 }
19171 }
19172 }
19173
19174 cx.notify();
19175 }
19176
19177 pub fn set_searchable(&mut self, searchable: bool) {
19178 self.searchable = searchable;
19179 }
19180
19181 pub fn searchable(&self) -> bool {
19182 self.searchable
19183 }
19184
19185 fn open_proposed_changes_editor(
19186 &mut self,
19187 _: &OpenProposedChangesEditor,
19188 window: &mut Window,
19189 cx: &mut Context<Self>,
19190 ) {
19191 let Some(workspace) = self.workspace() else {
19192 cx.propagate();
19193 return;
19194 };
19195
19196 let selections = self.selections.all::<usize>(cx);
19197 let multi_buffer = self.buffer.read(cx);
19198 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19199 let mut new_selections_by_buffer = HashMap::default();
19200 for selection in selections {
19201 for (buffer, range, _) in
19202 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
19203 {
19204 let mut range = range.to_point(buffer);
19205 range.start.column = 0;
19206 range.end.column = buffer.line_len(range.end.row);
19207 new_selections_by_buffer
19208 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
19209 .or_insert(Vec::new())
19210 .push(range)
19211 }
19212 }
19213
19214 let proposed_changes_buffers = new_selections_by_buffer
19215 .into_iter()
19216 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
19217 .collect::<Vec<_>>();
19218 let proposed_changes_editor = cx.new(|cx| {
19219 ProposedChangesEditor::new(
19220 "Proposed changes",
19221 proposed_changes_buffers,
19222 self.project.clone(),
19223 window,
19224 cx,
19225 )
19226 });
19227
19228 window.defer(cx, move |window, cx| {
19229 workspace.update(cx, |workspace, cx| {
19230 workspace.active_pane().update(cx, |pane, cx| {
19231 pane.add_item(
19232 Box::new(proposed_changes_editor),
19233 true,
19234 true,
19235 None,
19236 window,
19237 cx,
19238 );
19239 });
19240 });
19241 });
19242 }
19243
19244 pub fn open_excerpts_in_split(
19245 &mut self,
19246 _: &OpenExcerptsSplit,
19247 window: &mut Window,
19248 cx: &mut Context<Self>,
19249 ) {
19250 self.open_excerpts_common(None, true, window, cx)
19251 }
19252
19253 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
19254 self.open_excerpts_common(None, false, window, cx)
19255 }
19256
19257 fn open_excerpts_common(
19258 &mut self,
19259 jump_data: Option<JumpData>,
19260 split: bool,
19261 window: &mut Window,
19262 cx: &mut Context<Self>,
19263 ) {
19264 let Some(workspace) = self.workspace() else {
19265 cx.propagate();
19266 return;
19267 };
19268
19269 if self.buffer.read(cx).is_singleton() {
19270 cx.propagate();
19271 return;
19272 }
19273
19274 let mut new_selections_by_buffer = HashMap::default();
19275 match &jump_data {
19276 Some(JumpData::MultiBufferPoint {
19277 excerpt_id,
19278 position,
19279 anchor,
19280 line_offset_from_top,
19281 }) => {
19282 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19283 if let Some(buffer) = multi_buffer_snapshot
19284 .buffer_id_for_excerpt(*excerpt_id)
19285 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
19286 {
19287 let buffer_snapshot = buffer.read(cx).snapshot();
19288 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
19289 language::ToPoint::to_point(anchor, &buffer_snapshot)
19290 } else {
19291 buffer_snapshot.clip_point(*position, Bias::Left)
19292 };
19293 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
19294 new_selections_by_buffer.insert(
19295 buffer,
19296 (
19297 vec![jump_to_offset..jump_to_offset],
19298 Some(*line_offset_from_top),
19299 ),
19300 );
19301 }
19302 }
19303 Some(JumpData::MultiBufferRow {
19304 row,
19305 line_offset_from_top,
19306 }) => {
19307 let point = MultiBufferPoint::new(row.0, 0);
19308 if let Some((buffer, buffer_point, _)) =
19309 self.buffer.read(cx).point_to_buffer_point(point, cx)
19310 {
19311 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
19312 new_selections_by_buffer
19313 .entry(buffer)
19314 .or_insert((Vec::new(), Some(*line_offset_from_top)))
19315 .0
19316 .push(buffer_offset..buffer_offset)
19317 }
19318 }
19319 None => {
19320 let selections = self.selections.all::<usize>(cx);
19321 let multi_buffer = self.buffer.read(cx);
19322 for selection in selections {
19323 for (snapshot, range, _, anchor) in multi_buffer
19324 .snapshot(cx)
19325 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
19326 {
19327 if let Some(anchor) = anchor {
19328 // selection is in a deleted hunk
19329 let Some(buffer_id) = anchor.buffer_id else {
19330 continue;
19331 };
19332 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
19333 continue;
19334 };
19335 let offset = text::ToOffset::to_offset(
19336 &anchor.text_anchor,
19337 &buffer_handle.read(cx).snapshot(),
19338 );
19339 let range = offset..offset;
19340 new_selections_by_buffer
19341 .entry(buffer_handle)
19342 .or_insert((Vec::new(), None))
19343 .0
19344 .push(range)
19345 } else {
19346 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
19347 else {
19348 continue;
19349 };
19350 new_selections_by_buffer
19351 .entry(buffer_handle)
19352 .or_insert((Vec::new(), None))
19353 .0
19354 .push(range)
19355 }
19356 }
19357 }
19358 }
19359 }
19360
19361 new_selections_by_buffer
19362 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
19363
19364 if new_selections_by_buffer.is_empty() {
19365 return;
19366 }
19367
19368 // We defer the pane interaction because we ourselves are a workspace item
19369 // and activating a new item causes the pane to call a method on us reentrantly,
19370 // which panics if we're on the stack.
19371 window.defer(cx, move |window, cx| {
19372 workspace.update(cx, |workspace, cx| {
19373 let pane = if split {
19374 workspace.adjacent_pane(window, cx)
19375 } else {
19376 workspace.active_pane().clone()
19377 };
19378
19379 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
19380 let editor = buffer
19381 .read(cx)
19382 .file()
19383 .is_none()
19384 .then(|| {
19385 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
19386 // so `workspace.open_project_item` will never find them, always opening a new editor.
19387 // Instead, we try to activate the existing editor in the pane first.
19388 let (editor, pane_item_index) =
19389 pane.read(cx).items().enumerate().find_map(|(i, item)| {
19390 let editor = item.downcast::<Editor>()?;
19391 let singleton_buffer =
19392 editor.read(cx).buffer().read(cx).as_singleton()?;
19393 if singleton_buffer == buffer {
19394 Some((editor, i))
19395 } else {
19396 None
19397 }
19398 })?;
19399 pane.update(cx, |pane, cx| {
19400 pane.activate_item(pane_item_index, true, true, window, cx)
19401 });
19402 Some(editor)
19403 })
19404 .flatten()
19405 .unwrap_or_else(|| {
19406 workspace.open_project_item::<Self>(
19407 pane.clone(),
19408 buffer,
19409 true,
19410 true,
19411 window,
19412 cx,
19413 )
19414 });
19415
19416 editor.update(cx, |editor, cx| {
19417 let autoscroll = match scroll_offset {
19418 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
19419 None => Autoscroll::newest(),
19420 };
19421 let nav_history = editor.nav_history.take();
19422 editor.change_selections(Some(autoscroll), window, cx, |s| {
19423 s.select_ranges(ranges);
19424 });
19425 editor.nav_history = nav_history;
19426 });
19427 }
19428 })
19429 });
19430 }
19431
19432 // For now, don't allow opening excerpts in buffers that aren't backed by
19433 // regular project files.
19434 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
19435 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
19436 }
19437
19438 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
19439 let snapshot = self.buffer.read(cx).read(cx);
19440 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
19441 Some(
19442 ranges
19443 .iter()
19444 .map(move |range| {
19445 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19446 })
19447 .collect(),
19448 )
19449 }
19450
19451 fn selection_replacement_ranges(
19452 &self,
19453 range: Range<OffsetUtf16>,
19454 cx: &mut App,
19455 ) -> Vec<Range<OffsetUtf16>> {
19456 let selections = self.selections.all::<OffsetUtf16>(cx);
19457 let newest_selection = selections
19458 .iter()
19459 .max_by_key(|selection| selection.id)
19460 .unwrap();
19461 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19462 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19463 let snapshot = self.buffer.read(cx).read(cx);
19464 selections
19465 .into_iter()
19466 .map(|mut selection| {
19467 selection.start.0 =
19468 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19469 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19470 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19471 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19472 })
19473 .collect()
19474 }
19475
19476 fn report_editor_event(
19477 &self,
19478 event_type: &'static str,
19479 file_extension: Option<String>,
19480 cx: &App,
19481 ) {
19482 if cfg!(any(test, feature = "test-support")) {
19483 return;
19484 }
19485
19486 let Some(project) = &self.project else { return };
19487
19488 // If None, we are in a file without an extension
19489 let file = self
19490 .buffer
19491 .read(cx)
19492 .as_singleton()
19493 .and_then(|b| b.read(cx).file());
19494 let file_extension = file_extension.or(file
19495 .as_ref()
19496 .and_then(|file| Path::new(file.file_name(cx)).extension())
19497 .and_then(|e| e.to_str())
19498 .map(|a| a.to_string()));
19499
19500 let vim_mode = vim_enabled(cx);
19501
19502 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
19503 let copilot_enabled = edit_predictions_provider
19504 == language::language_settings::EditPredictionProvider::Copilot;
19505 let copilot_enabled_for_language = self
19506 .buffer
19507 .read(cx)
19508 .language_settings(cx)
19509 .show_edit_predictions;
19510
19511 let project = project.read(cx);
19512 telemetry::event!(
19513 event_type,
19514 file_extension,
19515 vim_mode,
19516 copilot_enabled,
19517 copilot_enabled_for_language,
19518 edit_predictions_provider,
19519 is_via_ssh = project.is_via_ssh(),
19520 );
19521 }
19522
19523 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
19524 /// with each line being an array of {text, highlight} objects.
19525 fn copy_highlight_json(
19526 &mut self,
19527 _: &CopyHighlightJson,
19528 window: &mut Window,
19529 cx: &mut Context<Self>,
19530 ) {
19531 #[derive(Serialize)]
19532 struct Chunk<'a> {
19533 text: String,
19534 highlight: Option<&'a str>,
19535 }
19536
19537 let snapshot = self.buffer.read(cx).snapshot(cx);
19538 let range = self
19539 .selected_text_range(false, window, cx)
19540 .and_then(|selection| {
19541 if selection.range.is_empty() {
19542 None
19543 } else {
19544 Some(selection.range)
19545 }
19546 })
19547 .unwrap_or_else(|| 0..snapshot.len());
19548
19549 let chunks = snapshot.chunks(range, true);
19550 let mut lines = Vec::new();
19551 let mut line: VecDeque<Chunk> = VecDeque::new();
19552
19553 let Some(style) = self.style.as_ref() else {
19554 return;
19555 };
19556
19557 for chunk in chunks {
19558 let highlight = chunk
19559 .syntax_highlight_id
19560 .and_then(|id| id.name(&style.syntax));
19561 let mut chunk_lines = chunk.text.split('\n').peekable();
19562 while let Some(text) = chunk_lines.next() {
19563 let mut merged_with_last_token = false;
19564 if let Some(last_token) = line.back_mut() {
19565 if last_token.highlight == highlight {
19566 last_token.text.push_str(text);
19567 merged_with_last_token = true;
19568 }
19569 }
19570
19571 if !merged_with_last_token {
19572 line.push_back(Chunk {
19573 text: text.into(),
19574 highlight,
19575 });
19576 }
19577
19578 if chunk_lines.peek().is_some() {
19579 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19580 line.pop_front();
19581 }
19582 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19583 line.pop_back();
19584 }
19585
19586 lines.push(mem::take(&mut line));
19587 }
19588 }
19589 }
19590
19591 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19592 return;
19593 };
19594 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19595 }
19596
19597 pub fn open_context_menu(
19598 &mut self,
19599 _: &OpenContextMenu,
19600 window: &mut Window,
19601 cx: &mut Context<Self>,
19602 ) {
19603 self.request_autoscroll(Autoscroll::newest(), cx);
19604 let position = self.selections.newest_display(cx).start;
19605 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19606 }
19607
19608 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19609 &self.inlay_hint_cache
19610 }
19611
19612 pub fn replay_insert_event(
19613 &mut self,
19614 text: &str,
19615 relative_utf16_range: Option<Range<isize>>,
19616 window: &mut Window,
19617 cx: &mut Context<Self>,
19618 ) {
19619 if !self.input_enabled {
19620 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19621 return;
19622 }
19623 if let Some(relative_utf16_range) = relative_utf16_range {
19624 let selections = self.selections.all::<OffsetUtf16>(cx);
19625 self.change_selections(None, window, cx, |s| {
19626 let new_ranges = selections.into_iter().map(|range| {
19627 let start = OffsetUtf16(
19628 range
19629 .head()
19630 .0
19631 .saturating_add_signed(relative_utf16_range.start),
19632 );
19633 let end = OffsetUtf16(
19634 range
19635 .head()
19636 .0
19637 .saturating_add_signed(relative_utf16_range.end),
19638 );
19639 start..end
19640 });
19641 s.select_ranges(new_ranges);
19642 });
19643 }
19644
19645 self.handle_input(text, window, cx);
19646 }
19647
19648 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19649 let Some(provider) = self.semantics_provider.as_ref() else {
19650 return false;
19651 };
19652
19653 let mut supports = false;
19654 self.buffer().update(cx, |this, cx| {
19655 this.for_each_buffer(|buffer| {
19656 supports |= provider.supports_inlay_hints(buffer, cx);
19657 });
19658 });
19659
19660 supports
19661 }
19662
19663 pub fn is_focused(&self, window: &Window) -> bool {
19664 self.focus_handle.is_focused(window)
19665 }
19666
19667 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19668 cx.emit(EditorEvent::Focused);
19669
19670 if let Some(descendant) = self
19671 .last_focused_descendant
19672 .take()
19673 .and_then(|descendant| descendant.upgrade())
19674 {
19675 window.focus(&descendant);
19676 } else {
19677 if let Some(blame) = self.blame.as_ref() {
19678 blame.update(cx, GitBlame::focus)
19679 }
19680
19681 self.blink_manager.update(cx, BlinkManager::enable);
19682 self.show_cursor_names(window, cx);
19683 self.buffer.update(cx, |buffer, cx| {
19684 buffer.finalize_last_transaction(cx);
19685 if self.leader_id.is_none() {
19686 buffer.set_active_selections(
19687 &self.selections.disjoint_anchors(),
19688 self.selections.line_mode,
19689 self.cursor_shape,
19690 cx,
19691 );
19692 }
19693 });
19694 }
19695 }
19696
19697 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19698 cx.emit(EditorEvent::FocusedIn)
19699 }
19700
19701 fn handle_focus_out(
19702 &mut self,
19703 event: FocusOutEvent,
19704 _window: &mut Window,
19705 cx: &mut Context<Self>,
19706 ) {
19707 if event.blurred != self.focus_handle {
19708 self.last_focused_descendant = Some(event.blurred);
19709 }
19710 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19711 }
19712
19713 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19714 self.blink_manager.update(cx, BlinkManager::disable);
19715 self.buffer
19716 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19717
19718 if let Some(blame) = self.blame.as_ref() {
19719 blame.update(cx, GitBlame::blur)
19720 }
19721 if !self.hover_state.focused(window, cx) {
19722 hide_hover(self, cx);
19723 }
19724 if !self
19725 .context_menu
19726 .borrow()
19727 .as_ref()
19728 .is_some_and(|context_menu| context_menu.focused(window, cx))
19729 {
19730 self.hide_context_menu(window, cx);
19731 }
19732 self.discard_inline_completion(false, cx);
19733 cx.emit(EditorEvent::Blurred);
19734 cx.notify();
19735 }
19736
19737 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19738 let mut pending: String = window
19739 .pending_input_keystrokes()
19740 .into_iter()
19741 .flatten()
19742 .filter_map(|keystroke| {
19743 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
19744 keystroke.key_char.clone()
19745 } else {
19746 None
19747 }
19748 })
19749 .collect();
19750
19751 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
19752 pending = "".to_string();
19753 }
19754
19755 let existing_pending = self
19756 .text_highlights::<PendingInput>(cx)
19757 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
19758 if existing_pending.is_none() && pending.is_empty() {
19759 return;
19760 }
19761 let transaction =
19762 self.transact(window, cx, |this, window, cx| {
19763 let selections = this.selections.all::<usize>(cx);
19764 let edits = selections
19765 .iter()
19766 .map(|selection| (selection.end..selection.end, pending.clone()));
19767 this.edit(edits, cx);
19768 this.change_selections(None, window, cx, |s| {
19769 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
19770 sel.start + ix * pending.len()..sel.end + ix * pending.len()
19771 }));
19772 });
19773 if let Some(existing_ranges) = existing_pending {
19774 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
19775 this.edit(edits, cx);
19776 }
19777 });
19778
19779 let snapshot = self.snapshot(window, cx);
19780 let ranges = self
19781 .selections
19782 .all::<usize>(cx)
19783 .into_iter()
19784 .map(|selection| {
19785 snapshot.buffer_snapshot.anchor_after(selection.end)
19786 ..snapshot
19787 .buffer_snapshot
19788 .anchor_before(selection.end + pending.len())
19789 })
19790 .collect();
19791
19792 if pending.is_empty() {
19793 self.clear_highlights::<PendingInput>(cx);
19794 } else {
19795 self.highlight_text::<PendingInput>(
19796 ranges,
19797 HighlightStyle {
19798 underline: Some(UnderlineStyle {
19799 thickness: px(1.),
19800 color: None,
19801 wavy: false,
19802 }),
19803 ..Default::default()
19804 },
19805 cx,
19806 );
19807 }
19808
19809 self.ime_transaction = self.ime_transaction.or(transaction);
19810 if let Some(transaction) = self.ime_transaction {
19811 self.buffer.update(cx, |buffer, cx| {
19812 buffer.group_until_transaction(transaction, cx);
19813 });
19814 }
19815
19816 if self.text_highlights::<PendingInput>(cx).is_none() {
19817 self.ime_transaction.take();
19818 }
19819 }
19820
19821 pub fn register_action_renderer(
19822 &mut self,
19823 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
19824 ) -> Subscription {
19825 let id = self.next_editor_action_id.post_inc();
19826 self.editor_actions
19827 .borrow_mut()
19828 .insert(id, Box::new(listener));
19829
19830 let editor_actions = self.editor_actions.clone();
19831 Subscription::new(move || {
19832 editor_actions.borrow_mut().remove(&id);
19833 })
19834 }
19835
19836 pub fn register_action<A: Action>(
19837 &mut self,
19838 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19839 ) -> Subscription {
19840 let id = self.next_editor_action_id.post_inc();
19841 let listener = Arc::new(listener);
19842 self.editor_actions.borrow_mut().insert(
19843 id,
19844 Box::new(move |_, window, _| {
19845 let listener = listener.clone();
19846 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19847 let action = action.downcast_ref().unwrap();
19848 if phase == DispatchPhase::Bubble {
19849 listener(action, window, cx)
19850 }
19851 })
19852 }),
19853 );
19854
19855 let editor_actions = self.editor_actions.clone();
19856 Subscription::new(move || {
19857 editor_actions.borrow_mut().remove(&id);
19858 })
19859 }
19860
19861 pub fn file_header_size(&self) -> u32 {
19862 FILE_HEADER_HEIGHT
19863 }
19864
19865 pub fn restore(
19866 &mut self,
19867 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19868 window: &mut Window,
19869 cx: &mut Context<Self>,
19870 ) {
19871 let workspace = self.workspace();
19872 let project = self.project.as_ref();
19873 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19874 let mut tasks = Vec::new();
19875 for (buffer_id, changes) in revert_changes {
19876 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19877 buffer.update(cx, |buffer, cx| {
19878 buffer.edit(
19879 changes
19880 .into_iter()
19881 .map(|(range, text)| (range, text.to_string())),
19882 None,
19883 cx,
19884 );
19885 });
19886
19887 if let Some(project) =
19888 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19889 {
19890 project.update(cx, |project, cx| {
19891 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19892 })
19893 }
19894 }
19895 }
19896 tasks
19897 });
19898 cx.spawn_in(window, async move |_, cx| {
19899 for (buffer, task) in save_tasks {
19900 let result = task.await;
19901 if result.is_err() {
19902 let Some(path) = buffer
19903 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19904 .ok()
19905 else {
19906 continue;
19907 };
19908 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19909 let Some(task) = cx
19910 .update_window_entity(&workspace, |workspace, window, cx| {
19911 workspace
19912 .open_path_preview(path, None, false, false, false, window, cx)
19913 })
19914 .ok()
19915 else {
19916 continue;
19917 };
19918 task.await.log_err();
19919 }
19920 }
19921 }
19922 })
19923 .detach();
19924 self.change_selections(None, window, cx, |selections| selections.refresh());
19925 }
19926
19927 pub fn to_pixel_point(
19928 &self,
19929 source: multi_buffer::Anchor,
19930 editor_snapshot: &EditorSnapshot,
19931 window: &mut Window,
19932 ) -> Option<gpui::Point<Pixels>> {
19933 let source_point = source.to_display_point(editor_snapshot);
19934 self.display_to_pixel_point(source_point, editor_snapshot, window)
19935 }
19936
19937 pub fn display_to_pixel_point(
19938 &self,
19939 source: DisplayPoint,
19940 editor_snapshot: &EditorSnapshot,
19941 window: &mut Window,
19942 ) -> Option<gpui::Point<Pixels>> {
19943 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19944 let text_layout_details = self.text_layout_details(window);
19945 let scroll_top = text_layout_details
19946 .scroll_anchor
19947 .scroll_position(editor_snapshot)
19948 .y;
19949
19950 if source.row().as_f32() < scroll_top.floor() {
19951 return None;
19952 }
19953 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19954 let source_y = line_height * (source.row().as_f32() - scroll_top);
19955 Some(gpui::Point::new(source_x, source_y))
19956 }
19957
19958 pub fn has_visible_completions_menu(&self) -> bool {
19959 !self.edit_prediction_preview_is_active()
19960 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19961 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19962 })
19963 }
19964
19965 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19966 if self.mode.is_minimap() {
19967 return;
19968 }
19969 self.addons
19970 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
19971 }
19972
19973 pub fn unregister_addon<T: Addon>(&mut self) {
19974 self.addons.remove(&std::any::TypeId::of::<T>());
19975 }
19976
19977 pub fn addon<T: Addon>(&self) -> Option<&T> {
19978 let type_id = std::any::TypeId::of::<T>();
19979 self.addons
19980 .get(&type_id)
19981 .and_then(|item| item.to_any().downcast_ref::<T>())
19982 }
19983
19984 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
19985 let type_id = std::any::TypeId::of::<T>();
19986 self.addons
19987 .get_mut(&type_id)
19988 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
19989 }
19990
19991 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
19992 let text_layout_details = self.text_layout_details(window);
19993 let style = &text_layout_details.editor_style;
19994 let font_id = window.text_system().resolve_font(&style.text.font());
19995 let font_size = style.text.font_size.to_pixels(window.rem_size());
19996 let line_height = style.text.line_height_in_pixels(window.rem_size());
19997 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
19998
19999 gpui::Size::new(em_width, line_height)
20000 }
20001
20002 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20003 self.load_diff_task.clone()
20004 }
20005
20006 fn read_metadata_from_db(
20007 &mut self,
20008 item_id: u64,
20009 workspace_id: WorkspaceId,
20010 window: &mut Window,
20011 cx: &mut Context<Editor>,
20012 ) {
20013 if self.is_singleton(cx)
20014 && !self.mode.is_minimap()
20015 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20016 {
20017 let buffer_snapshot = OnceCell::new();
20018
20019 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20020 if !folds.is_empty() {
20021 let snapshot =
20022 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20023 self.fold_ranges(
20024 folds
20025 .into_iter()
20026 .map(|(start, end)| {
20027 snapshot.clip_offset(start, Bias::Left)
20028 ..snapshot.clip_offset(end, Bias::Right)
20029 })
20030 .collect(),
20031 false,
20032 window,
20033 cx,
20034 );
20035 }
20036 }
20037
20038 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20039 if !selections.is_empty() {
20040 let snapshot =
20041 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20042 // skip adding the initial selection to selection history
20043 self.selection_history.mode = SelectionHistoryMode::Skipping;
20044 self.change_selections(None, window, cx, |s| {
20045 s.select_ranges(selections.into_iter().map(|(start, end)| {
20046 snapshot.clip_offset(start, Bias::Left)
20047 ..snapshot.clip_offset(end, Bias::Right)
20048 }));
20049 });
20050 self.selection_history.mode = SelectionHistoryMode::Normal;
20051 }
20052 };
20053 }
20054
20055 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20056 }
20057}
20058
20059fn vim_enabled(cx: &App) -> bool {
20060 cx.global::<SettingsStore>()
20061 .raw_user_settings()
20062 .get("vim_mode")
20063 == Some(&serde_json::Value::Bool(true))
20064}
20065
20066fn process_completion_for_edit(
20067 completion: &Completion,
20068 intent: CompletionIntent,
20069 buffer: &Entity<Buffer>,
20070 cursor_position: &text::Anchor,
20071 cx: &mut Context<Editor>,
20072) -> CompletionEdit {
20073 let buffer = buffer.read(cx);
20074 let buffer_snapshot = buffer.snapshot();
20075 let (snippet, new_text) = if completion.is_snippet() {
20076 // Workaround for typescript language server issues so that methods don't expand within
20077 // strings and functions with type expressions. The previous point is used because the query
20078 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
20079 let mut snippet_source = completion.new_text.clone();
20080 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
20081 previous_point.column = previous_point.column.saturating_sub(1);
20082 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
20083 if scope.prefers_label_for_snippet_in_completion() {
20084 if let Some(label) = completion.label() {
20085 if matches!(
20086 completion.kind(),
20087 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20088 ) {
20089 snippet_source = label;
20090 }
20091 }
20092 }
20093 }
20094 match Snippet::parse(&snippet_source).log_err() {
20095 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20096 None => (None, completion.new_text.clone()),
20097 }
20098 } else {
20099 (None, completion.new_text.clone())
20100 };
20101
20102 let mut range_to_replace = {
20103 let replace_range = &completion.replace_range;
20104 if let CompletionSource::Lsp {
20105 insert_range: Some(insert_range),
20106 ..
20107 } = &completion.source
20108 {
20109 debug_assert_eq!(
20110 insert_range.start, replace_range.start,
20111 "insert_range and replace_range should start at the same position"
20112 );
20113 debug_assert!(
20114 insert_range
20115 .start
20116 .cmp(&cursor_position, &buffer_snapshot)
20117 .is_le(),
20118 "insert_range should start before or at cursor position"
20119 );
20120 debug_assert!(
20121 replace_range
20122 .start
20123 .cmp(&cursor_position, &buffer_snapshot)
20124 .is_le(),
20125 "replace_range should start before or at cursor position"
20126 );
20127 debug_assert!(
20128 insert_range
20129 .end
20130 .cmp(&cursor_position, &buffer_snapshot)
20131 .is_le(),
20132 "insert_range should end before or at cursor position"
20133 );
20134
20135 let should_replace = match intent {
20136 CompletionIntent::CompleteWithInsert => false,
20137 CompletionIntent::CompleteWithReplace => true,
20138 CompletionIntent::Complete | CompletionIntent::Compose => {
20139 let insert_mode =
20140 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
20141 .completions
20142 .lsp_insert_mode;
20143 match insert_mode {
20144 LspInsertMode::Insert => false,
20145 LspInsertMode::Replace => true,
20146 LspInsertMode::ReplaceSubsequence => {
20147 let mut text_to_replace = buffer.chars_for_range(
20148 buffer.anchor_before(replace_range.start)
20149 ..buffer.anchor_after(replace_range.end),
20150 );
20151 let mut current_needle = text_to_replace.next();
20152 for haystack_ch in completion.label.text.chars() {
20153 if let Some(needle_ch) = current_needle {
20154 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
20155 current_needle = text_to_replace.next();
20156 }
20157 }
20158 }
20159 current_needle.is_none()
20160 }
20161 LspInsertMode::ReplaceSuffix => {
20162 if replace_range
20163 .end
20164 .cmp(&cursor_position, &buffer_snapshot)
20165 .is_gt()
20166 {
20167 let range_after_cursor = *cursor_position..replace_range.end;
20168 let text_after_cursor = buffer
20169 .text_for_range(
20170 buffer.anchor_before(range_after_cursor.start)
20171 ..buffer.anchor_after(range_after_cursor.end),
20172 )
20173 .collect::<String>()
20174 .to_ascii_lowercase();
20175 completion
20176 .label
20177 .text
20178 .to_ascii_lowercase()
20179 .ends_with(&text_after_cursor)
20180 } else {
20181 true
20182 }
20183 }
20184 }
20185 }
20186 };
20187
20188 if should_replace {
20189 replace_range.clone()
20190 } else {
20191 insert_range.clone()
20192 }
20193 } else {
20194 replace_range.clone()
20195 }
20196 };
20197
20198 if range_to_replace
20199 .end
20200 .cmp(&cursor_position, &buffer_snapshot)
20201 .is_lt()
20202 {
20203 range_to_replace.end = *cursor_position;
20204 }
20205
20206 CompletionEdit {
20207 new_text,
20208 replace_range: range_to_replace.to_offset(&buffer),
20209 snippet,
20210 }
20211}
20212
20213struct CompletionEdit {
20214 new_text: String,
20215 replace_range: Range<usize>,
20216 snippet: Option<Snippet>,
20217}
20218
20219fn insert_extra_newline_brackets(
20220 buffer: &MultiBufferSnapshot,
20221 range: Range<usize>,
20222 language: &language::LanguageScope,
20223) -> bool {
20224 let leading_whitespace_len = buffer
20225 .reversed_chars_at(range.start)
20226 .take_while(|c| c.is_whitespace() && *c != '\n')
20227 .map(|c| c.len_utf8())
20228 .sum::<usize>();
20229 let trailing_whitespace_len = buffer
20230 .chars_at(range.end)
20231 .take_while(|c| c.is_whitespace() && *c != '\n')
20232 .map(|c| c.len_utf8())
20233 .sum::<usize>();
20234 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
20235
20236 language.brackets().any(|(pair, enabled)| {
20237 let pair_start = pair.start.trim_end();
20238 let pair_end = pair.end.trim_start();
20239
20240 enabled
20241 && pair.newline
20242 && buffer.contains_str_at(range.end, pair_end)
20243 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
20244 })
20245}
20246
20247fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
20248 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
20249 [(buffer, range, _)] => (*buffer, range.clone()),
20250 _ => return false,
20251 };
20252 let pair = {
20253 let mut result: Option<BracketMatch> = None;
20254
20255 for pair in buffer
20256 .all_bracket_ranges(range.clone())
20257 .filter(move |pair| {
20258 pair.open_range.start <= range.start && pair.close_range.end >= range.end
20259 })
20260 {
20261 let len = pair.close_range.end - pair.open_range.start;
20262
20263 if let Some(existing) = &result {
20264 let existing_len = existing.close_range.end - existing.open_range.start;
20265 if len > existing_len {
20266 continue;
20267 }
20268 }
20269
20270 result = Some(pair);
20271 }
20272
20273 result
20274 };
20275 let Some(pair) = pair else {
20276 return false;
20277 };
20278 pair.newline_only
20279 && buffer
20280 .chars_for_range(pair.open_range.end..range.start)
20281 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
20282 .all(|c| c.is_whitespace() && c != '\n')
20283}
20284
20285fn update_uncommitted_diff_for_buffer(
20286 editor: Entity<Editor>,
20287 project: &Entity<Project>,
20288 buffers: impl IntoIterator<Item = Entity<Buffer>>,
20289 buffer: Entity<MultiBuffer>,
20290 cx: &mut App,
20291) -> Task<()> {
20292 let mut tasks = Vec::new();
20293 project.update(cx, |project, cx| {
20294 for buffer in buffers {
20295 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
20296 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
20297 }
20298 }
20299 });
20300 cx.spawn(async move |cx| {
20301 let diffs = future::join_all(tasks).await;
20302 if editor
20303 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
20304 .unwrap_or(false)
20305 {
20306 return;
20307 }
20308
20309 buffer
20310 .update(cx, |buffer, cx| {
20311 for diff in diffs.into_iter().flatten() {
20312 buffer.add_diff(diff, cx);
20313 }
20314 })
20315 .ok();
20316 })
20317}
20318
20319fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
20320 let tab_size = tab_size.get() as usize;
20321 let mut width = offset;
20322
20323 for ch in text.chars() {
20324 width += if ch == '\t' {
20325 tab_size - (width % tab_size)
20326 } else {
20327 1
20328 };
20329 }
20330
20331 width - offset
20332}
20333
20334#[cfg(test)]
20335mod tests {
20336 use super::*;
20337
20338 #[test]
20339 fn test_string_size_with_expanded_tabs() {
20340 let nz = |val| NonZeroU32::new(val).unwrap();
20341 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
20342 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
20343 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
20344 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
20345 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
20346 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
20347 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
20348 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
20349 }
20350}
20351
20352/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
20353struct WordBreakingTokenizer<'a> {
20354 input: &'a str,
20355}
20356
20357impl<'a> WordBreakingTokenizer<'a> {
20358 fn new(input: &'a str) -> Self {
20359 Self { input }
20360 }
20361}
20362
20363fn is_char_ideographic(ch: char) -> bool {
20364 use unicode_script::Script::*;
20365 use unicode_script::UnicodeScript;
20366 matches!(ch.script(), Han | Tangut | Yi)
20367}
20368
20369fn is_grapheme_ideographic(text: &str) -> bool {
20370 text.chars().any(is_char_ideographic)
20371}
20372
20373fn is_grapheme_whitespace(text: &str) -> bool {
20374 text.chars().any(|x| x.is_whitespace())
20375}
20376
20377fn should_stay_with_preceding_ideograph(text: &str) -> bool {
20378 text.chars().next().map_or(false, |ch| {
20379 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
20380 })
20381}
20382
20383#[derive(PartialEq, Eq, Debug, Clone, Copy)]
20384enum WordBreakToken<'a> {
20385 Word { token: &'a str, grapheme_len: usize },
20386 InlineWhitespace { token: &'a str, grapheme_len: usize },
20387 Newline,
20388}
20389
20390impl<'a> Iterator for WordBreakingTokenizer<'a> {
20391 /// Yields a span, the count of graphemes in the token, and whether it was
20392 /// whitespace. Note that it also breaks at word boundaries.
20393 type Item = WordBreakToken<'a>;
20394
20395 fn next(&mut self) -> Option<Self::Item> {
20396 use unicode_segmentation::UnicodeSegmentation;
20397 if self.input.is_empty() {
20398 return None;
20399 }
20400
20401 let mut iter = self.input.graphemes(true).peekable();
20402 let mut offset = 0;
20403 let mut grapheme_len = 0;
20404 if let Some(first_grapheme) = iter.next() {
20405 let is_newline = first_grapheme == "\n";
20406 let is_whitespace = is_grapheme_whitespace(first_grapheme);
20407 offset += first_grapheme.len();
20408 grapheme_len += 1;
20409 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
20410 if let Some(grapheme) = iter.peek().copied() {
20411 if should_stay_with_preceding_ideograph(grapheme) {
20412 offset += grapheme.len();
20413 grapheme_len += 1;
20414 }
20415 }
20416 } else {
20417 let mut words = self.input[offset..].split_word_bound_indices().peekable();
20418 let mut next_word_bound = words.peek().copied();
20419 if next_word_bound.map_or(false, |(i, _)| i == 0) {
20420 next_word_bound = words.next();
20421 }
20422 while let Some(grapheme) = iter.peek().copied() {
20423 if next_word_bound.map_or(false, |(i, _)| i == offset) {
20424 break;
20425 };
20426 if is_grapheme_whitespace(grapheme) != is_whitespace
20427 || (grapheme == "\n") != is_newline
20428 {
20429 break;
20430 };
20431 offset += grapheme.len();
20432 grapheme_len += 1;
20433 iter.next();
20434 }
20435 }
20436 let token = &self.input[..offset];
20437 self.input = &self.input[offset..];
20438 if token == "\n" {
20439 Some(WordBreakToken::Newline)
20440 } else if is_whitespace {
20441 Some(WordBreakToken::InlineWhitespace {
20442 token,
20443 grapheme_len,
20444 })
20445 } else {
20446 Some(WordBreakToken::Word {
20447 token,
20448 grapheme_len,
20449 })
20450 }
20451 } else {
20452 None
20453 }
20454 }
20455}
20456
20457#[test]
20458fn test_word_breaking_tokenizer() {
20459 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
20460 ("", &[]),
20461 (" ", &[whitespace(" ", 2)]),
20462 ("Ʒ", &[word("Ʒ", 1)]),
20463 ("Ǽ", &[word("Ǽ", 1)]),
20464 ("⋑", &[word("⋑", 1)]),
20465 ("⋑⋑", &[word("⋑⋑", 2)]),
20466 (
20467 "原理,进而",
20468 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
20469 ),
20470 (
20471 "hello world",
20472 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
20473 ),
20474 (
20475 "hello, world",
20476 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
20477 ),
20478 (
20479 " hello world",
20480 &[
20481 whitespace(" ", 2),
20482 word("hello", 5),
20483 whitespace(" ", 1),
20484 word("world", 5),
20485 ],
20486 ),
20487 (
20488 "这是什么 \n 钢笔",
20489 &[
20490 word("这", 1),
20491 word("是", 1),
20492 word("什", 1),
20493 word("么", 1),
20494 whitespace(" ", 1),
20495 newline(),
20496 whitespace(" ", 1),
20497 word("钢", 1),
20498 word("笔", 1),
20499 ],
20500 ),
20501 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
20502 ];
20503
20504 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20505 WordBreakToken::Word {
20506 token,
20507 grapheme_len,
20508 }
20509 }
20510
20511 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20512 WordBreakToken::InlineWhitespace {
20513 token,
20514 grapheme_len,
20515 }
20516 }
20517
20518 fn newline() -> WordBreakToken<'static> {
20519 WordBreakToken::Newline
20520 }
20521
20522 for (input, result) in tests {
20523 assert_eq!(
20524 WordBreakingTokenizer::new(input)
20525 .collect::<Vec<_>>()
20526 .as_slice(),
20527 *result,
20528 );
20529 }
20530}
20531
20532fn wrap_with_prefix(
20533 line_prefix: String,
20534 unwrapped_text: String,
20535 wrap_column: usize,
20536 tab_size: NonZeroU32,
20537 preserve_existing_whitespace: bool,
20538) -> String {
20539 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
20540 let mut wrapped_text = String::new();
20541 let mut current_line = line_prefix.clone();
20542
20543 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
20544 let mut current_line_len = line_prefix_len;
20545 let mut in_whitespace = false;
20546 for token in tokenizer {
20547 let have_preceding_whitespace = in_whitespace;
20548 match token {
20549 WordBreakToken::Word {
20550 token,
20551 grapheme_len,
20552 } => {
20553 in_whitespace = false;
20554 if current_line_len + grapheme_len > wrap_column
20555 && current_line_len != line_prefix_len
20556 {
20557 wrapped_text.push_str(current_line.trim_end());
20558 wrapped_text.push('\n');
20559 current_line.truncate(line_prefix.len());
20560 current_line_len = line_prefix_len;
20561 }
20562 current_line.push_str(token);
20563 current_line_len += grapheme_len;
20564 }
20565 WordBreakToken::InlineWhitespace {
20566 mut token,
20567 mut grapheme_len,
20568 } => {
20569 in_whitespace = true;
20570 if have_preceding_whitespace && !preserve_existing_whitespace {
20571 continue;
20572 }
20573 if !preserve_existing_whitespace {
20574 token = " ";
20575 grapheme_len = 1;
20576 }
20577 if current_line_len + grapheme_len > wrap_column {
20578 wrapped_text.push_str(current_line.trim_end());
20579 wrapped_text.push('\n');
20580 current_line.truncate(line_prefix.len());
20581 current_line_len = line_prefix_len;
20582 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
20583 current_line.push_str(token);
20584 current_line_len += grapheme_len;
20585 }
20586 }
20587 WordBreakToken::Newline => {
20588 in_whitespace = true;
20589 if preserve_existing_whitespace {
20590 wrapped_text.push_str(current_line.trim_end());
20591 wrapped_text.push('\n');
20592 current_line.truncate(line_prefix.len());
20593 current_line_len = line_prefix_len;
20594 } else if have_preceding_whitespace {
20595 continue;
20596 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
20597 {
20598 wrapped_text.push_str(current_line.trim_end());
20599 wrapped_text.push('\n');
20600 current_line.truncate(line_prefix.len());
20601 current_line_len = line_prefix_len;
20602 } else if current_line_len != line_prefix_len {
20603 current_line.push(' ');
20604 current_line_len += 1;
20605 }
20606 }
20607 }
20608 }
20609
20610 if !current_line.is_empty() {
20611 wrapped_text.push_str(¤t_line);
20612 }
20613 wrapped_text
20614}
20615
20616#[test]
20617fn test_wrap_with_prefix() {
20618 assert_eq!(
20619 wrap_with_prefix(
20620 "# ".to_string(),
20621 "abcdefg".to_string(),
20622 4,
20623 NonZeroU32::new(4).unwrap(),
20624 false,
20625 ),
20626 "# abcdefg"
20627 );
20628 assert_eq!(
20629 wrap_with_prefix(
20630 "".to_string(),
20631 "\thello world".to_string(),
20632 8,
20633 NonZeroU32::new(4).unwrap(),
20634 false,
20635 ),
20636 "hello\nworld"
20637 );
20638 assert_eq!(
20639 wrap_with_prefix(
20640 "// ".to_string(),
20641 "xx \nyy zz aa bb cc".to_string(),
20642 12,
20643 NonZeroU32::new(4).unwrap(),
20644 false,
20645 ),
20646 "// xx yy zz\n// aa bb cc"
20647 );
20648 assert_eq!(
20649 wrap_with_prefix(
20650 String::new(),
20651 "这是什么 \n 钢笔".to_string(),
20652 3,
20653 NonZeroU32::new(4).unwrap(),
20654 false,
20655 ),
20656 "这是什\n么 钢\n笔"
20657 );
20658}
20659
20660pub trait CollaborationHub {
20661 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
20662 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
20663 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
20664}
20665
20666impl CollaborationHub for Entity<Project> {
20667 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
20668 self.read(cx).collaborators()
20669 }
20670
20671 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
20672 self.read(cx).user_store().read(cx).participant_indices()
20673 }
20674
20675 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
20676 let this = self.read(cx);
20677 let user_ids = this.collaborators().values().map(|c| c.user_id);
20678 this.user_store().read(cx).participant_names(user_ids, cx)
20679 }
20680}
20681
20682pub trait SemanticsProvider {
20683 fn hover(
20684 &self,
20685 buffer: &Entity<Buffer>,
20686 position: text::Anchor,
20687 cx: &mut App,
20688 ) -> Option<Task<Vec<project::Hover>>>;
20689
20690 fn inline_values(
20691 &self,
20692 buffer_handle: Entity<Buffer>,
20693 range: Range<text::Anchor>,
20694 cx: &mut App,
20695 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20696
20697 fn inlay_hints(
20698 &self,
20699 buffer_handle: Entity<Buffer>,
20700 range: Range<text::Anchor>,
20701 cx: &mut App,
20702 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20703
20704 fn resolve_inlay_hint(
20705 &self,
20706 hint: InlayHint,
20707 buffer_handle: Entity<Buffer>,
20708 server_id: LanguageServerId,
20709 cx: &mut App,
20710 ) -> Option<Task<anyhow::Result<InlayHint>>>;
20711
20712 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
20713
20714 fn document_highlights(
20715 &self,
20716 buffer: &Entity<Buffer>,
20717 position: text::Anchor,
20718 cx: &mut App,
20719 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
20720
20721 fn definitions(
20722 &self,
20723 buffer: &Entity<Buffer>,
20724 position: text::Anchor,
20725 kind: GotoDefinitionKind,
20726 cx: &mut App,
20727 ) -> Option<Task<Result<Vec<LocationLink>>>>;
20728
20729 fn range_for_rename(
20730 &self,
20731 buffer: &Entity<Buffer>,
20732 position: text::Anchor,
20733 cx: &mut App,
20734 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
20735
20736 fn perform_rename(
20737 &self,
20738 buffer: &Entity<Buffer>,
20739 position: text::Anchor,
20740 new_name: String,
20741 cx: &mut App,
20742 ) -> Option<Task<Result<ProjectTransaction>>>;
20743
20744 fn pull_diagnostics_for_buffer(
20745 &self,
20746 buffer: Entity<Buffer>,
20747 cx: &mut App,
20748 ) -> Task<anyhow::Result<()>>;
20749}
20750
20751pub trait CompletionProvider {
20752 fn completions(
20753 &self,
20754 excerpt_id: ExcerptId,
20755 buffer: &Entity<Buffer>,
20756 buffer_position: text::Anchor,
20757 trigger: CompletionContext,
20758 window: &mut Window,
20759 cx: &mut Context<Editor>,
20760 ) -> Task<Result<Vec<CompletionResponse>>>;
20761
20762 fn resolve_completions(
20763 &self,
20764 _buffer: Entity<Buffer>,
20765 _completion_indices: Vec<usize>,
20766 _completions: Rc<RefCell<Box<[Completion]>>>,
20767 _cx: &mut Context<Editor>,
20768 ) -> Task<Result<bool>> {
20769 Task::ready(Ok(false))
20770 }
20771
20772 fn apply_additional_edits_for_completion(
20773 &self,
20774 _buffer: Entity<Buffer>,
20775 _completions: Rc<RefCell<Box<[Completion]>>>,
20776 _completion_index: usize,
20777 _push_to_history: bool,
20778 _cx: &mut Context<Editor>,
20779 ) -> Task<Result<Option<language::Transaction>>> {
20780 Task::ready(Ok(None))
20781 }
20782
20783 fn is_completion_trigger(
20784 &self,
20785 buffer: &Entity<Buffer>,
20786 position: language::Anchor,
20787 text: &str,
20788 trigger_in_words: bool,
20789 menu_is_open: bool,
20790 cx: &mut Context<Editor>,
20791 ) -> bool;
20792
20793 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
20794
20795 fn sort_completions(&self) -> bool {
20796 true
20797 }
20798
20799 fn filter_completions(&self) -> bool {
20800 true
20801 }
20802}
20803
20804pub trait CodeActionProvider {
20805 fn id(&self) -> Arc<str>;
20806
20807 fn code_actions(
20808 &self,
20809 buffer: &Entity<Buffer>,
20810 range: Range<text::Anchor>,
20811 window: &mut Window,
20812 cx: &mut App,
20813 ) -> Task<Result<Vec<CodeAction>>>;
20814
20815 fn apply_code_action(
20816 &self,
20817 buffer_handle: Entity<Buffer>,
20818 action: CodeAction,
20819 excerpt_id: ExcerptId,
20820 push_to_history: bool,
20821 window: &mut Window,
20822 cx: &mut App,
20823 ) -> Task<Result<ProjectTransaction>>;
20824}
20825
20826impl CodeActionProvider for Entity<Project> {
20827 fn id(&self) -> Arc<str> {
20828 "project".into()
20829 }
20830
20831 fn code_actions(
20832 &self,
20833 buffer: &Entity<Buffer>,
20834 range: Range<text::Anchor>,
20835 _window: &mut Window,
20836 cx: &mut App,
20837 ) -> Task<Result<Vec<CodeAction>>> {
20838 self.update(cx, |project, cx| {
20839 let code_lens = project.code_lens(buffer, range.clone(), cx);
20840 let code_actions = project.code_actions(buffer, range, None, cx);
20841 cx.background_spawn(async move {
20842 let (code_lens, code_actions) = join(code_lens, code_actions).await;
20843 Ok(code_lens
20844 .context("code lens fetch")?
20845 .into_iter()
20846 .chain(code_actions.context("code action fetch")?)
20847 .collect())
20848 })
20849 })
20850 }
20851
20852 fn apply_code_action(
20853 &self,
20854 buffer_handle: Entity<Buffer>,
20855 action: CodeAction,
20856 _excerpt_id: ExcerptId,
20857 push_to_history: bool,
20858 _window: &mut Window,
20859 cx: &mut App,
20860 ) -> Task<Result<ProjectTransaction>> {
20861 self.update(cx, |project, cx| {
20862 project.apply_code_action(buffer_handle, action, push_to_history, cx)
20863 })
20864 }
20865}
20866
20867fn snippet_completions(
20868 project: &Project,
20869 buffer: &Entity<Buffer>,
20870 buffer_position: text::Anchor,
20871 cx: &mut App,
20872) -> Task<Result<CompletionResponse>> {
20873 let languages = buffer.read(cx).languages_at(buffer_position);
20874 let snippet_store = project.snippets().read(cx);
20875
20876 let scopes: Vec<_> = languages
20877 .iter()
20878 .filter_map(|language| {
20879 let language_name = language.lsp_id();
20880 let snippets = snippet_store.snippets_for(Some(language_name), cx);
20881
20882 if snippets.is_empty() {
20883 None
20884 } else {
20885 Some((language.default_scope(), snippets))
20886 }
20887 })
20888 .collect();
20889
20890 if scopes.is_empty() {
20891 return Task::ready(Ok(CompletionResponse {
20892 completions: vec![],
20893 is_incomplete: false,
20894 }));
20895 }
20896
20897 let snapshot = buffer.read(cx).text_snapshot();
20898 let chars: String = snapshot
20899 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
20900 .collect();
20901 let executor = cx.background_executor().clone();
20902
20903 cx.background_spawn(async move {
20904 let mut is_incomplete = false;
20905 let mut completions: Vec<Completion> = Vec::new();
20906 for (scope, snippets) in scopes.into_iter() {
20907 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
20908 let mut last_word = chars
20909 .chars()
20910 .take_while(|c| classifier.is_word(*c))
20911 .collect::<String>();
20912 last_word = last_word.chars().rev().collect();
20913
20914 if last_word.is_empty() {
20915 return Ok(CompletionResponse {
20916 completions: vec![],
20917 is_incomplete: true,
20918 });
20919 }
20920
20921 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
20922 let to_lsp = |point: &text::Anchor| {
20923 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
20924 point_to_lsp(end)
20925 };
20926 let lsp_end = to_lsp(&buffer_position);
20927
20928 let candidates = snippets
20929 .iter()
20930 .enumerate()
20931 .flat_map(|(ix, snippet)| {
20932 snippet
20933 .prefix
20934 .iter()
20935 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
20936 })
20937 .collect::<Vec<StringMatchCandidate>>();
20938
20939 const MAX_RESULTS: usize = 100;
20940 let mut matches = fuzzy::match_strings(
20941 &candidates,
20942 &last_word,
20943 last_word.chars().any(|c| c.is_uppercase()),
20944 MAX_RESULTS,
20945 &Default::default(),
20946 executor.clone(),
20947 )
20948 .await;
20949
20950 if matches.len() >= MAX_RESULTS {
20951 is_incomplete = true;
20952 }
20953
20954 // Remove all candidates where the query's start does not match the start of any word in the candidate
20955 if let Some(query_start) = last_word.chars().next() {
20956 matches.retain(|string_match| {
20957 split_words(&string_match.string).any(|word| {
20958 // Check that the first codepoint of the word as lowercase matches the first
20959 // codepoint of the query as lowercase
20960 word.chars()
20961 .flat_map(|codepoint| codepoint.to_lowercase())
20962 .zip(query_start.to_lowercase())
20963 .all(|(word_cp, query_cp)| word_cp == query_cp)
20964 })
20965 });
20966 }
20967
20968 let matched_strings = matches
20969 .into_iter()
20970 .map(|m| m.string)
20971 .collect::<HashSet<_>>();
20972
20973 completions.extend(snippets.iter().filter_map(|snippet| {
20974 let matching_prefix = snippet
20975 .prefix
20976 .iter()
20977 .find(|prefix| matched_strings.contains(*prefix))?;
20978 let start = as_offset - last_word.len();
20979 let start = snapshot.anchor_before(start);
20980 let range = start..buffer_position;
20981 let lsp_start = to_lsp(&start);
20982 let lsp_range = lsp::Range {
20983 start: lsp_start,
20984 end: lsp_end,
20985 };
20986 Some(Completion {
20987 replace_range: range,
20988 new_text: snippet.body.clone(),
20989 source: CompletionSource::Lsp {
20990 insert_range: None,
20991 server_id: LanguageServerId(usize::MAX),
20992 resolved: true,
20993 lsp_completion: Box::new(lsp::CompletionItem {
20994 label: snippet.prefix.first().unwrap().clone(),
20995 kind: Some(CompletionItemKind::SNIPPET),
20996 label_details: snippet.description.as_ref().map(|description| {
20997 lsp::CompletionItemLabelDetails {
20998 detail: Some(description.clone()),
20999 description: None,
21000 }
21001 }),
21002 insert_text_format: Some(InsertTextFormat::SNIPPET),
21003 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21004 lsp::InsertReplaceEdit {
21005 new_text: snippet.body.clone(),
21006 insert: lsp_range,
21007 replace: lsp_range,
21008 },
21009 )),
21010 filter_text: Some(snippet.body.clone()),
21011 sort_text: Some(char::MAX.to_string()),
21012 ..lsp::CompletionItem::default()
21013 }),
21014 lsp_defaults: None,
21015 },
21016 label: CodeLabel {
21017 text: matching_prefix.clone(),
21018 runs: Vec::new(),
21019 filter_range: 0..matching_prefix.len(),
21020 },
21021 icon_path: None,
21022 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
21023 single_line: snippet.name.clone().into(),
21024 plain_text: snippet
21025 .description
21026 .clone()
21027 .map(|description| description.into()),
21028 }),
21029 insert_text_mode: None,
21030 confirm: None,
21031 })
21032 }))
21033 }
21034
21035 Ok(CompletionResponse {
21036 completions,
21037 is_incomplete,
21038 })
21039 })
21040}
21041
21042impl CompletionProvider for Entity<Project> {
21043 fn completions(
21044 &self,
21045 _excerpt_id: ExcerptId,
21046 buffer: &Entity<Buffer>,
21047 buffer_position: text::Anchor,
21048 options: CompletionContext,
21049 _window: &mut Window,
21050 cx: &mut Context<Editor>,
21051 ) -> Task<Result<Vec<CompletionResponse>>> {
21052 self.update(cx, |project, cx| {
21053 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21054 let project_completions = project.completions(buffer, buffer_position, options, cx);
21055 cx.background_spawn(async move {
21056 let mut responses = project_completions.await?;
21057 let snippets = snippets.await?;
21058 if !snippets.completions.is_empty() {
21059 responses.push(snippets);
21060 }
21061 Ok(responses)
21062 })
21063 })
21064 }
21065
21066 fn resolve_completions(
21067 &self,
21068 buffer: Entity<Buffer>,
21069 completion_indices: Vec<usize>,
21070 completions: Rc<RefCell<Box<[Completion]>>>,
21071 cx: &mut Context<Editor>,
21072 ) -> Task<Result<bool>> {
21073 self.update(cx, |project, cx| {
21074 project.lsp_store().update(cx, |lsp_store, cx| {
21075 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21076 })
21077 })
21078 }
21079
21080 fn apply_additional_edits_for_completion(
21081 &self,
21082 buffer: Entity<Buffer>,
21083 completions: Rc<RefCell<Box<[Completion]>>>,
21084 completion_index: usize,
21085 push_to_history: bool,
21086 cx: &mut Context<Editor>,
21087 ) -> Task<Result<Option<language::Transaction>>> {
21088 self.update(cx, |project, cx| {
21089 project.lsp_store().update(cx, |lsp_store, cx| {
21090 lsp_store.apply_additional_edits_for_completion(
21091 buffer,
21092 completions,
21093 completion_index,
21094 push_to_history,
21095 cx,
21096 )
21097 })
21098 })
21099 }
21100
21101 fn is_completion_trigger(
21102 &self,
21103 buffer: &Entity<Buffer>,
21104 position: language::Anchor,
21105 text: &str,
21106 trigger_in_words: bool,
21107 menu_is_open: bool,
21108 cx: &mut Context<Editor>,
21109 ) -> bool {
21110 let mut chars = text.chars();
21111 let char = if let Some(char) = chars.next() {
21112 char
21113 } else {
21114 return false;
21115 };
21116 if chars.next().is_some() {
21117 return false;
21118 }
21119
21120 let buffer = buffer.read(cx);
21121 let snapshot = buffer.snapshot();
21122 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
21123 return false;
21124 }
21125 let classifier = snapshot.char_classifier_at(position).for_completion(true);
21126 if trigger_in_words && classifier.is_word(char) {
21127 return true;
21128 }
21129
21130 buffer.completion_triggers().contains(text)
21131 }
21132}
21133
21134impl SemanticsProvider for Entity<Project> {
21135 fn hover(
21136 &self,
21137 buffer: &Entity<Buffer>,
21138 position: text::Anchor,
21139 cx: &mut App,
21140 ) -> Option<Task<Vec<project::Hover>>> {
21141 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
21142 }
21143
21144 fn document_highlights(
21145 &self,
21146 buffer: &Entity<Buffer>,
21147 position: text::Anchor,
21148 cx: &mut App,
21149 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
21150 Some(self.update(cx, |project, cx| {
21151 project.document_highlights(buffer, position, cx)
21152 }))
21153 }
21154
21155 fn definitions(
21156 &self,
21157 buffer: &Entity<Buffer>,
21158 position: text::Anchor,
21159 kind: GotoDefinitionKind,
21160 cx: &mut App,
21161 ) -> Option<Task<Result<Vec<LocationLink>>>> {
21162 Some(self.update(cx, |project, cx| match kind {
21163 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
21164 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
21165 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
21166 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
21167 }))
21168 }
21169
21170 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
21171 // TODO: make this work for remote projects
21172 self.update(cx, |project, cx| {
21173 if project
21174 .active_debug_session(cx)
21175 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
21176 {
21177 return true;
21178 }
21179
21180 buffer.update(cx, |buffer, cx| {
21181 project.any_language_server_supports_inlay_hints(buffer, cx)
21182 })
21183 })
21184 }
21185
21186 fn inline_values(
21187 &self,
21188 buffer_handle: Entity<Buffer>,
21189
21190 range: Range<text::Anchor>,
21191 cx: &mut App,
21192 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21193 self.update(cx, |project, cx| {
21194 let (session, active_stack_frame) = project.active_debug_session(cx)?;
21195
21196 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
21197 })
21198 }
21199
21200 fn inlay_hints(
21201 &self,
21202 buffer_handle: Entity<Buffer>,
21203 range: Range<text::Anchor>,
21204 cx: &mut App,
21205 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21206 Some(self.update(cx, |project, cx| {
21207 project.inlay_hints(buffer_handle, range, cx)
21208 }))
21209 }
21210
21211 fn resolve_inlay_hint(
21212 &self,
21213 hint: InlayHint,
21214 buffer_handle: Entity<Buffer>,
21215 server_id: LanguageServerId,
21216 cx: &mut App,
21217 ) -> Option<Task<anyhow::Result<InlayHint>>> {
21218 Some(self.update(cx, |project, cx| {
21219 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
21220 }))
21221 }
21222
21223 fn range_for_rename(
21224 &self,
21225 buffer: &Entity<Buffer>,
21226 position: text::Anchor,
21227 cx: &mut App,
21228 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
21229 Some(self.update(cx, |project, cx| {
21230 let buffer = buffer.clone();
21231 let task = project.prepare_rename(buffer.clone(), position, cx);
21232 cx.spawn(async move |_, cx| {
21233 Ok(match task.await? {
21234 PrepareRenameResponse::Success(range) => Some(range),
21235 PrepareRenameResponse::InvalidPosition => None,
21236 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
21237 // Fallback on using TreeSitter info to determine identifier range
21238 buffer.read_with(cx, |buffer, _| {
21239 let snapshot = buffer.snapshot();
21240 let (range, kind) = snapshot.surrounding_word(position);
21241 if kind != Some(CharKind::Word) {
21242 return None;
21243 }
21244 Some(
21245 snapshot.anchor_before(range.start)
21246 ..snapshot.anchor_after(range.end),
21247 )
21248 })?
21249 }
21250 })
21251 })
21252 }))
21253 }
21254
21255 fn perform_rename(
21256 &self,
21257 buffer: &Entity<Buffer>,
21258 position: text::Anchor,
21259 new_name: String,
21260 cx: &mut App,
21261 ) -> Option<Task<Result<ProjectTransaction>>> {
21262 Some(self.update(cx, |project, cx| {
21263 project.perform_rename(buffer.clone(), position, new_name, cx)
21264 }))
21265 }
21266
21267 fn pull_diagnostics_for_buffer(
21268 &self,
21269 buffer: Entity<Buffer>,
21270 cx: &mut App,
21271 ) -> Task<anyhow::Result<()>> {
21272 let diagnostics = self.update(cx, |project, cx| {
21273 project
21274 .lsp_store()
21275 .update(cx, |lsp_store, cx| lsp_store.pull_diagnostics(buffer, cx))
21276 });
21277 let project = self.clone();
21278 cx.spawn(async move |cx| {
21279 let diagnostics = diagnostics.await.context("pulling diagnostics")?;
21280 project.update(cx, |project, cx| {
21281 project.lsp_store().update(cx, |lsp_store, cx| {
21282 for diagnostics_set in diagnostics {
21283 let LspPullDiagnostics::Response {
21284 server_id,
21285 uri,
21286 diagnostics,
21287 } = diagnostics_set
21288 else {
21289 continue;
21290 };
21291
21292 let adapter = lsp_store.language_server_adapter_for_id(server_id);
21293 let disk_based_sources = adapter
21294 .as_ref()
21295 .map(|adapter| adapter.disk_based_diagnostic_sources.as_slice())
21296 .unwrap_or(&[]);
21297 match diagnostics {
21298 PulledDiagnostics::Unchanged { result_id } => {
21299 lsp_store
21300 .merge_diagnostics(
21301 server_id,
21302 lsp::PublishDiagnosticsParams {
21303 uri: uri.clone(),
21304 diagnostics: Vec::new(),
21305 version: None,
21306 },
21307 Some(result_id),
21308 DiagnosticSourceKind::Pulled,
21309 disk_based_sources,
21310 |_, _| true,
21311 cx,
21312 )
21313 .log_err();
21314 }
21315 PulledDiagnostics::Changed {
21316 diagnostics,
21317 result_id,
21318 } => {
21319 lsp_store
21320 .merge_diagnostics(
21321 server_id,
21322 lsp::PublishDiagnosticsParams {
21323 uri: uri.clone(),
21324 diagnostics,
21325 version: None,
21326 },
21327 result_id,
21328 DiagnosticSourceKind::Pulled,
21329 disk_based_sources,
21330 |old_diagnostic, _| match old_diagnostic.source_kind {
21331 DiagnosticSourceKind::Pulled => false,
21332 DiagnosticSourceKind::Other
21333 | DiagnosticSourceKind::Pushed => true,
21334 },
21335 cx,
21336 )
21337 .log_err();
21338 }
21339 }
21340 }
21341 })
21342 })
21343 })
21344 }
21345}
21346
21347fn inlay_hint_settings(
21348 location: Anchor,
21349 snapshot: &MultiBufferSnapshot,
21350 cx: &mut Context<Editor>,
21351) -> InlayHintSettings {
21352 let file = snapshot.file_at(location);
21353 let language = snapshot.language_at(location).map(|l| l.name());
21354 language_settings(language, file, cx).inlay_hints
21355}
21356
21357fn consume_contiguous_rows(
21358 contiguous_row_selections: &mut Vec<Selection<Point>>,
21359 selection: &Selection<Point>,
21360 display_map: &DisplaySnapshot,
21361 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
21362) -> (MultiBufferRow, MultiBufferRow) {
21363 contiguous_row_selections.push(selection.clone());
21364 let start_row = MultiBufferRow(selection.start.row);
21365 let mut end_row = ending_row(selection, display_map);
21366
21367 while let Some(next_selection) = selections.peek() {
21368 if next_selection.start.row <= end_row.0 {
21369 end_row = ending_row(next_selection, display_map);
21370 contiguous_row_selections.push(selections.next().unwrap().clone());
21371 } else {
21372 break;
21373 }
21374 }
21375 (start_row, end_row)
21376}
21377
21378fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
21379 if next_selection.end.column > 0 || next_selection.is_empty() {
21380 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
21381 } else {
21382 MultiBufferRow(next_selection.end.row)
21383 }
21384}
21385
21386impl EditorSnapshot {
21387 pub fn remote_selections_in_range<'a>(
21388 &'a self,
21389 range: &'a Range<Anchor>,
21390 collaboration_hub: &dyn CollaborationHub,
21391 cx: &'a App,
21392 ) -> impl 'a + Iterator<Item = RemoteSelection> {
21393 let participant_names = collaboration_hub.user_names(cx);
21394 let participant_indices = collaboration_hub.user_participant_indices(cx);
21395 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
21396 let collaborators_by_replica_id = collaborators_by_peer_id
21397 .values()
21398 .map(|collaborator| (collaborator.replica_id, collaborator))
21399 .collect::<HashMap<_, _>>();
21400 self.buffer_snapshot
21401 .selections_in_range(range, false)
21402 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
21403 if replica_id == AGENT_REPLICA_ID {
21404 Some(RemoteSelection {
21405 replica_id,
21406 selection,
21407 cursor_shape,
21408 line_mode,
21409 collaborator_id: CollaboratorId::Agent,
21410 user_name: Some("Agent".into()),
21411 color: cx.theme().players().agent(),
21412 })
21413 } else {
21414 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
21415 let participant_index = participant_indices.get(&collaborator.user_id).copied();
21416 let user_name = participant_names.get(&collaborator.user_id).cloned();
21417 Some(RemoteSelection {
21418 replica_id,
21419 selection,
21420 cursor_shape,
21421 line_mode,
21422 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
21423 user_name,
21424 color: if let Some(index) = participant_index {
21425 cx.theme().players().color_for_participant(index.0)
21426 } else {
21427 cx.theme().players().absent()
21428 },
21429 })
21430 }
21431 })
21432 }
21433
21434 pub fn hunks_for_ranges(
21435 &self,
21436 ranges: impl IntoIterator<Item = Range<Point>>,
21437 ) -> Vec<MultiBufferDiffHunk> {
21438 let mut hunks = Vec::new();
21439 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
21440 HashMap::default();
21441 for query_range in ranges {
21442 let query_rows =
21443 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
21444 for hunk in self.buffer_snapshot.diff_hunks_in_range(
21445 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
21446 ) {
21447 // Include deleted hunks that are adjacent to the query range, because
21448 // otherwise they would be missed.
21449 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
21450 if hunk.status().is_deleted() {
21451 intersects_range |= hunk.row_range.start == query_rows.end;
21452 intersects_range |= hunk.row_range.end == query_rows.start;
21453 }
21454 if intersects_range {
21455 if !processed_buffer_rows
21456 .entry(hunk.buffer_id)
21457 .or_default()
21458 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
21459 {
21460 continue;
21461 }
21462 hunks.push(hunk);
21463 }
21464 }
21465 }
21466
21467 hunks
21468 }
21469
21470 fn display_diff_hunks_for_rows<'a>(
21471 &'a self,
21472 display_rows: Range<DisplayRow>,
21473 folded_buffers: &'a HashSet<BufferId>,
21474 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
21475 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
21476 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
21477
21478 self.buffer_snapshot
21479 .diff_hunks_in_range(buffer_start..buffer_end)
21480 .filter_map(|hunk| {
21481 if folded_buffers.contains(&hunk.buffer_id) {
21482 return None;
21483 }
21484
21485 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
21486 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
21487
21488 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
21489 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
21490
21491 let display_hunk = if hunk_display_start.column() != 0 {
21492 DisplayDiffHunk::Folded {
21493 display_row: hunk_display_start.row(),
21494 }
21495 } else {
21496 let mut end_row = hunk_display_end.row();
21497 if hunk_display_end.column() > 0 {
21498 end_row.0 += 1;
21499 }
21500 let is_created_file = hunk.is_created_file();
21501 DisplayDiffHunk::Unfolded {
21502 status: hunk.status(),
21503 diff_base_byte_range: hunk.diff_base_byte_range,
21504 display_row_range: hunk_display_start.row()..end_row,
21505 multi_buffer_range: Anchor::range_in_buffer(
21506 hunk.excerpt_id,
21507 hunk.buffer_id,
21508 hunk.buffer_range,
21509 ),
21510 is_created_file,
21511 }
21512 };
21513
21514 Some(display_hunk)
21515 })
21516 }
21517
21518 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
21519 self.display_snapshot.buffer_snapshot.language_at(position)
21520 }
21521
21522 pub fn is_focused(&self) -> bool {
21523 self.is_focused
21524 }
21525
21526 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
21527 self.placeholder_text.as_ref()
21528 }
21529
21530 pub fn scroll_position(&self) -> gpui::Point<f32> {
21531 self.scroll_anchor.scroll_position(&self.display_snapshot)
21532 }
21533
21534 fn gutter_dimensions(
21535 &self,
21536 font_id: FontId,
21537 font_size: Pixels,
21538 max_line_number_width: Pixels,
21539 cx: &App,
21540 ) -> Option<GutterDimensions> {
21541 if !self.show_gutter {
21542 return None;
21543 }
21544
21545 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
21546 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
21547
21548 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
21549 matches!(
21550 ProjectSettings::get_global(cx).git.git_gutter,
21551 Some(GitGutterSetting::TrackedFiles)
21552 )
21553 });
21554 let gutter_settings = EditorSettings::get_global(cx).gutter;
21555 let show_line_numbers = self
21556 .show_line_numbers
21557 .unwrap_or(gutter_settings.line_numbers);
21558 let line_gutter_width = if show_line_numbers {
21559 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
21560 let min_width_for_number_on_gutter =
21561 em_advance * gutter_settings.min_line_number_digits as f32;
21562 max_line_number_width.max(min_width_for_number_on_gutter)
21563 } else {
21564 0.0.into()
21565 };
21566
21567 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
21568 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
21569
21570 let git_blame_entries_width =
21571 self.git_blame_gutter_max_author_length
21572 .map(|max_author_length| {
21573 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
21574 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
21575
21576 /// The number of characters to dedicate to gaps and margins.
21577 const SPACING_WIDTH: usize = 4;
21578
21579 let max_char_count = max_author_length.min(renderer.max_author_length())
21580 + ::git::SHORT_SHA_LENGTH
21581 + MAX_RELATIVE_TIMESTAMP.len()
21582 + SPACING_WIDTH;
21583
21584 em_advance * max_char_count
21585 });
21586
21587 let is_singleton = self.buffer_snapshot.is_singleton();
21588
21589 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
21590 left_padding += if !is_singleton {
21591 em_width * 4.0
21592 } else if show_runnables || show_breakpoints {
21593 em_width * 3.0
21594 } else if show_git_gutter && show_line_numbers {
21595 em_width * 2.0
21596 } else if show_git_gutter || show_line_numbers {
21597 em_width
21598 } else {
21599 px(0.)
21600 };
21601
21602 let shows_folds = is_singleton && gutter_settings.folds;
21603
21604 let right_padding = if shows_folds && show_line_numbers {
21605 em_width * 4.0
21606 } else if shows_folds || (!is_singleton && show_line_numbers) {
21607 em_width * 3.0
21608 } else if show_line_numbers {
21609 em_width
21610 } else {
21611 px(0.)
21612 };
21613
21614 Some(GutterDimensions {
21615 left_padding,
21616 right_padding,
21617 width: line_gutter_width + left_padding + right_padding,
21618 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
21619 git_blame_entries_width,
21620 })
21621 }
21622
21623 pub fn render_crease_toggle(
21624 &self,
21625 buffer_row: MultiBufferRow,
21626 row_contains_cursor: bool,
21627 editor: Entity<Editor>,
21628 window: &mut Window,
21629 cx: &mut App,
21630 ) -> Option<AnyElement> {
21631 let folded = self.is_line_folded(buffer_row);
21632 let mut is_foldable = false;
21633
21634 if let Some(crease) = self
21635 .crease_snapshot
21636 .query_row(buffer_row, &self.buffer_snapshot)
21637 {
21638 is_foldable = true;
21639 match crease {
21640 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
21641 if let Some(render_toggle) = render_toggle {
21642 let toggle_callback =
21643 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
21644 if folded {
21645 editor.update(cx, |editor, cx| {
21646 editor.fold_at(buffer_row, window, cx)
21647 });
21648 } else {
21649 editor.update(cx, |editor, cx| {
21650 editor.unfold_at(buffer_row, window, cx)
21651 });
21652 }
21653 });
21654 return Some((render_toggle)(
21655 buffer_row,
21656 folded,
21657 toggle_callback,
21658 window,
21659 cx,
21660 ));
21661 }
21662 }
21663 }
21664 }
21665
21666 is_foldable |= self.starts_indent(buffer_row);
21667
21668 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
21669 Some(
21670 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
21671 .toggle_state(folded)
21672 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
21673 if folded {
21674 this.unfold_at(buffer_row, window, cx);
21675 } else {
21676 this.fold_at(buffer_row, window, cx);
21677 }
21678 }))
21679 .into_any_element(),
21680 )
21681 } else {
21682 None
21683 }
21684 }
21685
21686 pub fn render_crease_trailer(
21687 &self,
21688 buffer_row: MultiBufferRow,
21689 window: &mut Window,
21690 cx: &mut App,
21691 ) -> Option<AnyElement> {
21692 let folded = self.is_line_folded(buffer_row);
21693 if let Crease::Inline { render_trailer, .. } = self
21694 .crease_snapshot
21695 .query_row(buffer_row, &self.buffer_snapshot)?
21696 {
21697 let render_trailer = render_trailer.as_ref()?;
21698 Some(render_trailer(buffer_row, folded, window, cx))
21699 } else {
21700 None
21701 }
21702 }
21703}
21704
21705impl Deref for EditorSnapshot {
21706 type Target = DisplaySnapshot;
21707
21708 fn deref(&self) -> &Self::Target {
21709 &self.display_snapshot
21710 }
21711}
21712
21713#[derive(Clone, Debug, PartialEq, Eq)]
21714pub enum EditorEvent {
21715 InputIgnored {
21716 text: Arc<str>,
21717 },
21718 InputHandled {
21719 utf16_range_to_replace: Option<Range<isize>>,
21720 text: Arc<str>,
21721 },
21722 ExcerptsAdded {
21723 buffer: Entity<Buffer>,
21724 predecessor: ExcerptId,
21725 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
21726 },
21727 ExcerptsRemoved {
21728 ids: Vec<ExcerptId>,
21729 removed_buffer_ids: Vec<BufferId>,
21730 },
21731 BufferFoldToggled {
21732 ids: Vec<ExcerptId>,
21733 folded: bool,
21734 },
21735 ExcerptsEdited {
21736 ids: Vec<ExcerptId>,
21737 },
21738 ExcerptsExpanded {
21739 ids: Vec<ExcerptId>,
21740 },
21741 BufferEdited,
21742 Edited {
21743 transaction_id: clock::Lamport,
21744 },
21745 Reparsed(BufferId),
21746 Focused,
21747 FocusedIn,
21748 Blurred,
21749 DirtyChanged,
21750 Saved,
21751 TitleChanged,
21752 DiffBaseChanged,
21753 SelectionsChanged {
21754 local: bool,
21755 },
21756 ScrollPositionChanged {
21757 local: bool,
21758 autoscroll: bool,
21759 },
21760 Closed,
21761 TransactionUndone {
21762 transaction_id: clock::Lamport,
21763 },
21764 TransactionBegun {
21765 transaction_id: clock::Lamport,
21766 },
21767 Reloaded,
21768 CursorShapeChanged,
21769 PushedToNavHistory {
21770 anchor: Anchor,
21771 is_deactivate: bool,
21772 },
21773}
21774
21775impl EventEmitter<EditorEvent> for Editor {}
21776
21777impl Focusable for Editor {
21778 fn focus_handle(&self, _cx: &App) -> FocusHandle {
21779 self.focus_handle.clone()
21780 }
21781}
21782
21783impl Render for Editor {
21784 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21785 let settings = ThemeSettings::get_global(cx);
21786
21787 let mut text_style = match self.mode {
21788 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
21789 color: cx.theme().colors().editor_foreground,
21790 font_family: settings.ui_font.family.clone(),
21791 font_features: settings.ui_font.features.clone(),
21792 font_fallbacks: settings.ui_font.fallbacks.clone(),
21793 font_size: rems(0.875).into(),
21794 font_weight: settings.ui_font.weight,
21795 line_height: relative(settings.buffer_line_height.value()),
21796 ..Default::default()
21797 },
21798 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
21799 color: cx.theme().colors().editor_foreground,
21800 font_family: settings.buffer_font.family.clone(),
21801 font_features: settings.buffer_font.features.clone(),
21802 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21803 font_size: settings.buffer_font_size(cx).into(),
21804 font_weight: settings.buffer_font.weight,
21805 line_height: relative(settings.buffer_line_height.value()),
21806 ..Default::default()
21807 },
21808 };
21809 if let Some(text_style_refinement) = &self.text_style_refinement {
21810 text_style.refine(text_style_refinement)
21811 }
21812
21813 let background = match self.mode {
21814 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
21815 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
21816 EditorMode::Full { .. } => cx.theme().colors().editor_background,
21817 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
21818 };
21819
21820 EditorElement::new(
21821 &cx.entity(),
21822 EditorStyle {
21823 background,
21824 local_player: cx.theme().players().local(),
21825 text: text_style,
21826 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
21827 syntax: cx.theme().syntax().clone(),
21828 status: cx.theme().status().clone(),
21829 inlay_hints_style: make_inlay_hints_style(cx),
21830 inline_completion_styles: make_suggestion_styles(cx),
21831 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
21832 show_underlines: !self.mode.is_minimap(),
21833 },
21834 )
21835 }
21836}
21837
21838impl EntityInputHandler for Editor {
21839 fn text_for_range(
21840 &mut self,
21841 range_utf16: Range<usize>,
21842 adjusted_range: &mut Option<Range<usize>>,
21843 _: &mut Window,
21844 cx: &mut Context<Self>,
21845 ) -> Option<String> {
21846 let snapshot = self.buffer.read(cx).read(cx);
21847 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
21848 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
21849 if (start.0..end.0) != range_utf16 {
21850 adjusted_range.replace(start.0..end.0);
21851 }
21852 Some(snapshot.text_for_range(start..end).collect())
21853 }
21854
21855 fn selected_text_range(
21856 &mut self,
21857 ignore_disabled_input: bool,
21858 _: &mut Window,
21859 cx: &mut Context<Self>,
21860 ) -> Option<UTF16Selection> {
21861 // Prevent the IME menu from appearing when holding down an alphabetic key
21862 // while input is disabled.
21863 if !ignore_disabled_input && !self.input_enabled {
21864 return None;
21865 }
21866
21867 let selection = self.selections.newest::<OffsetUtf16>(cx);
21868 let range = selection.range();
21869
21870 Some(UTF16Selection {
21871 range: range.start.0..range.end.0,
21872 reversed: selection.reversed,
21873 })
21874 }
21875
21876 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
21877 let snapshot = self.buffer.read(cx).read(cx);
21878 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
21879 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
21880 }
21881
21882 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21883 self.clear_highlights::<InputComposition>(cx);
21884 self.ime_transaction.take();
21885 }
21886
21887 fn replace_text_in_range(
21888 &mut self,
21889 range_utf16: Option<Range<usize>>,
21890 text: &str,
21891 window: &mut Window,
21892 cx: &mut Context<Self>,
21893 ) {
21894 if !self.input_enabled {
21895 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21896 return;
21897 }
21898
21899 self.transact(window, cx, |this, window, cx| {
21900 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
21901 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21902 Some(this.selection_replacement_ranges(range_utf16, cx))
21903 } else {
21904 this.marked_text_ranges(cx)
21905 };
21906
21907 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
21908 let newest_selection_id = this.selections.newest_anchor().id;
21909 this.selections
21910 .all::<OffsetUtf16>(cx)
21911 .iter()
21912 .zip(ranges_to_replace.iter())
21913 .find_map(|(selection, range)| {
21914 if selection.id == newest_selection_id {
21915 Some(
21916 (range.start.0 as isize - selection.head().0 as isize)
21917 ..(range.end.0 as isize - selection.head().0 as isize),
21918 )
21919 } else {
21920 None
21921 }
21922 })
21923 });
21924
21925 cx.emit(EditorEvent::InputHandled {
21926 utf16_range_to_replace: range_to_replace,
21927 text: text.into(),
21928 });
21929
21930 if let Some(new_selected_ranges) = new_selected_ranges {
21931 this.change_selections(None, window, cx, |selections| {
21932 selections.select_ranges(new_selected_ranges)
21933 });
21934 this.backspace(&Default::default(), window, cx);
21935 }
21936
21937 this.handle_input(text, window, cx);
21938 });
21939
21940 if let Some(transaction) = self.ime_transaction {
21941 self.buffer.update(cx, |buffer, cx| {
21942 buffer.group_until_transaction(transaction, cx);
21943 });
21944 }
21945
21946 self.unmark_text(window, cx);
21947 }
21948
21949 fn replace_and_mark_text_in_range(
21950 &mut self,
21951 range_utf16: Option<Range<usize>>,
21952 text: &str,
21953 new_selected_range_utf16: Option<Range<usize>>,
21954 window: &mut Window,
21955 cx: &mut Context<Self>,
21956 ) {
21957 if !self.input_enabled {
21958 return;
21959 }
21960
21961 let transaction = self.transact(window, cx, |this, window, cx| {
21962 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
21963 let snapshot = this.buffer.read(cx).read(cx);
21964 if let Some(relative_range_utf16) = range_utf16.as_ref() {
21965 for marked_range in &mut marked_ranges {
21966 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
21967 marked_range.start.0 += relative_range_utf16.start;
21968 marked_range.start =
21969 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
21970 marked_range.end =
21971 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
21972 }
21973 }
21974 Some(marked_ranges)
21975 } else if let Some(range_utf16) = range_utf16 {
21976 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21977 Some(this.selection_replacement_ranges(range_utf16, cx))
21978 } else {
21979 None
21980 };
21981
21982 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
21983 let newest_selection_id = this.selections.newest_anchor().id;
21984 this.selections
21985 .all::<OffsetUtf16>(cx)
21986 .iter()
21987 .zip(ranges_to_replace.iter())
21988 .find_map(|(selection, range)| {
21989 if selection.id == newest_selection_id {
21990 Some(
21991 (range.start.0 as isize - selection.head().0 as isize)
21992 ..(range.end.0 as isize - selection.head().0 as isize),
21993 )
21994 } else {
21995 None
21996 }
21997 })
21998 });
21999
22000 cx.emit(EditorEvent::InputHandled {
22001 utf16_range_to_replace: range_to_replace,
22002 text: text.into(),
22003 });
22004
22005 if let Some(ranges) = ranges_to_replace {
22006 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
22007 }
22008
22009 let marked_ranges = {
22010 let snapshot = this.buffer.read(cx).read(cx);
22011 this.selections
22012 .disjoint_anchors()
22013 .iter()
22014 .map(|selection| {
22015 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22016 })
22017 .collect::<Vec<_>>()
22018 };
22019
22020 if text.is_empty() {
22021 this.unmark_text(window, cx);
22022 } else {
22023 this.highlight_text::<InputComposition>(
22024 marked_ranges.clone(),
22025 HighlightStyle {
22026 underline: Some(UnderlineStyle {
22027 thickness: px(1.),
22028 color: None,
22029 wavy: false,
22030 }),
22031 ..Default::default()
22032 },
22033 cx,
22034 );
22035 }
22036
22037 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22038 let use_autoclose = this.use_autoclose;
22039 let use_auto_surround = this.use_auto_surround;
22040 this.set_use_autoclose(false);
22041 this.set_use_auto_surround(false);
22042 this.handle_input(text, window, cx);
22043 this.set_use_autoclose(use_autoclose);
22044 this.set_use_auto_surround(use_auto_surround);
22045
22046 if let Some(new_selected_range) = new_selected_range_utf16 {
22047 let snapshot = this.buffer.read(cx).read(cx);
22048 let new_selected_ranges = marked_ranges
22049 .into_iter()
22050 .map(|marked_range| {
22051 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22052 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22053 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22054 snapshot.clip_offset_utf16(new_start, Bias::Left)
22055 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22056 })
22057 .collect::<Vec<_>>();
22058
22059 drop(snapshot);
22060 this.change_selections(None, window, cx, |selections| {
22061 selections.select_ranges(new_selected_ranges)
22062 });
22063 }
22064 });
22065
22066 self.ime_transaction = self.ime_transaction.or(transaction);
22067 if let Some(transaction) = self.ime_transaction {
22068 self.buffer.update(cx, |buffer, cx| {
22069 buffer.group_until_transaction(transaction, cx);
22070 });
22071 }
22072
22073 if self.text_highlights::<InputComposition>(cx).is_none() {
22074 self.ime_transaction.take();
22075 }
22076 }
22077
22078 fn bounds_for_range(
22079 &mut self,
22080 range_utf16: Range<usize>,
22081 element_bounds: gpui::Bounds<Pixels>,
22082 window: &mut Window,
22083 cx: &mut Context<Self>,
22084 ) -> Option<gpui::Bounds<Pixels>> {
22085 let text_layout_details = self.text_layout_details(window);
22086 let gpui::Size {
22087 width: em_width,
22088 height: line_height,
22089 } = self.character_size(window);
22090
22091 let snapshot = self.snapshot(window, cx);
22092 let scroll_position = snapshot.scroll_position();
22093 let scroll_left = scroll_position.x * em_width;
22094
22095 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22096 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22097 + self.gutter_dimensions.width
22098 + self.gutter_dimensions.margin;
22099 let y = line_height * (start.row().as_f32() - scroll_position.y);
22100
22101 Some(Bounds {
22102 origin: element_bounds.origin + point(x, y),
22103 size: size(em_width, line_height),
22104 })
22105 }
22106
22107 fn character_index_for_point(
22108 &mut self,
22109 point: gpui::Point<Pixels>,
22110 _window: &mut Window,
22111 _cx: &mut Context<Self>,
22112 ) -> Option<usize> {
22113 let position_map = self.last_position_map.as_ref()?;
22114 if !position_map.text_hitbox.contains(&point) {
22115 return None;
22116 }
22117 let display_point = position_map.point_for_position(point).previous_valid;
22118 let anchor = position_map
22119 .snapshot
22120 .display_point_to_anchor(display_point, Bias::Left);
22121 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22122 Some(utf16_offset.0)
22123 }
22124}
22125
22126trait SelectionExt {
22127 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22128 fn spanned_rows(
22129 &self,
22130 include_end_if_at_line_start: bool,
22131 map: &DisplaySnapshot,
22132 ) -> Range<MultiBufferRow>;
22133}
22134
22135impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22136 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22137 let start = self
22138 .start
22139 .to_point(&map.buffer_snapshot)
22140 .to_display_point(map);
22141 let end = self
22142 .end
22143 .to_point(&map.buffer_snapshot)
22144 .to_display_point(map);
22145 if self.reversed {
22146 end..start
22147 } else {
22148 start..end
22149 }
22150 }
22151
22152 fn spanned_rows(
22153 &self,
22154 include_end_if_at_line_start: bool,
22155 map: &DisplaySnapshot,
22156 ) -> Range<MultiBufferRow> {
22157 let start = self.start.to_point(&map.buffer_snapshot);
22158 let mut end = self.end.to_point(&map.buffer_snapshot);
22159 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22160 end.row -= 1;
22161 }
22162
22163 let buffer_start = map.prev_line_boundary(start).0;
22164 let buffer_end = map.next_line_boundary(end).0;
22165 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22166 }
22167}
22168
22169impl<T: InvalidationRegion> InvalidationStack<T> {
22170 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22171 where
22172 S: Clone + ToOffset,
22173 {
22174 while let Some(region) = self.last() {
22175 let all_selections_inside_invalidation_ranges =
22176 if selections.len() == region.ranges().len() {
22177 selections
22178 .iter()
22179 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
22180 .all(|(selection, invalidation_range)| {
22181 let head = selection.head().to_offset(buffer);
22182 invalidation_range.start <= head && invalidation_range.end >= head
22183 })
22184 } else {
22185 false
22186 };
22187
22188 if all_selections_inside_invalidation_ranges {
22189 break;
22190 } else {
22191 self.pop();
22192 }
22193 }
22194 }
22195}
22196
22197impl<T> Default for InvalidationStack<T> {
22198 fn default() -> Self {
22199 Self(Default::default())
22200 }
22201}
22202
22203impl<T> Deref for InvalidationStack<T> {
22204 type Target = Vec<T>;
22205
22206 fn deref(&self) -> &Self::Target {
22207 &self.0
22208 }
22209}
22210
22211impl<T> DerefMut for InvalidationStack<T> {
22212 fn deref_mut(&mut self) -> &mut Self::Target {
22213 &mut self.0
22214 }
22215}
22216
22217impl InvalidationRegion for SnippetState {
22218 fn ranges(&self) -> &[Range<Anchor>] {
22219 &self.ranges[self.active_index]
22220 }
22221}
22222
22223fn inline_completion_edit_text(
22224 current_snapshot: &BufferSnapshot,
22225 edits: &[(Range<Anchor>, String)],
22226 edit_preview: &EditPreview,
22227 include_deletions: bool,
22228 cx: &App,
22229) -> HighlightedText {
22230 let edits = edits
22231 .iter()
22232 .map(|(anchor, text)| {
22233 (
22234 anchor.start.text_anchor..anchor.end.text_anchor,
22235 text.clone(),
22236 )
22237 })
22238 .collect::<Vec<_>>();
22239
22240 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
22241}
22242
22243pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
22244 match severity {
22245 lsp::DiagnosticSeverity::ERROR => colors.error,
22246 lsp::DiagnosticSeverity::WARNING => colors.warning,
22247 lsp::DiagnosticSeverity::INFORMATION => colors.info,
22248 lsp::DiagnosticSeverity::HINT => colors.info,
22249 _ => colors.ignored,
22250 }
22251}
22252
22253pub fn styled_runs_for_code_label<'a>(
22254 label: &'a CodeLabel,
22255 syntax_theme: &'a theme::SyntaxTheme,
22256) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
22257 let fade_out = HighlightStyle {
22258 fade_out: Some(0.35),
22259 ..Default::default()
22260 };
22261
22262 let mut prev_end = label.filter_range.end;
22263 label
22264 .runs
22265 .iter()
22266 .enumerate()
22267 .flat_map(move |(ix, (range, highlight_id))| {
22268 let style = if let Some(style) = highlight_id.style(syntax_theme) {
22269 style
22270 } else {
22271 return Default::default();
22272 };
22273 let mut muted_style = style;
22274 muted_style.highlight(fade_out);
22275
22276 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
22277 if range.start >= label.filter_range.end {
22278 if range.start > prev_end {
22279 runs.push((prev_end..range.start, fade_out));
22280 }
22281 runs.push((range.clone(), muted_style));
22282 } else if range.end <= label.filter_range.end {
22283 runs.push((range.clone(), style));
22284 } else {
22285 runs.push((range.start..label.filter_range.end, style));
22286 runs.push((label.filter_range.end..range.end, muted_style));
22287 }
22288 prev_end = cmp::max(prev_end, range.end);
22289
22290 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
22291 runs.push((prev_end..label.text.len(), fade_out));
22292 }
22293
22294 runs
22295 })
22296}
22297
22298pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
22299 let mut prev_index = 0;
22300 let mut prev_codepoint: Option<char> = None;
22301 text.char_indices()
22302 .chain([(text.len(), '\0')])
22303 .filter_map(move |(index, codepoint)| {
22304 let prev_codepoint = prev_codepoint.replace(codepoint)?;
22305 let is_boundary = index == text.len()
22306 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
22307 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
22308 if is_boundary {
22309 let chunk = &text[prev_index..index];
22310 prev_index = index;
22311 Some(chunk)
22312 } else {
22313 None
22314 }
22315 })
22316}
22317
22318pub trait RangeToAnchorExt: Sized {
22319 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
22320
22321 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
22322 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
22323 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
22324 }
22325}
22326
22327impl<T: ToOffset> RangeToAnchorExt for Range<T> {
22328 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
22329 let start_offset = self.start.to_offset(snapshot);
22330 let end_offset = self.end.to_offset(snapshot);
22331 if start_offset == end_offset {
22332 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
22333 } else {
22334 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
22335 }
22336 }
22337}
22338
22339pub trait RowExt {
22340 fn as_f32(&self) -> f32;
22341
22342 fn next_row(&self) -> Self;
22343
22344 fn previous_row(&self) -> Self;
22345
22346 fn minus(&self, other: Self) -> u32;
22347}
22348
22349impl RowExt for DisplayRow {
22350 fn as_f32(&self) -> f32 {
22351 self.0 as f32
22352 }
22353
22354 fn next_row(&self) -> Self {
22355 Self(self.0 + 1)
22356 }
22357
22358 fn previous_row(&self) -> Self {
22359 Self(self.0.saturating_sub(1))
22360 }
22361
22362 fn minus(&self, other: Self) -> u32 {
22363 self.0 - other.0
22364 }
22365}
22366
22367impl RowExt for MultiBufferRow {
22368 fn as_f32(&self) -> f32 {
22369 self.0 as f32
22370 }
22371
22372 fn next_row(&self) -> Self {
22373 Self(self.0 + 1)
22374 }
22375
22376 fn previous_row(&self) -> Self {
22377 Self(self.0.saturating_sub(1))
22378 }
22379
22380 fn minus(&self, other: Self) -> u32 {
22381 self.0 - other.0
22382 }
22383}
22384
22385trait RowRangeExt {
22386 type Row;
22387
22388 fn len(&self) -> usize;
22389
22390 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
22391}
22392
22393impl RowRangeExt for Range<MultiBufferRow> {
22394 type Row = MultiBufferRow;
22395
22396 fn len(&self) -> usize {
22397 (self.end.0 - self.start.0) as usize
22398 }
22399
22400 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
22401 (self.start.0..self.end.0).map(MultiBufferRow)
22402 }
22403}
22404
22405impl RowRangeExt for Range<DisplayRow> {
22406 type Row = DisplayRow;
22407
22408 fn len(&self) -> usize {
22409 (self.end.0 - self.start.0) as usize
22410 }
22411
22412 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
22413 (self.start.0..self.end.0).map(DisplayRow)
22414 }
22415}
22416
22417/// If select range has more than one line, we
22418/// just point the cursor to range.start.
22419fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
22420 if range.start.row == range.end.row {
22421 range
22422 } else {
22423 range.start..range.start
22424 }
22425}
22426pub struct KillRing(ClipboardItem);
22427impl Global for KillRing {}
22428
22429const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
22430
22431enum BreakpointPromptEditAction {
22432 Log,
22433 Condition,
22434 HitCondition,
22435}
22436
22437struct BreakpointPromptEditor {
22438 pub(crate) prompt: Entity<Editor>,
22439 editor: WeakEntity<Editor>,
22440 breakpoint_anchor: Anchor,
22441 breakpoint: Breakpoint,
22442 edit_action: BreakpointPromptEditAction,
22443 block_ids: HashSet<CustomBlockId>,
22444 editor_margins: Arc<Mutex<EditorMargins>>,
22445 _subscriptions: Vec<Subscription>,
22446}
22447
22448impl BreakpointPromptEditor {
22449 const MAX_LINES: u8 = 4;
22450
22451 fn new(
22452 editor: WeakEntity<Editor>,
22453 breakpoint_anchor: Anchor,
22454 breakpoint: Breakpoint,
22455 edit_action: BreakpointPromptEditAction,
22456 window: &mut Window,
22457 cx: &mut Context<Self>,
22458 ) -> Self {
22459 let base_text = match edit_action {
22460 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
22461 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
22462 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
22463 }
22464 .map(|msg| msg.to_string())
22465 .unwrap_or_default();
22466
22467 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
22468 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
22469
22470 let prompt = cx.new(|cx| {
22471 let mut prompt = Editor::new(
22472 EditorMode::AutoHeight {
22473 max_lines: Self::MAX_LINES as usize,
22474 },
22475 buffer,
22476 None,
22477 window,
22478 cx,
22479 );
22480 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
22481 prompt.set_show_cursor_when_unfocused(false, cx);
22482 prompt.set_placeholder_text(
22483 match edit_action {
22484 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
22485 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
22486 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
22487 },
22488 cx,
22489 );
22490
22491 prompt
22492 });
22493
22494 Self {
22495 prompt,
22496 editor,
22497 breakpoint_anchor,
22498 breakpoint,
22499 edit_action,
22500 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
22501 block_ids: Default::default(),
22502 _subscriptions: vec![],
22503 }
22504 }
22505
22506 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
22507 self.block_ids.extend(block_ids)
22508 }
22509
22510 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
22511 if let Some(editor) = self.editor.upgrade() {
22512 let message = self
22513 .prompt
22514 .read(cx)
22515 .buffer
22516 .read(cx)
22517 .as_singleton()
22518 .expect("A multi buffer in breakpoint prompt isn't possible")
22519 .read(cx)
22520 .as_rope()
22521 .to_string();
22522
22523 editor.update(cx, |editor, cx| {
22524 editor.edit_breakpoint_at_anchor(
22525 self.breakpoint_anchor,
22526 self.breakpoint.clone(),
22527 match self.edit_action {
22528 BreakpointPromptEditAction::Log => {
22529 BreakpointEditAction::EditLogMessage(message.into())
22530 }
22531 BreakpointPromptEditAction::Condition => {
22532 BreakpointEditAction::EditCondition(message.into())
22533 }
22534 BreakpointPromptEditAction::HitCondition => {
22535 BreakpointEditAction::EditHitCondition(message.into())
22536 }
22537 },
22538 cx,
22539 );
22540
22541 editor.remove_blocks(self.block_ids.clone(), None, cx);
22542 cx.focus_self(window);
22543 });
22544 }
22545 }
22546
22547 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
22548 self.editor
22549 .update(cx, |editor, cx| {
22550 editor.remove_blocks(self.block_ids.clone(), None, cx);
22551 window.focus(&editor.focus_handle);
22552 })
22553 .log_err();
22554 }
22555
22556 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
22557 let settings = ThemeSettings::get_global(cx);
22558 let text_style = TextStyle {
22559 color: if self.prompt.read(cx).read_only(cx) {
22560 cx.theme().colors().text_disabled
22561 } else {
22562 cx.theme().colors().text
22563 },
22564 font_family: settings.buffer_font.family.clone(),
22565 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22566 font_size: settings.buffer_font_size(cx).into(),
22567 font_weight: settings.buffer_font.weight,
22568 line_height: relative(settings.buffer_line_height.value()),
22569 ..Default::default()
22570 };
22571 EditorElement::new(
22572 &self.prompt,
22573 EditorStyle {
22574 background: cx.theme().colors().editor_background,
22575 local_player: cx.theme().players().local(),
22576 text: text_style,
22577 ..Default::default()
22578 },
22579 )
22580 }
22581}
22582
22583impl Render for BreakpointPromptEditor {
22584 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22585 let editor_margins = *self.editor_margins.lock();
22586 let gutter_dimensions = editor_margins.gutter;
22587 h_flex()
22588 .key_context("Editor")
22589 .bg(cx.theme().colors().editor_background)
22590 .border_y_1()
22591 .border_color(cx.theme().status().info_border)
22592 .size_full()
22593 .py(window.line_height() / 2.5)
22594 .on_action(cx.listener(Self::confirm))
22595 .on_action(cx.listener(Self::cancel))
22596 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
22597 .child(div().flex_1().child(self.render_prompt_editor(cx)))
22598 }
22599}
22600
22601impl Focusable for BreakpointPromptEditor {
22602 fn focus_handle(&self, cx: &App) -> FocusHandle {
22603 self.prompt.focus_handle(cx)
22604 }
22605}
22606
22607fn all_edits_insertions_or_deletions(
22608 edits: &Vec<(Range<Anchor>, String)>,
22609 snapshot: &MultiBufferSnapshot,
22610) -> bool {
22611 let mut all_insertions = true;
22612 let mut all_deletions = true;
22613
22614 for (range, new_text) in edits.iter() {
22615 let range_is_empty = range.to_offset(&snapshot).is_empty();
22616 let text_is_empty = new_text.is_empty();
22617
22618 if range_is_empty != text_is_empty {
22619 if range_is_empty {
22620 all_deletions = false;
22621 } else {
22622 all_insertions = false;
22623 }
22624 } else {
22625 return false;
22626 }
22627
22628 if !all_insertions && !all_deletions {
22629 return false;
22630 }
22631 }
22632 all_insertions || all_deletions
22633}
22634
22635struct MissingEditPredictionKeybindingTooltip;
22636
22637impl Render for MissingEditPredictionKeybindingTooltip {
22638 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22639 ui::tooltip_container(window, cx, |container, _, cx| {
22640 container
22641 .flex_shrink_0()
22642 .max_w_80()
22643 .min_h(rems_from_px(124.))
22644 .justify_between()
22645 .child(
22646 v_flex()
22647 .flex_1()
22648 .text_ui_sm(cx)
22649 .child(Label::new("Conflict with Accept Keybinding"))
22650 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
22651 )
22652 .child(
22653 h_flex()
22654 .pb_1()
22655 .gap_1()
22656 .items_end()
22657 .w_full()
22658 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
22659 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
22660 }))
22661 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
22662 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
22663 })),
22664 )
22665 })
22666 }
22667}
22668
22669#[derive(Debug, Clone, Copy, PartialEq)]
22670pub struct LineHighlight {
22671 pub background: Background,
22672 pub border: Option<gpui::Hsla>,
22673 pub include_gutter: bool,
22674 pub type_id: Option<TypeId>,
22675}
22676
22677fn render_diff_hunk_controls(
22678 row: u32,
22679 status: &DiffHunkStatus,
22680 hunk_range: Range<Anchor>,
22681 is_created_file: bool,
22682 line_height: Pixels,
22683 editor: &Entity<Editor>,
22684 _window: &mut Window,
22685 cx: &mut App,
22686) -> AnyElement {
22687 h_flex()
22688 .h(line_height)
22689 .mr_1()
22690 .gap_1()
22691 .px_0p5()
22692 .pb_1()
22693 .border_x_1()
22694 .border_b_1()
22695 .border_color(cx.theme().colors().border_variant)
22696 .rounded_b_lg()
22697 .bg(cx.theme().colors().editor_background)
22698 .gap_1()
22699 .block_mouse_except_scroll()
22700 .shadow_md()
22701 .child(if status.has_secondary_hunk() {
22702 Button::new(("stage", row as u64), "Stage")
22703 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22704 .tooltip({
22705 let focus_handle = editor.focus_handle(cx);
22706 move |window, cx| {
22707 Tooltip::for_action_in(
22708 "Stage Hunk",
22709 &::git::ToggleStaged,
22710 &focus_handle,
22711 window,
22712 cx,
22713 )
22714 }
22715 })
22716 .on_click({
22717 let editor = editor.clone();
22718 move |_event, _window, cx| {
22719 editor.update(cx, |editor, cx| {
22720 editor.stage_or_unstage_diff_hunks(
22721 true,
22722 vec![hunk_range.start..hunk_range.start],
22723 cx,
22724 );
22725 });
22726 }
22727 })
22728 } else {
22729 Button::new(("unstage", row as u64), "Unstage")
22730 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22731 .tooltip({
22732 let focus_handle = editor.focus_handle(cx);
22733 move |window, cx| {
22734 Tooltip::for_action_in(
22735 "Unstage Hunk",
22736 &::git::ToggleStaged,
22737 &focus_handle,
22738 window,
22739 cx,
22740 )
22741 }
22742 })
22743 .on_click({
22744 let editor = editor.clone();
22745 move |_event, _window, cx| {
22746 editor.update(cx, |editor, cx| {
22747 editor.stage_or_unstage_diff_hunks(
22748 false,
22749 vec![hunk_range.start..hunk_range.start],
22750 cx,
22751 );
22752 });
22753 }
22754 })
22755 })
22756 .child(
22757 Button::new(("restore", row as u64), "Restore")
22758 .tooltip({
22759 let focus_handle = editor.focus_handle(cx);
22760 move |window, cx| {
22761 Tooltip::for_action_in(
22762 "Restore Hunk",
22763 &::git::Restore,
22764 &focus_handle,
22765 window,
22766 cx,
22767 )
22768 }
22769 })
22770 .on_click({
22771 let editor = editor.clone();
22772 move |_event, window, cx| {
22773 editor.update(cx, |editor, cx| {
22774 let snapshot = editor.snapshot(window, cx);
22775 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
22776 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
22777 });
22778 }
22779 })
22780 .disabled(is_created_file),
22781 )
22782 .when(
22783 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
22784 |el| {
22785 el.child(
22786 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
22787 .shape(IconButtonShape::Square)
22788 .icon_size(IconSize::Small)
22789 // .disabled(!has_multiple_hunks)
22790 .tooltip({
22791 let focus_handle = editor.focus_handle(cx);
22792 move |window, cx| {
22793 Tooltip::for_action_in(
22794 "Next Hunk",
22795 &GoToHunk,
22796 &focus_handle,
22797 window,
22798 cx,
22799 )
22800 }
22801 })
22802 .on_click({
22803 let editor = editor.clone();
22804 move |_event, window, cx| {
22805 editor.update(cx, |editor, cx| {
22806 let snapshot = editor.snapshot(window, cx);
22807 let position =
22808 hunk_range.end.to_point(&snapshot.buffer_snapshot);
22809 editor.go_to_hunk_before_or_after_position(
22810 &snapshot,
22811 position,
22812 Direction::Next,
22813 window,
22814 cx,
22815 );
22816 editor.expand_selected_diff_hunks(cx);
22817 });
22818 }
22819 }),
22820 )
22821 .child(
22822 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
22823 .shape(IconButtonShape::Square)
22824 .icon_size(IconSize::Small)
22825 // .disabled(!has_multiple_hunks)
22826 .tooltip({
22827 let focus_handle = editor.focus_handle(cx);
22828 move |window, cx| {
22829 Tooltip::for_action_in(
22830 "Previous Hunk",
22831 &GoToPreviousHunk,
22832 &focus_handle,
22833 window,
22834 cx,
22835 )
22836 }
22837 })
22838 .on_click({
22839 let editor = editor.clone();
22840 move |_event, window, cx| {
22841 editor.update(cx, |editor, cx| {
22842 let snapshot = editor.snapshot(window, cx);
22843 let point =
22844 hunk_range.start.to_point(&snapshot.buffer_snapshot);
22845 editor.go_to_hunk_before_or_after_position(
22846 &snapshot,
22847 point,
22848 Direction::Prev,
22849 window,
22850 cx,
22851 );
22852 editor.expand_selected_diff_hunks(cx);
22853 });
22854 }
22855 }),
22856 )
22857 },
22858 )
22859 .into_any_element()
22860}