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 MIN_LINE_NUMBER_DIGITS: u32 = 4;
244pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
245
246pub type RenderDiffHunkControlsFn = Arc<
247 dyn Fn(
248 u32,
249 &DiffHunkStatus,
250 Range<Anchor>,
251 bool,
252 Pixels,
253 &Entity<Editor>,
254 &mut Window,
255 &mut App,
256 ) -> AnyElement,
257>;
258
259struct InlineValueCache {
260 enabled: bool,
261 inlays: Vec<InlayId>,
262 refresh_task: Task<Option<()>>,
263}
264
265impl InlineValueCache {
266 fn new(enabled: bool) -> Self {
267 Self {
268 enabled,
269 inlays: Vec::new(),
270 refresh_task: Task::ready(None),
271 }
272 }
273}
274
275#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
276pub enum InlayId {
277 InlineCompletion(usize),
278 Hint(usize),
279 DebuggerValue(usize),
280}
281
282impl InlayId {
283 fn id(&self) -> usize {
284 match self {
285 Self::InlineCompletion(id) => *id,
286 Self::Hint(id) => *id,
287 Self::DebuggerValue(id) => *id,
288 }
289 }
290}
291
292pub enum ActiveDebugLine {}
293pub enum DebugStackFrameLine {}
294enum DocumentHighlightRead {}
295enum DocumentHighlightWrite {}
296enum InputComposition {}
297pub enum PendingInput {}
298enum SelectedTextHighlight {}
299
300pub enum ConflictsOuter {}
301pub enum ConflictsOurs {}
302pub enum ConflictsTheirs {}
303pub enum ConflictsOursMarker {}
304pub enum ConflictsTheirsMarker {}
305
306#[derive(Debug, Copy, Clone, PartialEq, Eq)]
307pub enum Navigated {
308 Yes,
309 No,
310}
311
312impl Navigated {
313 pub fn from_bool(yes: bool) -> Navigated {
314 if yes { Navigated::Yes } else { Navigated::No }
315 }
316}
317
318#[derive(Debug, Clone, PartialEq, Eq)]
319enum DisplayDiffHunk {
320 Folded {
321 display_row: DisplayRow,
322 },
323 Unfolded {
324 is_created_file: bool,
325 diff_base_byte_range: Range<usize>,
326 display_row_range: Range<DisplayRow>,
327 multi_buffer_range: Range<Anchor>,
328 status: DiffHunkStatus,
329 },
330}
331
332pub enum HideMouseCursorOrigin {
333 TypingAction,
334 MovementAction,
335}
336
337pub fn init_settings(cx: &mut App) {
338 EditorSettings::register(cx);
339}
340
341pub fn init(cx: &mut App) {
342 init_settings(cx);
343
344 cx.set_global(GlobalBlameRenderer(Arc::new(())));
345
346 workspace::register_project_item::<Editor>(cx);
347 workspace::FollowableViewRegistry::register::<Editor>(cx);
348 workspace::register_serializable_item::<Editor>(cx);
349
350 cx.observe_new(
351 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
352 workspace.register_action(Editor::new_file);
353 workspace.register_action(Editor::new_file_vertical);
354 workspace.register_action(Editor::new_file_horizontal);
355 workspace.register_action(Editor::cancel_language_server_work);
356 },
357 )
358 .detach();
359
360 cx.on_action(move |_: &workspace::NewFile, cx| {
361 let app_state = workspace::AppState::global(cx);
362 if let Some(app_state) = app_state.upgrade() {
363 workspace::open_new(
364 Default::default(),
365 app_state,
366 cx,
367 |workspace, window, cx| {
368 Editor::new_file(workspace, &Default::default(), window, cx)
369 },
370 )
371 .detach();
372 }
373 });
374 cx.on_action(move |_: &workspace::NewWindow, cx| {
375 let app_state = workspace::AppState::global(cx);
376 if let Some(app_state) = app_state.upgrade() {
377 workspace::open_new(
378 Default::default(),
379 app_state,
380 cx,
381 |workspace, window, cx| {
382 cx.activate(true);
383 Editor::new_file(workspace, &Default::default(), window, cx)
384 },
385 )
386 .detach();
387 }
388 });
389}
390
391pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
392 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
393}
394
395pub trait DiagnosticRenderer {
396 fn render_group(
397 &self,
398 diagnostic_group: Vec<DiagnosticEntry<Point>>,
399 buffer_id: BufferId,
400 snapshot: EditorSnapshot,
401 editor: WeakEntity<Editor>,
402 cx: &mut App,
403 ) -> Vec<BlockProperties<Anchor>>;
404
405 fn render_hover(
406 &self,
407 diagnostic_group: Vec<DiagnosticEntry<Point>>,
408 range: Range<Point>,
409 buffer_id: BufferId,
410 cx: &mut App,
411 ) -> Option<Entity<markdown::Markdown>>;
412
413 fn open_link(
414 &self,
415 editor: &mut Editor,
416 link: SharedString,
417 window: &mut Window,
418 cx: &mut Context<Editor>,
419 );
420}
421
422pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
423
424impl GlobalDiagnosticRenderer {
425 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
426 cx.try_global::<Self>().map(|g| g.0.clone())
427 }
428}
429
430impl gpui::Global for GlobalDiagnosticRenderer {}
431pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
432 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
433}
434
435pub struct SearchWithinRange;
436
437trait InvalidationRegion {
438 fn ranges(&self) -> &[Range<Anchor>];
439}
440
441#[derive(Clone, Debug, PartialEq)]
442pub enum SelectPhase {
443 Begin {
444 position: DisplayPoint,
445 add: bool,
446 click_count: usize,
447 },
448 BeginColumnar {
449 position: DisplayPoint,
450 reset: bool,
451 goal_column: u32,
452 },
453 Extend {
454 position: DisplayPoint,
455 click_count: usize,
456 },
457 Update {
458 position: DisplayPoint,
459 goal_column: u32,
460 scroll_delta: gpui::Point<f32>,
461 },
462 End,
463}
464
465#[derive(Clone, Debug)]
466pub enum SelectMode {
467 Character,
468 Word(Range<Anchor>),
469 Line(Range<Anchor>),
470 All,
471}
472
473#[derive(Clone, PartialEq, Eq, Debug)]
474pub enum EditorMode {
475 SingleLine {
476 auto_width: bool,
477 },
478 AutoHeight {
479 max_lines: usize,
480 },
481 Full {
482 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
483 scale_ui_elements_with_buffer_font_size: bool,
484 /// When set to `true`, the editor will render a background for the active line.
485 show_active_line_background: bool,
486 /// When set to `true`, the editor's height will be determined by its content.
487 sized_by_content: bool,
488 },
489 Minimap {
490 parent: WeakEntity<Editor>,
491 },
492}
493
494impl EditorMode {
495 pub fn full() -> Self {
496 Self::Full {
497 scale_ui_elements_with_buffer_font_size: true,
498 show_active_line_background: true,
499 sized_by_content: false,
500 }
501 }
502
503 pub fn is_full(&self) -> bool {
504 matches!(self, Self::Full { .. })
505 }
506
507 fn is_minimap(&self) -> bool {
508 matches!(self, Self::Minimap { .. })
509 }
510}
511
512#[derive(Copy, Clone, Debug)]
513pub enum SoftWrap {
514 /// Prefer not to wrap at all.
515 ///
516 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
517 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
518 GitDiff,
519 /// Prefer a single line generally, unless an overly long line is encountered.
520 None,
521 /// Soft wrap lines that exceed the editor width.
522 EditorWidth,
523 /// Soft wrap lines at the preferred line length.
524 Column(u32),
525 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
526 Bounded(u32),
527}
528
529#[derive(Clone)]
530pub struct EditorStyle {
531 pub background: Hsla,
532 pub local_player: PlayerColor,
533 pub text: TextStyle,
534 pub scrollbar_width: Pixels,
535 pub syntax: Arc<SyntaxTheme>,
536 pub status: StatusColors,
537 pub inlay_hints_style: HighlightStyle,
538 pub inline_completion_styles: InlineCompletionStyles,
539 pub unnecessary_code_fade: f32,
540 pub show_underlines: bool,
541}
542
543impl Default for EditorStyle {
544 fn default() -> Self {
545 Self {
546 background: Hsla::default(),
547 local_player: PlayerColor::default(),
548 text: TextStyle::default(),
549 scrollbar_width: Pixels::default(),
550 syntax: Default::default(),
551 // HACK: Status colors don't have a real default.
552 // We should look into removing the status colors from the editor
553 // style and retrieve them directly from the theme.
554 status: StatusColors::dark(),
555 inlay_hints_style: HighlightStyle::default(),
556 inline_completion_styles: InlineCompletionStyles {
557 insertion: HighlightStyle::default(),
558 whitespace: HighlightStyle::default(),
559 },
560 unnecessary_code_fade: Default::default(),
561 show_underlines: true,
562 }
563 }
564}
565
566pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
567 let show_background = language_settings::language_settings(None, None, cx)
568 .inlay_hints
569 .show_background;
570
571 HighlightStyle {
572 color: Some(cx.theme().status().hint),
573 background_color: show_background.then(|| cx.theme().status().hint_background),
574 ..HighlightStyle::default()
575 }
576}
577
578pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
579 InlineCompletionStyles {
580 insertion: HighlightStyle {
581 color: Some(cx.theme().status().predictive),
582 ..HighlightStyle::default()
583 },
584 whitespace: HighlightStyle {
585 background_color: Some(cx.theme().status().created_background),
586 ..HighlightStyle::default()
587 },
588 }
589}
590
591type CompletionId = usize;
592
593pub(crate) enum EditDisplayMode {
594 TabAccept,
595 DiffPopover,
596 Inline,
597}
598
599enum InlineCompletion {
600 Edit {
601 edits: Vec<(Range<Anchor>, String)>,
602 edit_preview: Option<EditPreview>,
603 display_mode: EditDisplayMode,
604 snapshot: BufferSnapshot,
605 },
606 Move {
607 target: Anchor,
608 snapshot: BufferSnapshot,
609 },
610}
611
612struct InlineCompletionState {
613 inlay_ids: Vec<InlayId>,
614 completion: InlineCompletion,
615 completion_id: Option<SharedString>,
616 invalidation_range: Range<Anchor>,
617}
618
619enum EditPredictionSettings {
620 Disabled,
621 Enabled {
622 show_in_menu: bool,
623 preview_requires_modifier: bool,
624 },
625}
626
627enum InlineCompletionHighlight {}
628
629#[derive(Debug, Clone)]
630struct InlineDiagnostic {
631 message: SharedString,
632 group_id: usize,
633 is_primary: bool,
634 start: Point,
635 severity: lsp::DiagnosticSeverity,
636}
637
638pub enum MenuInlineCompletionsPolicy {
639 Never,
640 ByProvider,
641}
642
643pub enum EditPredictionPreview {
644 /// Modifier is not pressed
645 Inactive { released_too_fast: bool },
646 /// Modifier pressed
647 Active {
648 since: Instant,
649 previous_scroll_position: Option<ScrollAnchor>,
650 },
651}
652
653impl EditPredictionPreview {
654 pub fn released_too_fast(&self) -> bool {
655 match self {
656 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
657 EditPredictionPreview::Active { .. } => false,
658 }
659 }
660
661 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
662 if let EditPredictionPreview::Active {
663 previous_scroll_position,
664 ..
665 } = self
666 {
667 *previous_scroll_position = scroll_position;
668 }
669 }
670}
671
672pub struct ContextMenuOptions {
673 pub min_entries_visible: usize,
674 pub max_entries_visible: usize,
675 pub placement: Option<ContextMenuPlacement>,
676}
677
678#[derive(Debug, Clone, PartialEq, Eq)]
679pub enum ContextMenuPlacement {
680 Above,
681 Below,
682}
683
684#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
685struct EditorActionId(usize);
686
687impl EditorActionId {
688 pub fn post_inc(&mut self) -> Self {
689 let answer = self.0;
690
691 *self = Self(answer + 1);
692
693 Self(answer)
694 }
695}
696
697// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
698// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
699
700type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
701type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
702
703#[derive(Default)]
704struct ScrollbarMarkerState {
705 scrollbar_size: Size<Pixels>,
706 dirty: bool,
707 markers: Arc<[PaintQuad]>,
708 pending_refresh: Option<Task<Result<()>>>,
709}
710
711impl ScrollbarMarkerState {
712 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
713 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
714 }
715}
716
717#[derive(Clone, Copy, PartialEq, Eq)]
718pub enum MinimapVisibility {
719 Disabled,
720 Enabled {
721 /// The configuration currently present in the users settings.
722 setting_configuration: bool,
723 /// Whether to override the currently set visibility from the users setting.
724 toggle_override: bool,
725 },
726}
727
728impl MinimapVisibility {
729 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
730 if mode.is_full() {
731 Self::Enabled {
732 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
733 toggle_override: false,
734 }
735 } else {
736 Self::Disabled
737 }
738 }
739
740 fn hidden(&self) -> Self {
741 match *self {
742 Self::Enabled {
743 setting_configuration,
744 ..
745 } => Self::Enabled {
746 setting_configuration,
747 toggle_override: setting_configuration,
748 },
749 Self::Disabled => Self::Disabled,
750 }
751 }
752
753 fn disabled(&self) -> bool {
754 match *self {
755 Self::Disabled => true,
756 _ => false,
757 }
758 }
759
760 fn settings_visibility(&self) -> bool {
761 match *self {
762 Self::Enabled {
763 setting_configuration,
764 ..
765 } => setting_configuration,
766 _ => false,
767 }
768 }
769
770 fn visible(&self) -> bool {
771 match *self {
772 Self::Enabled {
773 setting_configuration,
774 toggle_override,
775 } => setting_configuration ^ toggle_override,
776 _ => false,
777 }
778 }
779
780 fn toggle_visibility(&self) -> Self {
781 match *self {
782 Self::Enabled {
783 toggle_override,
784 setting_configuration,
785 } => Self::Enabled {
786 setting_configuration,
787 toggle_override: !toggle_override,
788 },
789 Self::Disabled => Self::Disabled,
790 }
791 }
792}
793
794#[derive(Clone, Debug)]
795struct RunnableTasks {
796 templates: Vec<(TaskSourceKind, TaskTemplate)>,
797 offset: multi_buffer::Anchor,
798 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
799 column: u32,
800 // Values of all named captures, including those starting with '_'
801 extra_variables: HashMap<String, String>,
802 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
803 context_range: Range<BufferOffset>,
804}
805
806impl RunnableTasks {
807 fn resolve<'a>(
808 &'a self,
809 cx: &'a task::TaskContext,
810 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
811 self.templates.iter().filter_map(|(kind, template)| {
812 template
813 .resolve_task(&kind.to_id_base(), cx)
814 .map(|task| (kind.clone(), task))
815 })
816 }
817}
818
819#[derive(Clone)]
820pub struct ResolvedTasks {
821 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
822 position: Anchor,
823}
824
825#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
826struct BufferOffset(usize);
827
828// Addons allow storing per-editor state in other crates (e.g. Vim)
829pub trait Addon: 'static {
830 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
831
832 fn render_buffer_header_controls(
833 &self,
834 _: &ExcerptInfo,
835 _: &Window,
836 _: &App,
837 ) -> Option<AnyElement> {
838 None
839 }
840
841 fn to_any(&self) -> &dyn std::any::Any;
842
843 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
844 None
845 }
846}
847
848/// A set of caret positions, registered when the editor was edited.
849pub struct ChangeList {
850 changes: Vec<Vec<Anchor>>,
851 /// Currently "selected" change.
852 position: Option<usize>,
853}
854
855impl ChangeList {
856 pub fn new() -> Self {
857 Self {
858 changes: Vec::new(),
859 position: None,
860 }
861 }
862
863 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
864 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
865 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
866 if self.changes.is_empty() {
867 return None;
868 }
869
870 let prev = self.position.unwrap_or(self.changes.len());
871 let next = if direction == Direction::Prev {
872 prev.saturating_sub(count)
873 } else {
874 (prev + count).min(self.changes.len() - 1)
875 };
876 self.position = Some(next);
877 self.changes.get(next).map(|anchors| anchors.as_slice())
878 }
879
880 /// Adds a new change to the list, resetting the change list position.
881 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
882 self.position.take();
883 if pop_state {
884 self.changes.pop();
885 }
886 self.changes.push(new_positions.clone());
887 }
888
889 pub fn last(&self) -> Option<&[Anchor]> {
890 self.changes.last().map(|anchors| anchors.as_slice())
891 }
892}
893
894#[derive(Clone)]
895struct InlineBlamePopoverState {
896 scroll_handle: ScrollHandle,
897 commit_message: Option<ParsedCommitMessage>,
898 markdown: Entity<Markdown>,
899}
900
901struct InlineBlamePopover {
902 position: gpui::Point<Pixels>,
903 show_task: Option<Task<()>>,
904 hide_task: Option<Task<()>>,
905 popover_bounds: Option<Bounds<Pixels>>,
906 popover_state: InlineBlamePopoverState,
907}
908
909enum SelectionDragState {
910 /// State when no drag related activity is detected.
911 None,
912 /// State when the mouse is down on a selection that is about to be dragged.
913 ReadyToDrag {
914 selection: Selection<Anchor>,
915 click_position: gpui::Point<Pixels>,
916 },
917 /// State when the mouse is dragging the selection in the editor.
918 Dragging {
919 selection: Selection<Anchor>,
920 drop_cursor: Selection<Anchor>,
921 hide_drop_cursor: bool,
922 },
923}
924
925/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
926/// a breakpoint on them.
927#[derive(Clone, Copy, Debug, PartialEq, Eq)]
928struct PhantomBreakpointIndicator {
929 display_row: DisplayRow,
930 /// There's a small debounce between hovering over the line and showing the indicator.
931 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
932 is_active: bool,
933 collides_with_existing_breakpoint: bool,
934}
935
936/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
937///
938/// See the [module level documentation](self) for more information.
939pub struct Editor {
940 focus_handle: FocusHandle,
941 last_focused_descendant: Option<WeakFocusHandle>,
942 /// The text buffer being edited
943 buffer: Entity<MultiBuffer>,
944 /// Map of how text in the buffer should be displayed.
945 /// Handles soft wraps, folds, fake inlay text insertions, etc.
946 pub display_map: Entity<DisplayMap>,
947 pub selections: SelectionsCollection,
948 pub scroll_manager: ScrollManager,
949 /// When inline assist editors are linked, they all render cursors because
950 /// typing enters text into each of them, even the ones that aren't focused.
951 pub(crate) show_cursor_when_unfocused: bool,
952 columnar_selection_tail: Option<Anchor>,
953 columnar_display_point: Option<DisplayPoint>,
954 add_selections_state: Option<AddSelectionsState>,
955 select_next_state: Option<SelectNextState>,
956 select_prev_state: Option<SelectNextState>,
957 selection_history: SelectionHistory,
958 defer_selection_effects: bool,
959 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
960 autoclose_regions: Vec<AutocloseRegion>,
961 snippet_stack: InvalidationStack<SnippetState>,
962 select_syntax_node_history: SelectSyntaxNodeHistory,
963 ime_transaction: Option<TransactionId>,
964 pub diagnostics_max_severity: DiagnosticSeverity,
965 active_diagnostics: ActiveDiagnostic,
966 show_inline_diagnostics: bool,
967 inline_diagnostics_update: Task<()>,
968 inline_diagnostics_enabled: bool,
969 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
970 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
971 hard_wrap: Option<usize>,
972
973 // TODO: make this a access method
974 pub project: Option<Entity<Project>>,
975 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
976 completion_provider: Option<Rc<dyn CompletionProvider>>,
977 collaboration_hub: Option<Box<dyn CollaborationHub>>,
978 blink_manager: Entity<BlinkManager>,
979 show_cursor_names: bool,
980 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
981 pub show_local_selections: bool,
982 mode: EditorMode,
983 show_breadcrumbs: bool,
984 show_gutter: bool,
985 show_scrollbars: ScrollbarAxes,
986 minimap_visibility: MinimapVisibility,
987 offset_content: bool,
988 disable_expand_excerpt_buttons: bool,
989 show_line_numbers: Option<bool>,
990 use_relative_line_numbers: Option<bool>,
991 show_git_diff_gutter: Option<bool>,
992 show_code_actions: Option<bool>,
993 show_runnables: Option<bool>,
994 show_breakpoints: Option<bool>,
995 show_wrap_guides: Option<bool>,
996 show_indent_guides: Option<bool>,
997 placeholder_text: Option<Arc<str>>,
998 highlight_order: usize,
999 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1000 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
1001 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1002 scrollbar_marker_state: ScrollbarMarkerState,
1003 active_indent_guides_state: ActiveIndentGuidesState,
1004 nav_history: Option<ItemNavHistory>,
1005 context_menu: RefCell<Option<CodeContextMenu>>,
1006 context_menu_options: Option<ContextMenuOptions>,
1007 mouse_context_menu: Option<MouseContextMenu>,
1008 completion_tasks: Vec<(CompletionId, Task<()>)>,
1009 inline_blame_popover: Option<InlineBlamePopover>,
1010 signature_help_state: SignatureHelpState,
1011 auto_signature_help: Option<bool>,
1012 find_all_references_task_sources: Vec<Anchor>,
1013 next_completion_id: CompletionId,
1014 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1015 code_actions_task: Option<Task<Result<()>>>,
1016 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1017 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1018 document_highlights_task: Option<Task<()>>,
1019 linked_editing_range_task: Option<Task<Option<()>>>,
1020 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1021 pending_rename: Option<RenameState>,
1022 searchable: bool,
1023 cursor_shape: CursorShape,
1024 current_line_highlight: Option<CurrentLineHighlight>,
1025 collapse_matches: bool,
1026 autoindent_mode: Option<AutoindentMode>,
1027 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1028 input_enabled: bool,
1029 use_modal_editing: bool,
1030 read_only: bool,
1031 leader_id: Option<CollaboratorId>,
1032 remote_id: Option<ViewId>,
1033 pub hover_state: HoverState,
1034 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1035 gutter_hovered: bool,
1036 hovered_link_state: Option<HoveredLinkState>,
1037 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1038 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1039 active_inline_completion: Option<InlineCompletionState>,
1040 /// Used to prevent flickering as the user types while the menu is open
1041 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1042 edit_prediction_settings: EditPredictionSettings,
1043 inline_completions_hidden_for_vim_mode: bool,
1044 show_inline_completions_override: Option<bool>,
1045 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1046 edit_prediction_preview: EditPredictionPreview,
1047 edit_prediction_indent_conflict: bool,
1048 edit_prediction_requires_modifier_in_indent_conflict: bool,
1049 inlay_hint_cache: InlayHintCache,
1050 next_inlay_id: usize,
1051 _subscriptions: Vec<Subscription>,
1052 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1053 gutter_dimensions: GutterDimensions,
1054 style: Option<EditorStyle>,
1055 text_style_refinement: Option<TextStyleRefinement>,
1056 next_editor_action_id: EditorActionId,
1057 editor_actions:
1058 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
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 = command_palette_hooks::CommandPaletteFilter::try_global(cx)
7545 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
7546
7547 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7548 BreakpointState::Enabled => Some("Disable"),
7549 BreakpointState::Disabled => Some("Enable"),
7550 });
7551
7552 let (anchor, breakpoint) =
7553 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7554
7555 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7556 menu.on_blur_subscription(Subscription::new(|| {}))
7557 .context(focus_handle)
7558 .when(run_to_cursor, |this| {
7559 let weak_editor = weak_editor.clone();
7560 this.entry("Run to cursor", None, move |window, cx| {
7561 weak_editor
7562 .update(cx, |editor, cx| {
7563 editor.change_selections(None, window, cx, |s| {
7564 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7565 });
7566 })
7567 .ok();
7568
7569 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
7570 })
7571 .separator()
7572 })
7573 .when_some(toggle_state_msg, |this, msg| {
7574 this.entry(msg, None, {
7575 let weak_editor = weak_editor.clone();
7576 let breakpoint = breakpoint.clone();
7577 move |_window, cx| {
7578 weak_editor
7579 .update(cx, |this, cx| {
7580 this.edit_breakpoint_at_anchor(
7581 anchor,
7582 breakpoint.as_ref().clone(),
7583 BreakpointEditAction::InvertState,
7584 cx,
7585 );
7586 })
7587 .log_err();
7588 }
7589 })
7590 })
7591 .entry(set_breakpoint_msg, None, {
7592 let weak_editor = weak_editor.clone();
7593 let breakpoint = breakpoint.clone();
7594 move |_window, cx| {
7595 weak_editor
7596 .update(cx, |this, cx| {
7597 this.edit_breakpoint_at_anchor(
7598 anchor,
7599 breakpoint.as_ref().clone(),
7600 BreakpointEditAction::Toggle,
7601 cx,
7602 );
7603 })
7604 .log_err();
7605 }
7606 })
7607 .entry(log_breakpoint_msg, None, {
7608 let breakpoint = breakpoint.clone();
7609 let weak_editor = weak_editor.clone();
7610 move |window, cx| {
7611 weak_editor
7612 .update(cx, |this, cx| {
7613 this.add_edit_breakpoint_block(
7614 anchor,
7615 breakpoint.as_ref(),
7616 BreakpointPromptEditAction::Log,
7617 window,
7618 cx,
7619 );
7620 })
7621 .log_err();
7622 }
7623 })
7624 .entry(condition_breakpoint_msg, None, {
7625 let breakpoint = breakpoint.clone();
7626 let weak_editor = weak_editor.clone();
7627 move |window, cx| {
7628 weak_editor
7629 .update(cx, |this, cx| {
7630 this.add_edit_breakpoint_block(
7631 anchor,
7632 breakpoint.as_ref(),
7633 BreakpointPromptEditAction::Condition,
7634 window,
7635 cx,
7636 );
7637 })
7638 .log_err();
7639 }
7640 })
7641 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7642 weak_editor
7643 .update(cx, |this, cx| {
7644 this.add_edit_breakpoint_block(
7645 anchor,
7646 breakpoint.as_ref(),
7647 BreakpointPromptEditAction::HitCondition,
7648 window,
7649 cx,
7650 );
7651 })
7652 .log_err();
7653 })
7654 })
7655 }
7656
7657 fn render_breakpoint(
7658 &self,
7659 position: Anchor,
7660 row: DisplayRow,
7661 breakpoint: &Breakpoint,
7662 state: Option<BreakpointSessionState>,
7663 cx: &mut Context<Self>,
7664 ) -> IconButton {
7665 let is_rejected = state.is_some_and(|s| !s.verified);
7666 // Is it a breakpoint that shows up when hovering over gutter?
7667 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7668 (false, false),
7669 |PhantomBreakpointIndicator {
7670 is_active,
7671 display_row,
7672 collides_with_existing_breakpoint,
7673 }| {
7674 (
7675 is_active && display_row == row,
7676 collides_with_existing_breakpoint,
7677 )
7678 },
7679 );
7680
7681 let (color, icon) = {
7682 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7683 (false, false) => ui::IconName::DebugBreakpoint,
7684 (true, false) => ui::IconName::DebugLogBreakpoint,
7685 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7686 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7687 };
7688
7689 let color = if is_phantom {
7690 Color::Hint
7691 } else if is_rejected {
7692 Color::Disabled
7693 } else {
7694 Color::Debugger
7695 };
7696
7697 (color, icon)
7698 };
7699
7700 let breakpoint = Arc::from(breakpoint.clone());
7701
7702 let alt_as_text = gpui::Keystroke {
7703 modifiers: Modifiers::secondary_key(),
7704 ..Default::default()
7705 };
7706 let primary_action_text = if breakpoint.is_disabled() {
7707 "Enable breakpoint"
7708 } else if is_phantom && !collides_with_existing {
7709 "Set breakpoint"
7710 } else {
7711 "Unset breakpoint"
7712 };
7713 let focus_handle = self.focus_handle.clone();
7714
7715 let meta = if is_rejected {
7716 SharedString::from("No executable code is associated with this line.")
7717 } else if collides_with_existing && !breakpoint.is_disabled() {
7718 SharedString::from(format!(
7719 "{alt_as_text}-click to disable,\nright-click for more options."
7720 ))
7721 } else {
7722 SharedString::from("Right-click for more options.")
7723 };
7724 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7725 .icon_size(IconSize::XSmall)
7726 .size(ui::ButtonSize::None)
7727 .when(is_rejected, |this| {
7728 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7729 })
7730 .icon_color(color)
7731 .style(ButtonStyle::Transparent)
7732 .on_click(cx.listener({
7733 let breakpoint = breakpoint.clone();
7734
7735 move |editor, event: &ClickEvent, window, cx| {
7736 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7737 BreakpointEditAction::InvertState
7738 } else {
7739 BreakpointEditAction::Toggle
7740 };
7741
7742 window.focus(&editor.focus_handle(cx));
7743 editor.edit_breakpoint_at_anchor(
7744 position,
7745 breakpoint.as_ref().clone(),
7746 edit_action,
7747 cx,
7748 );
7749 }
7750 }))
7751 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7752 editor.set_breakpoint_context_menu(
7753 row,
7754 Some(position),
7755 event.down.position,
7756 window,
7757 cx,
7758 );
7759 }))
7760 .tooltip(move |window, cx| {
7761 Tooltip::with_meta_in(
7762 primary_action_text,
7763 Some(&ToggleBreakpoint),
7764 meta.clone(),
7765 &focus_handle,
7766 window,
7767 cx,
7768 )
7769 })
7770 }
7771
7772 fn build_tasks_context(
7773 project: &Entity<Project>,
7774 buffer: &Entity<Buffer>,
7775 buffer_row: u32,
7776 tasks: &Arc<RunnableTasks>,
7777 cx: &mut Context<Self>,
7778 ) -> Task<Option<task::TaskContext>> {
7779 let position = Point::new(buffer_row, tasks.column);
7780 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7781 let location = Location {
7782 buffer: buffer.clone(),
7783 range: range_start..range_start,
7784 };
7785 // Fill in the environmental variables from the tree-sitter captures
7786 let mut captured_task_variables = TaskVariables::default();
7787 for (capture_name, value) in tasks.extra_variables.clone() {
7788 captured_task_variables.insert(
7789 task::VariableName::Custom(capture_name.into()),
7790 value.clone(),
7791 );
7792 }
7793 project.update(cx, |project, cx| {
7794 project.task_store().update(cx, |task_store, cx| {
7795 task_store.task_context_for_location(captured_task_variables, location, cx)
7796 })
7797 })
7798 }
7799
7800 pub fn spawn_nearest_task(
7801 &mut self,
7802 action: &SpawnNearestTask,
7803 window: &mut Window,
7804 cx: &mut Context<Self>,
7805 ) {
7806 let Some((workspace, _)) = self.workspace.clone() else {
7807 return;
7808 };
7809 let Some(project) = self.project.clone() else {
7810 return;
7811 };
7812
7813 // Try to find a closest, enclosing node using tree-sitter that has a
7814 // task
7815 let Some((buffer, buffer_row, tasks)) = self
7816 .find_enclosing_node_task(cx)
7817 // Or find the task that's closest in row-distance.
7818 .or_else(|| self.find_closest_task(cx))
7819 else {
7820 return;
7821 };
7822
7823 let reveal_strategy = action.reveal;
7824 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7825 cx.spawn_in(window, async move |_, cx| {
7826 let context = task_context.await?;
7827 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7828
7829 let resolved = &mut resolved_task.resolved;
7830 resolved.reveal = reveal_strategy;
7831
7832 workspace
7833 .update_in(cx, |workspace, window, cx| {
7834 workspace.schedule_resolved_task(
7835 task_source_kind,
7836 resolved_task,
7837 false,
7838 window,
7839 cx,
7840 );
7841 })
7842 .ok()
7843 })
7844 .detach();
7845 }
7846
7847 fn find_closest_task(
7848 &mut self,
7849 cx: &mut Context<Self>,
7850 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7851 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7852
7853 let ((buffer_id, row), tasks) = self
7854 .tasks
7855 .iter()
7856 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7857
7858 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7859 let tasks = Arc::new(tasks.to_owned());
7860 Some((buffer, *row, tasks))
7861 }
7862
7863 fn find_enclosing_node_task(
7864 &mut self,
7865 cx: &mut Context<Self>,
7866 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7867 let snapshot = self.buffer.read(cx).snapshot(cx);
7868 let offset = self.selections.newest::<usize>(cx).head();
7869 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7870 let buffer_id = excerpt.buffer().remote_id();
7871
7872 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7873 let mut cursor = layer.node().walk();
7874
7875 while cursor.goto_first_child_for_byte(offset).is_some() {
7876 if cursor.node().end_byte() == offset {
7877 cursor.goto_next_sibling();
7878 }
7879 }
7880
7881 // Ascend to the smallest ancestor that contains the range and has a task.
7882 loop {
7883 let node = cursor.node();
7884 let node_range = node.byte_range();
7885 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7886
7887 // Check if this node contains our offset
7888 if node_range.start <= offset && node_range.end >= offset {
7889 // If it contains offset, check for task
7890 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7891 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7892 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7893 }
7894 }
7895
7896 if !cursor.goto_parent() {
7897 break;
7898 }
7899 }
7900 None
7901 }
7902
7903 fn render_run_indicator(
7904 &self,
7905 _style: &EditorStyle,
7906 is_active: bool,
7907 row: DisplayRow,
7908 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7909 cx: &mut Context<Self>,
7910 ) -> IconButton {
7911 let color = Color::Muted;
7912 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7913
7914 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7915 .shape(ui::IconButtonShape::Square)
7916 .icon_size(IconSize::XSmall)
7917 .icon_color(color)
7918 .toggle_state(is_active)
7919 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7920 let quick_launch = e.down.button == MouseButton::Left;
7921 window.focus(&editor.focus_handle(cx));
7922 editor.toggle_code_actions(
7923 &ToggleCodeActions {
7924 deployed_from: Some(CodeActionSource::Indicator(row)),
7925 quick_launch,
7926 },
7927 window,
7928 cx,
7929 );
7930 }))
7931 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7932 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7933 }))
7934 }
7935
7936 pub fn context_menu_visible(&self) -> bool {
7937 !self.edit_prediction_preview_is_active()
7938 && self
7939 .context_menu
7940 .borrow()
7941 .as_ref()
7942 .map_or(false, |menu| menu.visible())
7943 }
7944
7945 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7946 self.context_menu
7947 .borrow()
7948 .as_ref()
7949 .map(|menu| menu.origin())
7950 }
7951
7952 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7953 self.context_menu_options = Some(options);
7954 }
7955
7956 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7957 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7958
7959 fn render_edit_prediction_popover(
7960 &mut self,
7961 text_bounds: &Bounds<Pixels>,
7962 content_origin: gpui::Point<Pixels>,
7963 right_margin: Pixels,
7964 editor_snapshot: &EditorSnapshot,
7965 visible_row_range: Range<DisplayRow>,
7966 scroll_top: f32,
7967 scroll_bottom: f32,
7968 line_layouts: &[LineWithInvisibles],
7969 line_height: Pixels,
7970 scroll_pixel_position: gpui::Point<Pixels>,
7971 newest_selection_head: Option<DisplayPoint>,
7972 editor_width: Pixels,
7973 style: &EditorStyle,
7974 window: &mut Window,
7975 cx: &mut App,
7976 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7977 if self.mode().is_minimap() {
7978 return None;
7979 }
7980 let active_inline_completion = self.active_inline_completion.as_ref()?;
7981
7982 if self.edit_prediction_visible_in_cursor_popover(true) {
7983 return None;
7984 }
7985
7986 match &active_inline_completion.completion {
7987 InlineCompletion::Move { target, .. } => {
7988 let target_display_point = target.to_display_point(editor_snapshot);
7989
7990 if self.edit_prediction_requires_modifier() {
7991 if !self.edit_prediction_preview_is_active() {
7992 return None;
7993 }
7994
7995 self.render_edit_prediction_modifier_jump_popover(
7996 text_bounds,
7997 content_origin,
7998 visible_row_range,
7999 line_layouts,
8000 line_height,
8001 scroll_pixel_position,
8002 newest_selection_head,
8003 target_display_point,
8004 window,
8005 cx,
8006 )
8007 } else {
8008 self.render_edit_prediction_eager_jump_popover(
8009 text_bounds,
8010 content_origin,
8011 editor_snapshot,
8012 visible_row_range,
8013 scroll_top,
8014 scroll_bottom,
8015 line_height,
8016 scroll_pixel_position,
8017 target_display_point,
8018 editor_width,
8019 window,
8020 cx,
8021 )
8022 }
8023 }
8024 InlineCompletion::Edit {
8025 display_mode: EditDisplayMode::Inline,
8026 ..
8027 } => None,
8028 InlineCompletion::Edit {
8029 display_mode: EditDisplayMode::TabAccept,
8030 edits,
8031 ..
8032 } => {
8033 let range = &edits.first()?.0;
8034 let target_display_point = range.end.to_display_point(editor_snapshot);
8035
8036 self.render_edit_prediction_end_of_line_popover(
8037 "Accept",
8038 editor_snapshot,
8039 visible_row_range,
8040 target_display_point,
8041 line_height,
8042 scroll_pixel_position,
8043 content_origin,
8044 editor_width,
8045 window,
8046 cx,
8047 )
8048 }
8049 InlineCompletion::Edit {
8050 edits,
8051 edit_preview,
8052 display_mode: EditDisplayMode::DiffPopover,
8053 snapshot,
8054 } => self.render_edit_prediction_diff_popover(
8055 text_bounds,
8056 content_origin,
8057 right_margin,
8058 editor_snapshot,
8059 visible_row_range,
8060 line_layouts,
8061 line_height,
8062 scroll_pixel_position,
8063 newest_selection_head,
8064 editor_width,
8065 style,
8066 edits,
8067 edit_preview,
8068 snapshot,
8069 window,
8070 cx,
8071 ),
8072 }
8073 }
8074
8075 fn render_edit_prediction_modifier_jump_popover(
8076 &mut self,
8077 text_bounds: &Bounds<Pixels>,
8078 content_origin: gpui::Point<Pixels>,
8079 visible_row_range: Range<DisplayRow>,
8080 line_layouts: &[LineWithInvisibles],
8081 line_height: Pixels,
8082 scroll_pixel_position: gpui::Point<Pixels>,
8083 newest_selection_head: Option<DisplayPoint>,
8084 target_display_point: DisplayPoint,
8085 window: &mut Window,
8086 cx: &mut App,
8087 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8088 let scrolled_content_origin =
8089 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8090
8091 const SCROLL_PADDING_Y: Pixels = px(12.);
8092
8093 if target_display_point.row() < visible_row_range.start {
8094 return self.render_edit_prediction_scroll_popover(
8095 |_| SCROLL_PADDING_Y,
8096 IconName::ArrowUp,
8097 visible_row_range,
8098 line_layouts,
8099 newest_selection_head,
8100 scrolled_content_origin,
8101 window,
8102 cx,
8103 );
8104 } else if target_display_point.row() >= visible_row_range.end {
8105 return self.render_edit_prediction_scroll_popover(
8106 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8107 IconName::ArrowDown,
8108 visible_row_range,
8109 line_layouts,
8110 newest_selection_head,
8111 scrolled_content_origin,
8112 window,
8113 cx,
8114 );
8115 }
8116
8117 const POLE_WIDTH: Pixels = px(2.);
8118
8119 let line_layout =
8120 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8121 let target_column = target_display_point.column() as usize;
8122
8123 let target_x = line_layout.x_for_index(target_column);
8124 let target_y =
8125 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8126
8127 let flag_on_right = target_x < text_bounds.size.width / 2.;
8128
8129 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8130 border_color.l += 0.001;
8131
8132 let mut element = v_flex()
8133 .items_end()
8134 .when(flag_on_right, |el| el.items_start())
8135 .child(if flag_on_right {
8136 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8137 .rounded_bl(px(0.))
8138 .rounded_tl(px(0.))
8139 .border_l_2()
8140 .border_color(border_color)
8141 } else {
8142 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8143 .rounded_br(px(0.))
8144 .rounded_tr(px(0.))
8145 .border_r_2()
8146 .border_color(border_color)
8147 })
8148 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8149 .into_any();
8150
8151 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8152
8153 let mut origin = scrolled_content_origin + point(target_x, target_y)
8154 - point(
8155 if flag_on_right {
8156 POLE_WIDTH
8157 } else {
8158 size.width - POLE_WIDTH
8159 },
8160 size.height - line_height,
8161 );
8162
8163 origin.x = origin.x.max(content_origin.x);
8164
8165 element.prepaint_at(origin, window, cx);
8166
8167 Some((element, origin))
8168 }
8169
8170 fn render_edit_prediction_scroll_popover(
8171 &mut self,
8172 to_y: impl Fn(Size<Pixels>) -> Pixels,
8173 scroll_icon: IconName,
8174 visible_row_range: Range<DisplayRow>,
8175 line_layouts: &[LineWithInvisibles],
8176 newest_selection_head: Option<DisplayPoint>,
8177 scrolled_content_origin: gpui::Point<Pixels>,
8178 window: &mut Window,
8179 cx: &mut App,
8180 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8181 let mut element = self
8182 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8183 .into_any();
8184
8185 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8186
8187 let cursor = newest_selection_head?;
8188 let cursor_row_layout =
8189 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8190 let cursor_column = cursor.column() as usize;
8191
8192 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8193
8194 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8195
8196 element.prepaint_at(origin, window, cx);
8197 Some((element, origin))
8198 }
8199
8200 fn render_edit_prediction_eager_jump_popover(
8201 &mut self,
8202 text_bounds: &Bounds<Pixels>,
8203 content_origin: gpui::Point<Pixels>,
8204 editor_snapshot: &EditorSnapshot,
8205 visible_row_range: Range<DisplayRow>,
8206 scroll_top: f32,
8207 scroll_bottom: f32,
8208 line_height: Pixels,
8209 scroll_pixel_position: gpui::Point<Pixels>,
8210 target_display_point: DisplayPoint,
8211 editor_width: Pixels,
8212 window: &mut Window,
8213 cx: &mut App,
8214 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8215 if target_display_point.row().as_f32() < scroll_top {
8216 let mut element = self
8217 .render_edit_prediction_line_popover(
8218 "Jump to Edit",
8219 Some(IconName::ArrowUp),
8220 window,
8221 cx,
8222 )?
8223 .into_any();
8224
8225 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8226 let offset = point(
8227 (text_bounds.size.width - size.width) / 2.,
8228 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8229 );
8230
8231 let origin = text_bounds.origin + offset;
8232 element.prepaint_at(origin, window, cx);
8233 Some((element, origin))
8234 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8235 let mut element = self
8236 .render_edit_prediction_line_popover(
8237 "Jump to Edit",
8238 Some(IconName::ArrowDown),
8239 window,
8240 cx,
8241 )?
8242 .into_any();
8243
8244 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8245 let offset = point(
8246 (text_bounds.size.width - size.width) / 2.,
8247 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8248 );
8249
8250 let origin = text_bounds.origin + offset;
8251 element.prepaint_at(origin, window, cx);
8252 Some((element, origin))
8253 } else {
8254 self.render_edit_prediction_end_of_line_popover(
8255 "Jump to Edit",
8256 editor_snapshot,
8257 visible_row_range,
8258 target_display_point,
8259 line_height,
8260 scroll_pixel_position,
8261 content_origin,
8262 editor_width,
8263 window,
8264 cx,
8265 )
8266 }
8267 }
8268
8269 fn render_edit_prediction_end_of_line_popover(
8270 self: &mut Editor,
8271 label: &'static str,
8272 editor_snapshot: &EditorSnapshot,
8273 visible_row_range: Range<DisplayRow>,
8274 target_display_point: DisplayPoint,
8275 line_height: Pixels,
8276 scroll_pixel_position: gpui::Point<Pixels>,
8277 content_origin: gpui::Point<Pixels>,
8278 editor_width: Pixels,
8279 window: &mut Window,
8280 cx: &mut App,
8281 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8282 let target_line_end = DisplayPoint::new(
8283 target_display_point.row(),
8284 editor_snapshot.line_len(target_display_point.row()),
8285 );
8286
8287 let mut element = self
8288 .render_edit_prediction_line_popover(label, None, window, cx)?
8289 .into_any();
8290
8291 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8292
8293 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8294
8295 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8296 let mut origin = start_point
8297 + line_origin
8298 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8299 origin.x = origin.x.max(content_origin.x);
8300
8301 let max_x = content_origin.x + editor_width - size.width;
8302
8303 if origin.x > max_x {
8304 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8305
8306 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8307 origin.y += offset;
8308 IconName::ArrowUp
8309 } else {
8310 origin.y -= offset;
8311 IconName::ArrowDown
8312 };
8313
8314 element = self
8315 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8316 .into_any();
8317
8318 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8319
8320 origin.x = content_origin.x + editor_width - size.width - px(2.);
8321 }
8322
8323 element.prepaint_at(origin, window, cx);
8324 Some((element, origin))
8325 }
8326
8327 fn render_edit_prediction_diff_popover(
8328 self: &Editor,
8329 text_bounds: &Bounds<Pixels>,
8330 content_origin: gpui::Point<Pixels>,
8331 right_margin: Pixels,
8332 editor_snapshot: &EditorSnapshot,
8333 visible_row_range: Range<DisplayRow>,
8334 line_layouts: &[LineWithInvisibles],
8335 line_height: Pixels,
8336 scroll_pixel_position: gpui::Point<Pixels>,
8337 newest_selection_head: Option<DisplayPoint>,
8338 editor_width: Pixels,
8339 style: &EditorStyle,
8340 edits: &Vec<(Range<Anchor>, String)>,
8341 edit_preview: &Option<language::EditPreview>,
8342 snapshot: &language::BufferSnapshot,
8343 window: &mut Window,
8344 cx: &mut App,
8345 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8346 let edit_start = edits
8347 .first()
8348 .unwrap()
8349 .0
8350 .start
8351 .to_display_point(editor_snapshot);
8352 let edit_end = edits
8353 .last()
8354 .unwrap()
8355 .0
8356 .end
8357 .to_display_point(editor_snapshot);
8358
8359 let is_visible = visible_row_range.contains(&edit_start.row())
8360 || visible_row_range.contains(&edit_end.row());
8361 if !is_visible {
8362 return None;
8363 }
8364
8365 let highlighted_edits =
8366 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8367
8368 let styled_text = highlighted_edits.to_styled_text(&style.text);
8369 let line_count = highlighted_edits.text.lines().count();
8370
8371 const BORDER_WIDTH: Pixels = px(1.);
8372
8373 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8374 let has_keybind = keybind.is_some();
8375
8376 let mut element = h_flex()
8377 .items_start()
8378 .child(
8379 h_flex()
8380 .bg(cx.theme().colors().editor_background)
8381 .border(BORDER_WIDTH)
8382 .shadow_sm()
8383 .border_color(cx.theme().colors().border)
8384 .rounded_l_lg()
8385 .when(line_count > 1, |el| el.rounded_br_lg())
8386 .pr_1()
8387 .child(styled_text),
8388 )
8389 .child(
8390 h_flex()
8391 .h(line_height + BORDER_WIDTH * 2.)
8392 .px_1p5()
8393 .gap_1()
8394 // Workaround: For some reason, there's a gap if we don't do this
8395 .ml(-BORDER_WIDTH)
8396 .shadow(vec![gpui::BoxShadow {
8397 color: gpui::black().opacity(0.05),
8398 offset: point(px(1.), px(1.)),
8399 blur_radius: px(2.),
8400 spread_radius: px(0.),
8401 }])
8402 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8403 .border(BORDER_WIDTH)
8404 .border_color(cx.theme().colors().border)
8405 .rounded_r_lg()
8406 .id("edit_prediction_diff_popover_keybind")
8407 .when(!has_keybind, |el| {
8408 let status_colors = cx.theme().status();
8409
8410 el.bg(status_colors.error_background)
8411 .border_color(status_colors.error.opacity(0.6))
8412 .child(Icon::new(IconName::Info).color(Color::Error))
8413 .cursor_default()
8414 .hoverable_tooltip(move |_window, cx| {
8415 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8416 })
8417 })
8418 .children(keybind),
8419 )
8420 .into_any();
8421
8422 let longest_row =
8423 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8424 let longest_line_width = if visible_row_range.contains(&longest_row) {
8425 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8426 } else {
8427 layout_line(
8428 longest_row,
8429 editor_snapshot,
8430 style,
8431 editor_width,
8432 |_| false,
8433 window,
8434 cx,
8435 )
8436 .width
8437 };
8438
8439 let viewport_bounds =
8440 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8441 right: -right_margin,
8442 ..Default::default()
8443 });
8444
8445 let x_after_longest =
8446 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8447 - scroll_pixel_position.x;
8448
8449 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8450
8451 // Fully visible if it can be displayed within the window (allow overlapping other
8452 // panes). However, this is only allowed if the popover starts within text_bounds.
8453 let can_position_to_the_right = x_after_longest < text_bounds.right()
8454 && x_after_longest + element_bounds.width < viewport_bounds.right();
8455
8456 let mut origin = if can_position_to_the_right {
8457 point(
8458 x_after_longest,
8459 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8460 - scroll_pixel_position.y,
8461 )
8462 } else {
8463 let cursor_row = newest_selection_head.map(|head| head.row());
8464 let above_edit = edit_start
8465 .row()
8466 .0
8467 .checked_sub(line_count as u32)
8468 .map(DisplayRow);
8469 let below_edit = Some(edit_end.row() + 1);
8470 let above_cursor =
8471 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8472 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8473
8474 // Place the edit popover adjacent to the edit if there is a location
8475 // available that is onscreen and does not obscure the cursor. Otherwise,
8476 // place it adjacent to the cursor.
8477 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8478 .into_iter()
8479 .flatten()
8480 .find(|&start_row| {
8481 let end_row = start_row + line_count as u32;
8482 visible_row_range.contains(&start_row)
8483 && visible_row_range.contains(&end_row)
8484 && cursor_row.map_or(true, |cursor_row| {
8485 !((start_row..end_row).contains(&cursor_row))
8486 })
8487 })?;
8488
8489 content_origin
8490 + point(
8491 -scroll_pixel_position.x,
8492 row_target.as_f32() * line_height - scroll_pixel_position.y,
8493 )
8494 };
8495
8496 origin.x -= BORDER_WIDTH;
8497
8498 window.defer_draw(element, origin, 1);
8499
8500 // Do not return an element, since it will already be drawn due to defer_draw.
8501 None
8502 }
8503
8504 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8505 px(30.)
8506 }
8507
8508 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8509 if self.read_only(cx) {
8510 cx.theme().players().read_only()
8511 } else {
8512 self.style.as_ref().unwrap().local_player
8513 }
8514 }
8515
8516 fn render_edit_prediction_accept_keybind(
8517 &self,
8518 window: &mut Window,
8519 cx: &App,
8520 ) -> Option<AnyElement> {
8521 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8522 let accept_keystroke = accept_binding.keystroke()?;
8523
8524 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8525
8526 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8527 Color::Accent
8528 } else {
8529 Color::Muted
8530 };
8531
8532 h_flex()
8533 .px_0p5()
8534 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8535 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8536 .text_size(TextSize::XSmall.rems(cx))
8537 .child(h_flex().children(ui::render_modifiers(
8538 &accept_keystroke.modifiers,
8539 PlatformStyle::platform(),
8540 Some(modifiers_color),
8541 Some(IconSize::XSmall.rems().into()),
8542 true,
8543 )))
8544 .when(is_platform_style_mac, |parent| {
8545 parent.child(accept_keystroke.key.clone())
8546 })
8547 .when(!is_platform_style_mac, |parent| {
8548 parent.child(
8549 Key::new(
8550 util::capitalize(&accept_keystroke.key),
8551 Some(Color::Default),
8552 )
8553 .size(Some(IconSize::XSmall.rems().into())),
8554 )
8555 })
8556 .into_any()
8557 .into()
8558 }
8559
8560 fn render_edit_prediction_line_popover(
8561 &self,
8562 label: impl Into<SharedString>,
8563 icon: Option<IconName>,
8564 window: &mut Window,
8565 cx: &App,
8566 ) -> Option<Stateful<Div>> {
8567 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8568
8569 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8570 let has_keybind = keybind.is_some();
8571
8572 let result = h_flex()
8573 .id("ep-line-popover")
8574 .py_0p5()
8575 .pl_1()
8576 .pr(padding_right)
8577 .gap_1()
8578 .rounded_md()
8579 .border_1()
8580 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8581 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8582 .shadow_sm()
8583 .when(!has_keybind, |el| {
8584 let status_colors = cx.theme().status();
8585
8586 el.bg(status_colors.error_background)
8587 .border_color(status_colors.error.opacity(0.6))
8588 .pl_2()
8589 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8590 .cursor_default()
8591 .hoverable_tooltip(move |_window, cx| {
8592 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8593 })
8594 })
8595 .children(keybind)
8596 .child(
8597 Label::new(label)
8598 .size(LabelSize::Small)
8599 .when(!has_keybind, |el| {
8600 el.color(cx.theme().status().error.into()).strikethrough()
8601 }),
8602 )
8603 .when(!has_keybind, |el| {
8604 el.child(
8605 h_flex().ml_1().child(
8606 Icon::new(IconName::Info)
8607 .size(IconSize::Small)
8608 .color(cx.theme().status().error.into()),
8609 ),
8610 )
8611 })
8612 .when_some(icon, |element, icon| {
8613 element.child(
8614 div()
8615 .mt(px(1.5))
8616 .child(Icon::new(icon).size(IconSize::Small)),
8617 )
8618 });
8619
8620 Some(result)
8621 }
8622
8623 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8624 let accent_color = cx.theme().colors().text_accent;
8625 let editor_bg_color = cx.theme().colors().editor_background;
8626 editor_bg_color.blend(accent_color.opacity(0.1))
8627 }
8628
8629 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8630 let accent_color = cx.theme().colors().text_accent;
8631 let editor_bg_color = cx.theme().colors().editor_background;
8632 editor_bg_color.blend(accent_color.opacity(0.6))
8633 }
8634
8635 fn render_edit_prediction_cursor_popover(
8636 &self,
8637 min_width: Pixels,
8638 max_width: Pixels,
8639 cursor_point: Point,
8640 style: &EditorStyle,
8641 accept_keystroke: Option<&gpui::Keystroke>,
8642 _window: &Window,
8643 cx: &mut Context<Editor>,
8644 ) -> Option<AnyElement> {
8645 let provider = self.edit_prediction_provider.as_ref()?;
8646
8647 if provider.provider.needs_terms_acceptance(cx) {
8648 return Some(
8649 h_flex()
8650 .min_w(min_width)
8651 .flex_1()
8652 .px_2()
8653 .py_1()
8654 .gap_3()
8655 .elevation_2(cx)
8656 .hover(|style| style.bg(cx.theme().colors().element_hover))
8657 .id("accept-terms")
8658 .cursor_pointer()
8659 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8660 .on_click(cx.listener(|this, _event, window, cx| {
8661 cx.stop_propagation();
8662 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8663 window.dispatch_action(
8664 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8665 cx,
8666 );
8667 }))
8668 .child(
8669 h_flex()
8670 .flex_1()
8671 .gap_2()
8672 .child(Icon::new(IconName::ZedPredict))
8673 .child(Label::new("Accept Terms of Service"))
8674 .child(div().w_full())
8675 .child(
8676 Icon::new(IconName::ArrowUpRight)
8677 .color(Color::Muted)
8678 .size(IconSize::Small),
8679 )
8680 .into_any_element(),
8681 )
8682 .into_any(),
8683 );
8684 }
8685
8686 let is_refreshing = provider.provider.is_refreshing(cx);
8687
8688 fn pending_completion_container() -> Div {
8689 h_flex()
8690 .h_full()
8691 .flex_1()
8692 .gap_2()
8693 .child(Icon::new(IconName::ZedPredict))
8694 }
8695
8696 let completion = match &self.active_inline_completion {
8697 Some(prediction) => {
8698 if !self.has_visible_completions_menu() {
8699 const RADIUS: Pixels = px(6.);
8700 const BORDER_WIDTH: Pixels = px(1.);
8701
8702 return Some(
8703 h_flex()
8704 .elevation_2(cx)
8705 .border(BORDER_WIDTH)
8706 .border_color(cx.theme().colors().border)
8707 .when(accept_keystroke.is_none(), |el| {
8708 el.border_color(cx.theme().status().error)
8709 })
8710 .rounded(RADIUS)
8711 .rounded_tl(px(0.))
8712 .overflow_hidden()
8713 .child(div().px_1p5().child(match &prediction.completion {
8714 InlineCompletion::Move { target, snapshot } => {
8715 use text::ToPoint as _;
8716 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8717 {
8718 Icon::new(IconName::ZedPredictDown)
8719 } else {
8720 Icon::new(IconName::ZedPredictUp)
8721 }
8722 }
8723 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8724 }))
8725 .child(
8726 h_flex()
8727 .gap_1()
8728 .py_1()
8729 .px_2()
8730 .rounded_r(RADIUS - BORDER_WIDTH)
8731 .border_l_1()
8732 .border_color(cx.theme().colors().border)
8733 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8734 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8735 el.child(
8736 Label::new("Hold")
8737 .size(LabelSize::Small)
8738 .when(accept_keystroke.is_none(), |el| {
8739 el.strikethrough()
8740 })
8741 .line_height_style(LineHeightStyle::UiLabel),
8742 )
8743 })
8744 .id("edit_prediction_cursor_popover_keybind")
8745 .when(accept_keystroke.is_none(), |el| {
8746 let status_colors = cx.theme().status();
8747
8748 el.bg(status_colors.error_background)
8749 .border_color(status_colors.error.opacity(0.6))
8750 .child(Icon::new(IconName::Info).color(Color::Error))
8751 .cursor_default()
8752 .hoverable_tooltip(move |_window, cx| {
8753 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8754 .into()
8755 })
8756 })
8757 .when_some(
8758 accept_keystroke.as_ref(),
8759 |el, accept_keystroke| {
8760 el.child(h_flex().children(ui::render_modifiers(
8761 &accept_keystroke.modifiers,
8762 PlatformStyle::platform(),
8763 Some(Color::Default),
8764 Some(IconSize::XSmall.rems().into()),
8765 false,
8766 )))
8767 },
8768 ),
8769 )
8770 .into_any(),
8771 );
8772 }
8773
8774 self.render_edit_prediction_cursor_popover_preview(
8775 prediction,
8776 cursor_point,
8777 style,
8778 cx,
8779 )?
8780 }
8781
8782 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8783 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8784 stale_completion,
8785 cursor_point,
8786 style,
8787 cx,
8788 )?,
8789
8790 None => {
8791 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8792 }
8793 },
8794
8795 None => pending_completion_container().child(Label::new("No Prediction")),
8796 };
8797
8798 let completion = if is_refreshing {
8799 completion
8800 .with_animation(
8801 "loading-completion",
8802 Animation::new(Duration::from_secs(2))
8803 .repeat()
8804 .with_easing(pulsating_between(0.4, 0.8)),
8805 |label, delta| label.opacity(delta),
8806 )
8807 .into_any_element()
8808 } else {
8809 completion.into_any_element()
8810 };
8811
8812 let has_completion = self.active_inline_completion.is_some();
8813
8814 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8815 Some(
8816 h_flex()
8817 .min_w(min_width)
8818 .max_w(max_width)
8819 .flex_1()
8820 .elevation_2(cx)
8821 .border_color(cx.theme().colors().border)
8822 .child(
8823 div()
8824 .flex_1()
8825 .py_1()
8826 .px_2()
8827 .overflow_hidden()
8828 .child(completion),
8829 )
8830 .when_some(accept_keystroke, |el, accept_keystroke| {
8831 if !accept_keystroke.modifiers.modified() {
8832 return el;
8833 }
8834
8835 el.child(
8836 h_flex()
8837 .h_full()
8838 .border_l_1()
8839 .rounded_r_lg()
8840 .border_color(cx.theme().colors().border)
8841 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8842 .gap_1()
8843 .py_1()
8844 .px_2()
8845 .child(
8846 h_flex()
8847 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8848 .when(is_platform_style_mac, |parent| parent.gap_1())
8849 .child(h_flex().children(ui::render_modifiers(
8850 &accept_keystroke.modifiers,
8851 PlatformStyle::platform(),
8852 Some(if !has_completion {
8853 Color::Muted
8854 } else {
8855 Color::Default
8856 }),
8857 None,
8858 false,
8859 ))),
8860 )
8861 .child(Label::new("Preview").into_any_element())
8862 .opacity(if has_completion { 1.0 } else { 0.4 }),
8863 )
8864 })
8865 .into_any(),
8866 )
8867 }
8868
8869 fn render_edit_prediction_cursor_popover_preview(
8870 &self,
8871 completion: &InlineCompletionState,
8872 cursor_point: Point,
8873 style: &EditorStyle,
8874 cx: &mut Context<Editor>,
8875 ) -> Option<Div> {
8876 use text::ToPoint as _;
8877
8878 fn render_relative_row_jump(
8879 prefix: impl Into<String>,
8880 current_row: u32,
8881 target_row: u32,
8882 ) -> Div {
8883 let (row_diff, arrow) = if target_row < current_row {
8884 (current_row - target_row, IconName::ArrowUp)
8885 } else {
8886 (target_row - current_row, IconName::ArrowDown)
8887 };
8888
8889 h_flex()
8890 .child(
8891 Label::new(format!("{}{}", prefix.into(), row_diff))
8892 .color(Color::Muted)
8893 .size(LabelSize::Small),
8894 )
8895 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8896 }
8897
8898 match &completion.completion {
8899 InlineCompletion::Move {
8900 target, snapshot, ..
8901 } => Some(
8902 h_flex()
8903 .px_2()
8904 .gap_2()
8905 .flex_1()
8906 .child(
8907 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8908 Icon::new(IconName::ZedPredictDown)
8909 } else {
8910 Icon::new(IconName::ZedPredictUp)
8911 },
8912 )
8913 .child(Label::new("Jump to Edit")),
8914 ),
8915
8916 InlineCompletion::Edit {
8917 edits,
8918 edit_preview,
8919 snapshot,
8920 display_mode: _,
8921 } => {
8922 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8923
8924 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8925 &snapshot,
8926 &edits,
8927 edit_preview.as_ref()?,
8928 true,
8929 cx,
8930 )
8931 .first_line_preview();
8932
8933 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8934 .with_default_highlights(&style.text, highlighted_edits.highlights);
8935
8936 let preview = h_flex()
8937 .gap_1()
8938 .min_w_16()
8939 .child(styled_text)
8940 .when(has_more_lines, |parent| parent.child("…"));
8941
8942 let left = if first_edit_row != cursor_point.row {
8943 render_relative_row_jump("", cursor_point.row, first_edit_row)
8944 .into_any_element()
8945 } else {
8946 Icon::new(IconName::ZedPredict).into_any_element()
8947 };
8948
8949 Some(
8950 h_flex()
8951 .h_full()
8952 .flex_1()
8953 .gap_2()
8954 .pr_1()
8955 .overflow_x_hidden()
8956 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8957 .child(left)
8958 .child(preview),
8959 )
8960 }
8961 }
8962 }
8963
8964 pub fn render_context_menu(
8965 &self,
8966 style: &EditorStyle,
8967 max_height_in_lines: u32,
8968 window: &mut Window,
8969 cx: &mut Context<Editor>,
8970 ) -> Option<AnyElement> {
8971 let menu = self.context_menu.borrow();
8972 let menu = menu.as_ref()?;
8973 if !menu.visible() {
8974 return None;
8975 };
8976 Some(menu.render(style, max_height_in_lines, window, cx))
8977 }
8978
8979 fn render_context_menu_aside(
8980 &mut self,
8981 max_size: Size<Pixels>,
8982 window: &mut Window,
8983 cx: &mut Context<Editor>,
8984 ) -> Option<AnyElement> {
8985 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8986 if menu.visible() {
8987 menu.render_aside(max_size, window, cx)
8988 } else {
8989 None
8990 }
8991 })
8992 }
8993
8994 fn hide_context_menu(
8995 &mut self,
8996 window: &mut Window,
8997 cx: &mut Context<Self>,
8998 ) -> Option<CodeContextMenu> {
8999 cx.notify();
9000 self.completion_tasks.clear();
9001 let context_menu = self.context_menu.borrow_mut().take();
9002 self.stale_inline_completion_in_menu.take();
9003 self.update_visible_inline_completion(window, cx);
9004 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9005 if let Some(completion_provider) = &self.completion_provider {
9006 completion_provider.selection_changed(None, window, cx);
9007 }
9008 }
9009 context_menu
9010 }
9011
9012 fn show_snippet_choices(
9013 &mut self,
9014 choices: &Vec<String>,
9015 selection: Range<Anchor>,
9016 cx: &mut Context<Self>,
9017 ) {
9018 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9019 (Some(a), Some(b)) if a == b => a,
9020 _ => {
9021 log::error!("expected anchor range to have matching buffer IDs");
9022 return;
9023 }
9024 };
9025 let multi_buffer = self.buffer().read(cx);
9026 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9027 return;
9028 };
9029
9030 let id = post_inc(&mut self.next_completion_id);
9031 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9032 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9033 CompletionsMenu::new_snippet_choices(
9034 id,
9035 true,
9036 choices,
9037 selection,
9038 buffer,
9039 snippet_sort_order,
9040 ),
9041 ));
9042 }
9043
9044 pub fn insert_snippet(
9045 &mut self,
9046 insertion_ranges: &[Range<usize>],
9047 snippet: Snippet,
9048 window: &mut Window,
9049 cx: &mut Context<Self>,
9050 ) -> Result<()> {
9051 struct Tabstop<T> {
9052 is_end_tabstop: bool,
9053 ranges: Vec<Range<T>>,
9054 choices: Option<Vec<String>>,
9055 }
9056
9057 let tabstops = self.buffer.update(cx, |buffer, cx| {
9058 let snippet_text: Arc<str> = snippet.text.clone().into();
9059 let edits = insertion_ranges
9060 .iter()
9061 .cloned()
9062 .map(|range| (range, snippet_text.clone()));
9063 let autoindent_mode = AutoindentMode::Block {
9064 original_indent_columns: Vec::new(),
9065 };
9066 buffer.edit(edits, Some(autoindent_mode), cx);
9067
9068 let snapshot = &*buffer.read(cx);
9069 let snippet = &snippet;
9070 snippet
9071 .tabstops
9072 .iter()
9073 .map(|tabstop| {
9074 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9075 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9076 });
9077 let mut tabstop_ranges = tabstop
9078 .ranges
9079 .iter()
9080 .flat_map(|tabstop_range| {
9081 let mut delta = 0_isize;
9082 insertion_ranges.iter().map(move |insertion_range| {
9083 let insertion_start = insertion_range.start as isize + delta;
9084 delta +=
9085 snippet.text.len() as isize - insertion_range.len() as isize;
9086
9087 let start = ((insertion_start + tabstop_range.start) as usize)
9088 .min(snapshot.len());
9089 let end = ((insertion_start + tabstop_range.end) as usize)
9090 .min(snapshot.len());
9091 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9092 })
9093 })
9094 .collect::<Vec<_>>();
9095 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9096
9097 Tabstop {
9098 is_end_tabstop,
9099 ranges: tabstop_ranges,
9100 choices: tabstop.choices.clone(),
9101 }
9102 })
9103 .collect::<Vec<_>>()
9104 });
9105 if let Some(tabstop) = tabstops.first() {
9106 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9107 // Reverse order so that the first range is the newest created selection.
9108 // Completions will use it and autoscroll will prioritize it.
9109 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9110 });
9111
9112 if let Some(choices) = &tabstop.choices {
9113 if let Some(selection) = tabstop.ranges.first() {
9114 self.show_snippet_choices(choices, selection.clone(), cx)
9115 }
9116 }
9117
9118 // If we're already at the last tabstop and it's at the end of the snippet,
9119 // we're done, we don't need to keep the state around.
9120 if !tabstop.is_end_tabstop {
9121 let choices = tabstops
9122 .iter()
9123 .map(|tabstop| tabstop.choices.clone())
9124 .collect();
9125
9126 let ranges = tabstops
9127 .into_iter()
9128 .map(|tabstop| tabstop.ranges)
9129 .collect::<Vec<_>>();
9130
9131 self.snippet_stack.push(SnippetState {
9132 active_index: 0,
9133 ranges,
9134 choices,
9135 });
9136 }
9137
9138 // Check whether the just-entered snippet ends with an auto-closable bracket.
9139 if self.autoclose_regions.is_empty() {
9140 let snapshot = self.buffer.read(cx).snapshot(cx);
9141 for selection in &mut self.selections.all::<Point>(cx) {
9142 let selection_head = selection.head();
9143 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9144 continue;
9145 };
9146
9147 let mut bracket_pair = None;
9148 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9149 let prev_chars = snapshot
9150 .reversed_chars_at(selection_head)
9151 .collect::<String>();
9152 for (pair, enabled) in scope.brackets() {
9153 if enabled
9154 && pair.close
9155 && prev_chars.starts_with(pair.start.as_str())
9156 && next_chars.starts_with(pair.end.as_str())
9157 {
9158 bracket_pair = Some(pair.clone());
9159 break;
9160 }
9161 }
9162 if let Some(pair) = bracket_pair {
9163 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9164 let autoclose_enabled =
9165 self.use_autoclose && snapshot_settings.use_autoclose;
9166 if autoclose_enabled {
9167 let start = snapshot.anchor_after(selection_head);
9168 let end = snapshot.anchor_after(selection_head);
9169 self.autoclose_regions.push(AutocloseRegion {
9170 selection_id: selection.id,
9171 range: start..end,
9172 pair,
9173 });
9174 }
9175 }
9176 }
9177 }
9178 }
9179 Ok(())
9180 }
9181
9182 pub fn move_to_next_snippet_tabstop(
9183 &mut self,
9184 window: &mut Window,
9185 cx: &mut Context<Self>,
9186 ) -> bool {
9187 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9188 }
9189
9190 pub fn move_to_prev_snippet_tabstop(
9191 &mut self,
9192 window: &mut Window,
9193 cx: &mut Context<Self>,
9194 ) -> bool {
9195 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9196 }
9197
9198 pub fn move_to_snippet_tabstop(
9199 &mut self,
9200 bias: Bias,
9201 window: &mut Window,
9202 cx: &mut Context<Self>,
9203 ) -> bool {
9204 if let Some(mut snippet) = self.snippet_stack.pop() {
9205 match bias {
9206 Bias::Left => {
9207 if snippet.active_index > 0 {
9208 snippet.active_index -= 1;
9209 } else {
9210 self.snippet_stack.push(snippet);
9211 return false;
9212 }
9213 }
9214 Bias::Right => {
9215 if snippet.active_index + 1 < snippet.ranges.len() {
9216 snippet.active_index += 1;
9217 } else {
9218 self.snippet_stack.push(snippet);
9219 return false;
9220 }
9221 }
9222 }
9223 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9224 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9225 // Reverse order so that the first range is the newest created selection.
9226 // Completions will use it and autoscroll will prioritize it.
9227 s.select_ranges(current_ranges.iter().rev().cloned())
9228 });
9229
9230 if let Some(choices) = &snippet.choices[snippet.active_index] {
9231 if let Some(selection) = current_ranges.first() {
9232 self.show_snippet_choices(&choices, selection.clone(), cx);
9233 }
9234 }
9235
9236 // If snippet state is not at the last tabstop, push it back on the stack
9237 if snippet.active_index + 1 < snippet.ranges.len() {
9238 self.snippet_stack.push(snippet);
9239 }
9240 return true;
9241 }
9242 }
9243
9244 false
9245 }
9246
9247 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9248 self.transact(window, cx, |this, window, cx| {
9249 this.select_all(&SelectAll, window, cx);
9250 this.insert("", window, cx);
9251 });
9252 }
9253
9254 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9255 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9256 self.transact(window, cx, |this, window, cx| {
9257 this.select_autoclose_pair(window, cx);
9258 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9259 if !this.linked_edit_ranges.is_empty() {
9260 let selections = this.selections.all::<MultiBufferPoint>(cx);
9261 let snapshot = this.buffer.read(cx).snapshot(cx);
9262
9263 for selection in selections.iter() {
9264 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9265 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9266 if selection_start.buffer_id != selection_end.buffer_id {
9267 continue;
9268 }
9269 if let Some(ranges) =
9270 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9271 {
9272 for (buffer, entries) in ranges {
9273 linked_ranges.entry(buffer).or_default().extend(entries);
9274 }
9275 }
9276 }
9277 }
9278
9279 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9280 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9281 for selection in &mut selections {
9282 if selection.is_empty() {
9283 let old_head = selection.head();
9284 let mut new_head =
9285 movement::left(&display_map, old_head.to_display_point(&display_map))
9286 .to_point(&display_map);
9287 if let Some((buffer, line_buffer_range)) = display_map
9288 .buffer_snapshot
9289 .buffer_line_for_row(MultiBufferRow(old_head.row))
9290 {
9291 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9292 let indent_len = match indent_size.kind {
9293 IndentKind::Space => {
9294 buffer.settings_at(line_buffer_range.start, cx).tab_size
9295 }
9296 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9297 };
9298 if old_head.column <= indent_size.len && old_head.column > 0 {
9299 let indent_len = indent_len.get();
9300 new_head = cmp::min(
9301 new_head,
9302 MultiBufferPoint::new(
9303 old_head.row,
9304 ((old_head.column - 1) / indent_len) * indent_len,
9305 ),
9306 );
9307 }
9308 }
9309
9310 selection.set_head(new_head, SelectionGoal::None);
9311 }
9312 }
9313
9314 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9315 s.select(selections)
9316 });
9317 this.insert("", window, cx);
9318 let empty_str: Arc<str> = Arc::from("");
9319 for (buffer, edits) in linked_ranges {
9320 let snapshot = buffer.read(cx).snapshot();
9321 use text::ToPoint as TP;
9322
9323 let edits = edits
9324 .into_iter()
9325 .map(|range| {
9326 let end_point = TP::to_point(&range.end, &snapshot);
9327 let mut start_point = TP::to_point(&range.start, &snapshot);
9328
9329 if end_point == start_point {
9330 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9331 .saturating_sub(1);
9332 start_point =
9333 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9334 };
9335
9336 (start_point..end_point, empty_str.clone())
9337 })
9338 .sorted_by_key(|(range, _)| range.start)
9339 .collect::<Vec<_>>();
9340 buffer.update(cx, |this, cx| {
9341 this.edit(edits, None, cx);
9342 })
9343 }
9344 this.refresh_inline_completion(true, false, window, cx);
9345 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9346 });
9347 }
9348
9349 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9350 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9351 self.transact(window, cx, |this, window, cx| {
9352 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9353 s.move_with(|map, selection| {
9354 if selection.is_empty() {
9355 let cursor = movement::right(map, selection.head());
9356 selection.end = cursor;
9357 selection.reversed = true;
9358 selection.goal = SelectionGoal::None;
9359 }
9360 })
9361 });
9362 this.insert("", window, cx);
9363 this.refresh_inline_completion(true, false, window, cx);
9364 });
9365 }
9366
9367 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9368 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9369 if self.move_to_prev_snippet_tabstop(window, cx) {
9370 return;
9371 }
9372 self.outdent(&Outdent, window, cx);
9373 }
9374
9375 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9376 if self.move_to_next_snippet_tabstop(window, cx) {
9377 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9378 return;
9379 }
9380 if self.read_only(cx) {
9381 return;
9382 }
9383 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9384 let mut selections = self.selections.all_adjusted(cx);
9385 let buffer = self.buffer.read(cx);
9386 let snapshot = buffer.snapshot(cx);
9387 let rows_iter = selections.iter().map(|s| s.head().row);
9388 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9389
9390 let has_some_cursor_in_whitespace = selections
9391 .iter()
9392 .filter(|selection| selection.is_empty())
9393 .any(|selection| {
9394 let cursor = selection.head();
9395 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9396 cursor.column < current_indent.len
9397 });
9398
9399 let mut edits = Vec::new();
9400 let mut prev_edited_row = 0;
9401 let mut row_delta = 0;
9402 for selection in &mut selections {
9403 if selection.start.row != prev_edited_row {
9404 row_delta = 0;
9405 }
9406 prev_edited_row = selection.end.row;
9407
9408 // If the selection is non-empty, then increase the indentation of the selected lines.
9409 if !selection.is_empty() {
9410 row_delta =
9411 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9412 continue;
9413 }
9414
9415 let cursor = selection.head();
9416 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9417 if let Some(suggested_indent) =
9418 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9419 {
9420 // Don't do anything if already at suggested indent
9421 // and there is any other cursor which is not
9422 if has_some_cursor_in_whitespace
9423 && cursor.column == current_indent.len
9424 && current_indent.len == suggested_indent.len
9425 {
9426 continue;
9427 }
9428
9429 // Adjust line and move cursor to suggested indent
9430 // if cursor is not at suggested indent
9431 if cursor.column < suggested_indent.len
9432 && cursor.column <= current_indent.len
9433 && current_indent.len <= suggested_indent.len
9434 {
9435 selection.start = Point::new(cursor.row, suggested_indent.len);
9436 selection.end = selection.start;
9437 if row_delta == 0 {
9438 edits.extend(Buffer::edit_for_indent_size_adjustment(
9439 cursor.row,
9440 current_indent,
9441 suggested_indent,
9442 ));
9443 row_delta = suggested_indent.len - current_indent.len;
9444 }
9445 continue;
9446 }
9447
9448 // If current indent is more than suggested indent
9449 // only move cursor to current indent and skip indent
9450 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9451 selection.start = Point::new(cursor.row, current_indent.len);
9452 selection.end = selection.start;
9453 continue;
9454 }
9455 }
9456
9457 // Otherwise, insert a hard or soft tab.
9458 let settings = buffer.language_settings_at(cursor, cx);
9459 let tab_size = if settings.hard_tabs {
9460 IndentSize::tab()
9461 } else {
9462 let tab_size = settings.tab_size.get();
9463 let indent_remainder = snapshot
9464 .text_for_range(Point::new(cursor.row, 0)..cursor)
9465 .flat_map(str::chars)
9466 .fold(row_delta % tab_size, |counter: u32, c| {
9467 if c == '\t' {
9468 0
9469 } else {
9470 (counter + 1) % tab_size
9471 }
9472 });
9473
9474 let chars_to_next_tab_stop = tab_size - indent_remainder;
9475 IndentSize::spaces(chars_to_next_tab_stop)
9476 };
9477 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9478 selection.end = selection.start;
9479 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9480 row_delta += tab_size.len;
9481 }
9482
9483 self.transact(window, cx, |this, window, cx| {
9484 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9485 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9486 s.select(selections)
9487 });
9488 this.refresh_inline_completion(true, false, window, cx);
9489 });
9490 }
9491
9492 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9493 if self.read_only(cx) {
9494 return;
9495 }
9496 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9497 let mut selections = self.selections.all::<Point>(cx);
9498 let mut prev_edited_row = 0;
9499 let mut row_delta = 0;
9500 let mut edits = Vec::new();
9501 let buffer = self.buffer.read(cx);
9502 let snapshot = buffer.snapshot(cx);
9503 for selection in &mut selections {
9504 if selection.start.row != prev_edited_row {
9505 row_delta = 0;
9506 }
9507 prev_edited_row = selection.end.row;
9508
9509 row_delta =
9510 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9511 }
9512
9513 self.transact(window, cx, |this, window, cx| {
9514 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9515 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9516 s.select(selections)
9517 });
9518 });
9519 }
9520
9521 fn indent_selection(
9522 buffer: &MultiBuffer,
9523 snapshot: &MultiBufferSnapshot,
9524 selection: &mut Selection<Point>,
9525 edits: &mut Vec<(Range<Point>, String)>,
9526 delta_for_start_row: u32,
9527 cx: &App,
9528 ) -> u32 {
9529 let settings = buffer.language_settings_at(selection.start, cx);
9530 let tab_size = settings.tab_size.get();
9531 let indent_kind = if settings.hard_tabs {
9532 IndentKind::Tab
9533 } else {
9534 IndentKind::Space
9535 };
9536 let mut start_row = selection.start.row;
9537 let mut end_row = selection.end.row + 1;
9538
9539 // If a selection ends at the beginning of a line, don't indent
9540 // that last line.
9541 if selection.end.column == 0 && selection.end.row > selection.start.row {
9542 end_row -= 1;
9543 }
9544
9545 // Avoid re-indenting a row that has already been indented by a
9546 // previous selection, but still update this selection's column
9547 // to reflect that indentation.
9548 if delta_for_start_row > 0 {
9549 start_row += 1;
9550 selection.start.column += delta_for_start_row;
9551 if selection.end.row == selection.start.row {
9552 selection.end.column += delta_for_start_row;
9553 }
9554 }
9555
9556 let mut delta_for_end_row = 0;
9557 let has_multiple_rows = start_row + 1 != end_row;
9558 for row in start_row..end_row {
9559 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9560 let indent_delta = match (current_indent.kind, indent_kind) {
9561 (IndentKind::Space, IndentKind::Space) => {
9562 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9563 IndentSize::spaces(columns_to_next_tab_stop)
9564 }
9565 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9566 (_, IndentKind::Tab) => IndentSize::tab(),
9567 };
9568
9569 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9570 0
9571 } else {
9572 selection.start.column
9573 };
9574 let row_start = Point::new(row, start);
9575 edits.push((
9576 row_start..row_start,
9577 indent_delta.chars().collect::<String>(),
9578 ));
9579
9580 // Update this selection's endpoints to reflect the indentation.
9581 if row == selection.start.row {
9582 selection.start.column += indent_delta.len;
9583 }
9584 if row == selection.end.row {
9585 selection.end.column += indent_delta.len;
9586 delta_for_end_row = indent_delta.len;
9587 }
9588 }
9589
9590 if selection.start.row == selection.end.row {
9591 delta_for_start_row + delta_for_end_row
9592 } else {
9593 delta_for_end_row
9594 }
9595 }
9596
9597 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9598 if self.read_only(cx) {
9599 return;
9600 }
9601 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9602 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9603 let selections = self.selections.all::<Point>(cx);
9604 let mut deletion_ranges = Vec::new();
9605 let mut last_outdent = None;
9606 {
9607 let buffer = self.buffer.read(cx);
9608 let snapshot = buffer.snapshot(cx);
9609 for selection in &selections {
9610 let settings = buffer.language_settings_at(selection.start, cx);
9611 let tab_size = settings.tab_size.get();
9612 let mut rows = selection.spanned_rows(false, &display_map);
9613
9614 // Avoid re-outdenting a row that has already been outdented by a
9615 // previous selection.
9616 if let Some(last_row) = last_outdent {
9617 if last_row == rows.start {
9618 rows.start = rows.start.next_row();
9619 }
9620 }
9621 let has_multiple_rows = rows.len() > 1;
9622 for row in rows.iter_rows() {
9623 let indent_size = snapshot.indent_size_for_line(row);
9624 if indent_size.len > 0 {
9625 let deletion_len = match indent_size.kind {
9626 IndentKind::Space => {
9627 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9628 if columns_to_prev_tab_stop == 0 {
9629 tab_size
9630 } else {
9631 columns_to_prev_tab_stop
9632 }
9633 }
9634 IndentKind::Tab => 1,
9635 };
9636 let start = if has_multiple_rows
9637 || deletion_len > selection.start.column
9638 || indent_size.len < selection.start.column
9639 {
9640 0
9641 } else {
9642 selection.start.column - deletion_len
9643 };
9644 deletion_ranges.push(
9645 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9646 );
9647 last_outdent = Some(row);
9648 }
9649 }
9650 }
9651 }
9652
9653 self.transact(window, cx, |this, window, cx| {
9654 this.buffer.update(cx, |buffer, cx| {
9655 let empty_str: Arc<str> = Arc::default();
9656 buffer.edit(
9657 deletion_ranges
9658 .into_iter()
9659 .map(|range| (range, empty_str.clone())),
9660 None,
9661 cx,
9662 );
9663 });
9664 let selections = this.selections.all::<usize>(cx);
9665 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9666 s.select(selections)
9667 });
9668 });
9669 }
9670
9671 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9672 if self.read_only(cx) {
9673 return;
9674 }
9675 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9676 let selections = self
9677 .selections
9678 .all::<usize>(cx)
9679 .into_iter()
9680 .map(|s| s.range());
9681
9682 self.transact(window, cx, |this, window, cx| {
9683 this.buffer.update(cx, |buffer, cx| {
9684 buffer.autoindent_ranges(selections, cx);
9685 });
9686 let selections = this.selections.all::<usize>(cx);
9687 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9688 s.select(selections)
9689 });
9690 });
9691 }
9692
9693 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9694 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9695 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9696 let selections = self.selections.all::<Point>(cx);
9697
9698 let mut new_cursors = Vec::new();
9699 let mut edit_ranges = Vec::new();
9700 let mut selections = selections.iter().peekable();
9701 while let Some(selection) = selections.next() {
9702 let mut rows = selection.spanned_rows(false, &display_map);
9703 let goal_display_column = selection.head().to_display_point(&display_map).column();
9704
9705 // Accumulate contiguous regions of rows that we want to delete.
9706 while let Some(next_selection) = selections.peek() {
9707 let next_rows = next_selection.spanned_rows(false, &display_map);
9708 if next_rows.start <= rows.end {
9709 rows.end = next_rows.end;
9710 selections.next().unwrap();
9711 } else {
9712 break;
9713 }
9714 }
9715
9716 let buffer = &display_map.buffer_snapshot;
9717 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9718 let edit_end;
9719 let cursor_buffer_row;
9720 if buffer.max_point().row >= rows.end.0 {
9721 // If there's a line after the range, delete the \n from the end of the row range
9722 // and position the cursor on the next line.
9723 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9724 cursor_buffer_row = rows.end;
9725 } else {
9726 // If there isn't a line after the range, delete the \n from the line before the
9727 // start of the row range and position the cursor there.
9728 edit_start = edit_start.saturating_sub(1);
9729 edit_end = buffer.len();
9730 cursor_buffer_row = rows.start.previous_row();
9731 }
9732
9733 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9734 *cursor.column_mut() =
9735 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9736
9737 new_cursors.push((
9738 selection.id,
9739 buffer.anchor_after(cursor.to_point(&display_map)),
9740 ));
9741 edit_ranges.push(edit_start..edit_end);
9742 }
9743
9744 self.transact(window, cx, |this, window, cx| {
9745 let buffer = this.buffer.update(cx, |buffer, cx| {
9746 let empty_str: Arc<str> = Arc::default();
9747 buffer.edit(
9748 edit_ranges
9749 .into_iter()
9750 .map(|range| (range, empty_str.clone())),
9751 None,
9752 cx,
9753 );
9754 buffer.snapshot(cx)
9755 });
9756 let new_selections = new_cursors
9757 .into_iter()
9758 .map(|(id, cursor)| {
9759 let cursor = cursor.to_point(&buffer);
9760 Selection {
9761 id,
9762 start: cursor,
9763 end: cursor,
9764 reversed: false,
9765 goal: SelectionGoal::None,
9766 }
9767 })
9768 .collect();
9769
9770 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9771 s.select(new_selections);
9772 });
9773 });
9774 }
9775
9776 pub fn join_lines_impl(
9777 &mut self,
9778 insert_whitespace: bool,
9779 window: &mut Window,
9780 cx: &mut Context<Self>,
9781 ) {
9782 if self.read_only(cx) {
9783 return;
9784 }
9785 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9786 for selection in self.selections.all::<Point>(cx) {
9787 let start = MultiBufferRow(selection.start.row);
9788 // Treat single line selections as if they include the next line. Otherwise this action
9789 // would do nothing for single line selections individual cursors.
9790 let end = if selection.start.row == selection.end.row {
9791 MultiBufferRow(selection.start.row + 1)
9792 } else {
9793 MultiBufferRow(selection.end.row)
9794 };
9795
9796 if let Some(last_row_range) = row_ranges.last_mut() {
9797 if start <= last_row_range.end {
9798 last_row_range.end = end;
9799 continue;
9800 }
9801 }
9802 row_ranges.push(start..end);
9803 }
9804
9805 let snapshot = self.buffer.read(cx).snapshot(cx);
9806 let mut cursor_positions = Vec::new();
9807 for row_range in &row_ranges {
9808 let anchor = snapshot.anchor_before(Point::new(
9809 row_range.end.previous_row().0,
9810 snapshot.line_len(row_range.end.previous_row()),
9811 ));
9812 cursor_positions.push(anchor..anchor);
9813 }
9814
9815 self.transact(window, cx, |this, window, cx| {
9816 for row_range in row_ranges.into_iter().rev() {
9817 for row in row_range.iter_rows().rev() {
9818 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9819 let next_line_row = row.next_row();
9820 let indent = snapshot.indent_size_for_line(next_line_row);
9821 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9822
9823 let replace =
9824 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9825 " "
9826 } else {
9827 ""
9828 };
9829
9830 this.buffer.update(cx, |buffer, cx| {
9831 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9832 });
9833 }
9834 }
9835
9836 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9837 s.select_anchor_ranges(cursor_positions)
9838 });
9839 });
9840 }
9841
9842 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9843 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9844 self.join_lines_impl(true, window, cx);
9845 }
9846
9847 pub fn sort_lines_case_sensitive(
9848 &mut self,
9849 _: &SortLinesCaseSensitive,
9850 window: &mut Window,
9851 cx: &mut Context<Self>,
9852 ) {
9853 self.manipulate_lines(window, cx, |lines| lines.sort())
9854 }
9855
9856 pub fn sort_lines_case_insensitive(
9857 &mut self,
9858 _: &SortLinesCaseInsensitive,
9859 window: &mut Window,
9860 cx: &mut Context<Self>,
9861 ) {
9862 self.manipulate_lines(window, cx, |lines| {
9863 lines.sort_by_key(|line| line.to_lowercase())
9864 })
9865 }
9866
9867 pub fn unique_lines_case_insensitive(
9868 &mut self,
9869 _: &UniqueLinesCaseInsensitive,
9870 window: &mut Window,
9871 cx: &mut Context<Self>,
9872 ) {
9873 self.manipulate_lines(window, cx, |lines| {
9874 let mut seen = HashSet::default();
9875 lines.retain(|line| seen.insert(line.to_lowercase()));
9876 })
9877 }
9878
9879 pub fn unique_lines_case_sensitive(
9880 &mut self,
9881 _: &UniqueLinesCaseSensitive,
9882 window: &mut Window,
9883 cx: &mut Context<Self>,
9884 ) {
9885 self.manipulate_lines(window, cx, |lines| {
9886 let mut seen = HashSet::default();
9887 lines.retain(|line| seen.insert(*line));
9888 })
9889 }
9890
9891 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9892 let Some(project) = self.project.clone() else {
9893 return;
9894 };
9895 self.reload(project, window, cx)
9896 .detach_and_notify_err(window, cx);
9897 }
9898
9899 pub fn restore_file(
9900 &mut self,
9901 _: &::git::RestoreFile,
9902 window: &mut Window,
9903 cx: &mut Context<Self>,
9904 ) {
9905 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9906 let mut buffer_ids = HashSet::default();
9907 let snapshot = self.buffer().read(cx).snapshot(cx);
9908 for selection in self.selections.all::<usize>(cx) {
9909 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9910 }
9911
9912 let buffer = self.buffer().read(cx);
9913 let ranges = buffer_ids
9914 .into_iter()
9915 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9916 .collect::<Vec<_>>();
9917
9918 self.restore_hunks_in_ranges(ranges, window, cx);
9919 }
9920
9921 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9922 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9923 let selections = self
9924 .selections
9925 .all(cx)
9926 .into_iter()
9927 .map(|s| s.range())
9928 .collect();
9929 self.restore_hunks_in_ranges(selections, window, cx);
9930 }
9931
9932 pub fn restore_hunks_in_ranges(
9933 &mut self,
9934 ranges: Vec<Range<Point>>,
9935 window: &mut Window,
9936 cx: &mut Context<Editor>,
9937 ) {
9938 let mut revert_changes = HashMap::default();
9939 let chunk_by = self
9940 .snapshot(window, cx)
9941 .hunks_for_ranges(ranges)
9942 .into_iter()
9943 .chunk_by(|hunk| hunk.buffer_id);
9944 for (buffer_id, hunks) in &chunk_by {
9945 let hunks = hunks.collect::<Vec<_>>();
9946 for hunk in &hunks {
9947 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9948 }
9949 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9950 }
9951 drop(chunk_by);
9952 if !revert_changes.is_empty() {
9953 self.transact(window, cx, |editor, window, cx| {
9954 editor.restore(revert_changes, window, cx);
9955 });
9956 }
9957 }
9958
9959 pub fn open_active_item_in_terminal(
9960 &mut self,
9961 _: &OpenInTerminal,
9962 window: &mut Window,
9963 cx: &mut Context<Self>,
9964 ) {
9965 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9966 let project_path = buffer.read(cx).project_path(cx)?;
9967 let project = self.project.as_ref()?.read(cx);
9968 let entry = project.entry_for_path(&project_path, cx)?;
9969 let parent = match &entry.canonical_path {
9970 Some(canonical_path) => canonical_path.to_path_buf(),
9971 None => project.absolute_path(&project_path, cx)?,
9972 }
9973 .parent()?
9974 .to_path_buf();
9975 Some(parent)
9976 }) {
9977 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9978 }
9979 }
9980
9981 fn set_breakpoint_context_menu(
9982 &mut self,
9983 display_row: DisplayRow,
9984 position: Option<Anchor>,
9985 clicked_point: gpui::Point<Pixels>,
9986 window: &mut Window,
9987 cx: &mut Context<Self>,
9988 ) {
9989 if !cx.has_flag::<DebuggerFeatureFlag>() {
9990 return;
9991 }
9992 let source = self
9993 .buffer
9994 .read(cx)
9995 .snapshot(cx)
9996 .anchor_before(Point::new(display_row.0, 0u32));
9997
9998 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9999
10000 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10001 self,
10002 source,
10003 clicked_point,
10004 context_menu,
10005 window,
10006 cx,
10007 );
10008 }
10009
10010 fn add_edit_breakpoint_block(
10011 &mut self,
10012 anchor: Anchor,
10013 breakpoint: &Breakpoint,
10014 edit_action: BreakpointPromptEditAction,
10015 window: &mut Window,
10016 cx: &mut Context<Self>,
10017 ) {
10018 let weak_editor = cx.weak_entity();
10019 let bp_prompt = cx.new(|cx| {
10020 BreakpointPromptEditor::new(
10021 weak_editor,
10022 anchor,
10023 breakpoint.clone(),
10024 edit_action,
10025 window,
10026 cx,
10027 )
10028 });
10029
10030 let height = bp_prompt.update(cx, |this, cx| {
10031 this.prompt
10032 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10033 });
10034 let cloned_prompt = bp_prompt.clone();
10035 let blocks = vec![BlockProperties {
10036 style: BlockStyle::Sticky,
10037 placement: BlockPlacement::Above(anchor),
10038 height: Some(height),
10039 render: Arc::new(move |cx| {
10040 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10041 cloned_prompt.clone().into_any_element()
10042 }),
10043 priority: 0,
10044 render_in_minimap: true,
10045 }];
10046
10047 let focus_handle = bp_prompt.focus_handle(cx);
10048 window.focus(&focus_handle);
10049
10050 let block_ids = self.insert_blocks(blocks, None, cx);
10051 bp_prompt.update(cx, |prompt, _| {
10052 prompt.add_block_ids(block_ids);
10053 });
10054 }
10055
10056 pub(crate) fn breakpoint_at_row(
10057 &self,
10058 row: u32,
10059 window: &mut Window,
10060 cx: &mut Context<Self>,
10061 ) -> Option<(Anchor, Breakpoint)> {
10062 let snapshot = self.snapshot(window, cx);
10063 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10064
10065 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10066 }
10067
10068 pub(crate) fn breakpoint_at_anchor(
10069 &self,
10070 breakpoint_position: Anchor,
10071 snapshot: &EditorSnapshot,
10072 cx: &mut Context<Self>,
10073 ) -> Option<(Anchor, Breakpoint)> {
10074 let project = self.project.clone()?;
10075
10076 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10077 snapshot
10078 .buffer_snapshot
10079 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10080 })?;
10081
10082 let enclosing_excerpt = breakpoint_position.excerpt_id;
10083 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10084 let buffer_snapshot = buffer.read(cx).snapshot();
10085
10086 let row = buffer_snapshot
10087 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10088 .row;
10089
10090 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10091 let anchor_end = snapshot
10092 .buffer_snapshot
10093 .anchor_after(Point::new(row, line_len));
10094
10095 let bp = self
10096 .breakpoint_store
10097 .as_ref()?
10098 .read_with(cx, |breakpoint_store, cx| {
10099 breakpoint_store
10100 .breakpoints(
10101 &buffer,
10102 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10103 &buffer_snapshot,
10104 cx,
10105 )
10106 .next()
10107 .and_then(|(bp, _)| {
10108 let breakpoint_row = buffer_snapshot
10109 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10110 .row;
10111
10112 if breakpoint_row == row {
10113 snapshot
10114 .buffer_snapshot
10115 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10116 .map(|position| (position, bp.bp.clone()))
10117 } else {
10118 None
10119 }
10120 })
10121 });
10122 bp
10123 }
10124
10125 pub fn edit_log_breakpoint(
10126 &mut self,
10127 _: &EditLogBreakpoint,
10128 window: &mut Window,
10129 cx: &mut Context<Self>,
10130 ) {
10131 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10132 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10133 message: None,
10134 state: BreakpointState::Enabled,
10135 condition: None,
10136 hit_condition: None,
10137 });
10138
10139 self.add_edit_breakpoint_block(
10140 anchor,
10141 &breakpoint,
10142 BreakpointPromptEditAction::Log,
10143 window,
10144 cx,
10145 );
10146 }
10147 }
10148
10149 fn breakpoints_at_cursors(
10150 &self,
10151 window: &mut Window,
10152 cx: &mut Context<Self>,
10153 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10154 let snapshot = self.snapshot(window, cx);
10155 let cursors = self
10156 .selections
10157 .disjoint_anchors()
10158 .into_iter()
10159 .map(|selection| {
10160 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10161
10162 let breakpoint_position = self
10163 .breakpoint_at_row(cursor_position.row, window, cx)
10164 .map(|bp| bp.0)
10165 .unwrap_or_else(|| {
10166 snapshot
10167 .display_snapshot
10168 .buffer_snapshot
10169 .anchor_after(Point::new(cursor_position.row, 0))
10170 });
10171
10172 let breakpoint = self
10173 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10174 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10175
10176 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10177 })
10178 // 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.
10179 .collect::<HashMap<Anchor, _>>();
10180
10181 cursors.into_iter().collect()
10182 }
10183
10184 pub fn enable_breakpoint(
10185 &mut self,
10186 _: &crate::actions::EnableBreakpoint,
10187 window: &mut Window,
10188 cx: &mut Context<Self>,
10189 ) {
10190 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10191 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10192 continue;
10193 };
10194 self.edit_breakpoint_at_anchor(
10195 anchor,
10196 breakpoint,
10197 BreakpointEditAction::InvertState,
10198 cx,
10199 );
10200 }
10201 }
10202
10203 pub fn disable_breakpoint(
10204 &mut self,
10205 _: &crate::actions::DisableBreakpoint,
10206 window: &mut Window,
10207 cx: &mut Context<Self>,
10208 ) {
10209 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10210 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10211 continue;
10212 };
10213 self.edit_breakpoint_at_anchor(
10214 anchor,
10215 breakpoint,
10216 BreakpointEditAction::InvertState,
10217 cx,
10218 );
10219 }
10220 }
10221
10222 pub fn toggle_breakpoint(
10223 &mut self,
10224 _: &crate::actions::ToggleBreakpoint,
10225 window: &mut Window,
10226 cx: &mut Context<Self>,
10227 ) {
10228 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10229 if let Some(breakpoint) = breakpoint {
10230 self.edit_breakpoint_at_anchor(
10231 anchor,
10232 breakpoint,
10233 BreakpointEditAction::Toggle,
10234 cx,
10235 );
10236 } else {
10237 self.edit_breakpoint_at_anchor(
10238 anchor,
10239 Breakpoint::new_standard(),
10240 BreakpointEditAction::Toggle,
10241 cx,
10242 );
10243 }
10244 }
10245 }
10246
10247 pub fn edit_breakpoint_at_anchor(
10248 &mut self,
10249 breakpoint_position: Anchor,
10250 breakpoint: Breakpoint,
10251 edit_action: BreakpointEditAction,
10252 cx: &mut Context<Self>,
10253 ) {
10254 let Some(breakpoint_store) = &self.breakpoint_store else {
10255 return;
10256 };
10257
10258 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10259 if breakpoint_position == Anchor::min() {
10260 self.buffer()
10261 .read(cx)
10262 .excerpt_buffer_ids()
10263 .into_iter()
10264 .next()
10265 } else {
10266 None
10267 }
10268 }) else {
10269 return;
10270 };
10271
10272 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10273 return;
10274 };
10275
10276 breakpoint_store.update(cx, |breakpoint_store, cx| {
10277 breakpoint_store.toggle_breakpoint(
10278 buffer,
10279 BreakpointWithPosition {
10280 position: breakpoint_position.text_anchor,
10281 bp: breakpoint,
10282 },
10283 edit_action,
10284 cx,
10285 );
10286 });
10287
10288 cx.notify();
10289 }
10290
10291 #[cfg(any(test, feature = "test-support"))]
10292 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10293 self.breakpoint_store.clone()
10294 }
10295
10296 pub fn prepare_restore_change(
10297 &self,
10298 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10299 hunk: &MultiBufferDiffHunk,
10300 cx: &mut App,
10301 ) -> Option<()> {
10302 if hunk.is_created_file() {
10303 return None;
10304 }
10305 let buffer = self.buffer.read(cx);
10306 let diff = buffer.diff_for(hunk.buffer_id)?;
10307 let buffer = buffer.buffer(hunk.buffer_id)?;
10308 let buffer = buffer.read(cx);
10309 let original_text = diff
10310 .read(cx)
10311 .base_text()
10312 .as_rope()
10313 .slice(hunk.diff_base_byte_range.clone());
10314 let buffer_snapshot = buffer.snapshot();
10315 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10316 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10317 probe
10318 .0
10319 .start
10320 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10321 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10322 }) {
10323 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10324 Some(())
10325 } else {
10326 None
10327 }
10328 }
10329
10330 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10331 self.manipulate_lines(window, cx, |lines| lines.reverse())
10332 }
10333
10334 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10335 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10336 }
10337
10338 fn manipulate_lines<Fn>(
10339 &mut self,
10340 window: &mut Window,
10341 cx: &mut Context<Self>,
10342 mut callback: Fn,
10343 ) where
10344 Fn: FnMut(&mut Vec<&str>),
10345 {
10346 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10347
10348 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10349 let buffer = self.buffer.read(cx).snapshot(cx);
10350
10351 let mut edits = Vec::new();
10352
10353 let selections = self.selections.all::<Point>(cx);
10354 let mut selections = selections.iter().peekable();
10355 let mut contiguous_row_selections = Vec::new();
10356 let mut new_selections = Vec::new();
10357 let mut added_lines = 0;
10358 let mut removed_lines = 0;
10359
10360 while let Some(selection) = selections.next() {
10361 let (start_row, end_row) = consume_contiguous_rows(
10362 &mut contiguous_row_selections,
10363 selection,
10364 &display_map,
10365 &mut selections,
10366 );
10367
10368 let start_point = Point::new(start_row.0, 0);
10369 let end_point = Point::new(
10370 end_row.previous_row().0,
10371 buffer.line_len(end_row.previous_row()),
10372 );
10373 let text = buffer
10374 .text_for_range(start_point..end_point)
10375 .collect::<String>();
10376
10377 let mut lines = text.split('\n').collect_vec();
10378
10379 let lines_before = lines.len();
10380 callback(&mut lines);
10381 let lines_after = lines.len();
10382
10383 edits.push((start_point..end_point, lines.join("\n")));
10384
10385 // Selections must change based on added and removed line count
10386 let start_row =
10387 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10388 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10389 new_selections.push(Selection {
10390 id: selection.id,
10391 start: start_row,
10392 end: end_row,
10393 goal: SelectionGoal::None,
10394 reversed: selection.reversed,
10395 });
10396
10397 if lines_after > lines_before {
10398 added_lines += lines_after - lines_before;
10399 } else if lines_before > lines_after {
10400 removed_lines += lines_before - lines_after;
10401 }
10402 }
10403
10404 self.transact(window, cx, |this, window, cx| {
10405 let buffer = this.buffer.update(cx, |buffer, cx| {
10406 buffer.edit(edits, None, cx);
10407 buffer.snapshot(cx)
10408 });
10409
10410 // Recalculate offsets on newly edited buffer
10411 let new_selections = new_selections
10412 .iter()
10413 .map(|s| {
10414 let start_point = Point::new(s.start.0, 0);
10415 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10416 Selection {
10417 id: s.id,
10418 start: buffer.point_to_offset(start_point),
10419 end: buffer.point_to_offset(end_point),
10420 goal: s.goal,
10421 reversed: s.reversed,
10422 }
10423 })
10424 .collect();
10425
10426 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10427 s.select(new_selections);
10428 });
10429
10430 this.request_autoscroll(Autoscroll::fit(), cx);
10431 });
10432 }
10433
10434 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10435 self.manipulate_text(window, cx, |text| {
10436 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10437 if has_upper_case_characters {
10438 text.to_lowercase()
10439 } else {
10440 text.to_uppercase()
10441 }
10442 })
10443 }
10444
10445 pub fn convert_to_upper_case(
10446 &mut self,
10447 _: &ConvertToUpperCase,
10448 window: &mut Window,
10449 cx: &mut Context<Self>,
10450 ) {
10451 self.manipulate_text(window, cx, |text| text.to_uppercase())
10452 }
10453
10454 pub fn convert_to_lower_case(
10455 &mut self,
10456 _: &ConvertToLowerCase,
10457 window: &mut Window,
10458 cx: &mut Context<Self>,
10459 ) {
10460 self.manipulate_text(window, cx, |text| text.to_lowercase())
10461 }
10462
10463 pub fn convert_to_title_case(
10464 &mut self,
10465 _: &ConvertToTitleCase,
10466 window: &mut Window,
10467 cx: &mut Context<Self>,
10468 ) {
10469 self.manipulate_text(window, cx, |text| {
10470 text.split('\n')
10471 .map(|line| line.to_case(Case::Title))
10472 .join("\n")
10473 })
10474 }
10475
10476 pub fn convert_to_snake_case(
10477 &mut self,
10478 _: &ConvertToSnakeCase,
10479 window: &mut Window,
10480 cx: &mut Context<Self>,
10481 ) {
10482 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10483 }
10484
10485 pub fn convert_to_kebab_case(
10486 &mut self,
10487 _: &ConvertToKebabCase,
10488 window: &mut Window,
10489 cx: &mut Context<Self>,
10490 ) {
10491 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10492 }
10493
10494 pub fn convert_to_upper_camel_case(
10495 &mut self,
10496 _: &ConvertToUpperCamelCase,
10497 window: &mut Window,
10498 cx: &mut Context<Self>,
10499 ) {
10500 self.manipulate_text(window, cx, |text| {
10501 text.split('\n')
10502 .map(|line| line.to_case(Case::UpperCamel))
10503 .join("\n")
10504 })
10505 }
10506
10507 pub fn convert_to_lower_camel_case(
10508 &mut self,
10509 _: &ConvertToLowerCamelCase,
10510 window: &mut Window,
10511 cx: &mut Context<Self>,
10512 ) {
10513 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10514 }
10515
10516 pub fn convert_to_opposite_case(
10517 &mut self,
10518 _: &ConvertToOppositeCase,
10519 window: &mut Window,
10520 cx: &mut Context<Self>,
10521 ) {
10522 self.manipulate_text(window, cx, |text| {
10523 text.chars()
10524 .fold(String::with_capacity(text.len()), |mut t, c| {
10525 if c.is_uppercase() {
10526 t.extend(c.to_lowercase());
10527 } else {
10528 t.extend(c.to_uppercase());
10529 }
10530 t
10531 })
10532 })
10533 }
10534
10535 pub fn convert_to_rot13(
10536 &mut self,
10537 _: &ConvertToRot13,
10538 window: &mut Window,
10539 cx: &mut Context<Self>,
10540 ) {
10541 self.manipulate_text(window, cx, |text| {
10542 text.chars()
10543 .map(|c| match c {
10544 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10545 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10546 _ => c,
10547 })
10548 .collect()
10549 })
10550 }
10551
10552 pub fn convert_to_rot47(
10553 &mut self,
10554 _: &ConvertToRot47,
10555 window: &mut Window,
10556 cx: &mut Context<Self>,
10557 ) {
10558 self.manipulate_text(window, cx, |text| {
10559 text.chars()
10560 .map(|c| {
10561 let code_point = c as u32;
10562 if code_point >= 33 && code_point <= 126 {
10563 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10564 }
10565 c
10566 })
10567 .collect()
10568 })
10569 }
10570
10571 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10572 where
10573 Fn: FnMut(&str) -> String,
10574 {
10575 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10576 let buffer = self.buffer.read(cx).snapshot(cx);
10577
10578 let mut new_selections = Vec::new();
10579 let mut edits = Vec::new();
10580 let mut selection_adjustment = 0i32;
10581
10582 for selection in self.selections.all::<usize>(cx) {
10583 let selection_is_empty = selection.is_empty();
10584
10585 let (start, end) = if selection_is_empty {
10586 let word_range = movement::surrounding_word(
10587 &display_map,
10588 selection.start.to_display_point(&display_map),
10589 );
10590 let start = word_range.start.to_offset(&display_map, Bias::Left);
10591 let end = word_range.end.to_offset(&display_map, Bias::Left);
10592 (start, end)
10593 } else {
10594 (selection.start, selection.end)
10595 };
10596
10597 let text = buffer.text_for_range(start..end).collect::<String>();
10598 let old_length = text.len() as i32;
10599 let text = callback(&text);
10600
10601 new_selections.push(Selection {
10602 start: (start as i32 - selection_adjustment) as usize,
10603 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10604 goal: SelectionGoal::None,
10605 ..selection
10606 });
10607
10608 selection_adjustment += old_length - text.len() as i32;
10609
10610 edits.push((start..end, text));
10611 }
10612
10613 self.transact(window, cx, |this, window, cx| {
10614 this.buffer.update(cx, |buffer, cx| {
10615 buffer.edit(edits, None, cx);
10616 });
10617
10618 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10619 s.select(new_selections);
10620 });
10621
10622 this.request_autoscroll(Autoscroll::fit(), cx);
10623 });
10624 }
10625
10626 pub fn move_selection_on_drop(
10627 &mut self,
10628 selection: &Selection<Anchor>,
10629 target: DisplayPoint,
10630 is_cut: bool,
10631 window: &mut Window,
10632 cx: &mut Context<Self>,
10633 ) {
10634 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10635 let buffer = &display_map.buffer_snapshot;
10636 let mut edits = Vec::new();
10637 let insert_point = display_map
10638 .clip_point(target, Bias::Left)
10639 .to_point(&display_map);
10640 let text = buffer
10641 .text_for_range(selection.start..selection.end)
10642 .collect::<String>();
10643 if is_cut {
10644 edits.push(((selection.start..selection.end), String::new()));
10645 }
10646 let insert_anchor = buffer.anchor_before(insert_point);
10647 edits.push(((insert_anchor..insert_anchor), text));
10648 let last_edit_start = insert_anchor.bias_left(buffer);
10649 let last_edit_end = insert_anchor.bias_right(buffer);
10650 self.transact(window, cx, |this, window, cx| {
10651 this.buffer.update(cx, |buffer, cx| {
10652 buffer.edit(edits, None, cx);
10653 });
10654 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10655 s.select_anchor_ranges([last_edit_start..last_edit_end]);
10656 });
10657 });
10658 }
10659
10660 pub fn clear_selection_drag_state(&mut self) {
10661 self.selection_drag_state = SelectionDragState::None;
10662 }
10663
10664 pub fn duplicate(
10665 &mut self,
10666 upwards: bool,
10667 whole_lines: bool,
10668 window: &mut Window,
10669 cx: &mut Context<Self>,
10670 ) {
10671 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10672
10673 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10674 let buffer = &display_map.buffer_snapshot;
10675 let selections = self.selections.all::<Point>(cx);
10676
10677 let mut edits = Vec::new();
10678 let mut selections_iter = selections.iter().peekable();
10679 while let Some(selection) = selections_iter.next() {
10680 let mut rows = selection.spanned_rows(false, &display_map);
10681 // duplicate line-wise
10682 if whole_lines || selection.start == selection.end {
10683 // Avoid duplicating the same lines twice.
10684 while let Some(next_selection) = selections_iter.peek() {
10685 let next_rows = next_selection.spanned_rows(false, &display_map);
10686 if next_rows.start < rows.end {
10687 rows.end = next_rows.end;
10688 selections_iter.next().unwrap();
10689 } else {
10690 break;
10691 }
10692 }
10693
10694 // Copy the text from the selected row region and splice it either at the start
10695 // or end of the region.
10696 let start = Point::new(rows.start.0, 0);
10697 let end = Point::new(
10698 rows.end.previous_row().0,
10699 buffer.line_len(rows.end.previous_row()),
10700 );
10701 let text = buffer
10702 .text_for_range(start..end)
10703 .chain(Some("\n"))
10704 .collect::<String>();
10705 let insert_location = if upwards {
10706 Point::new(rows.end.0, 0)
10707 } else {
10708 start
10709 };
10710 edits.push((insert_location..insert_location, text));
10711 } else {
10712 // duplicate character-wise
10713 let start = selection.start;
10714 let end = selection.end;
10715 let text = buffer.text_for_range(start..end).collect::<String>();
10716 edits.push((selection.end..selection.end, text));
10717 }
10718 }
10719
10720 self.transact(window, cx, |this, _, cx| {
10721 this.buffer.update(cx, |buffer, cx| {
10722 buffer.edit(edits, None, cx);
10723 });
10724
10725 this.request_autoscroll(Autoscroll::fit(), cx);
10726 });
10727 }
10728
10729 pub fn duplicate_line_up(
10730 &mut self,
10731 _: &DuplicateLineUp,
10732 window: &mut Window,
10733 cx: &mut Context<Self>,
10734 ) {
10735 self.duplicate(true, true, window, cx);
10736 }
10737
10738 pub fn duplicate_line_down(
10739 &mut self,
10740 _: &DuplicateLineDown,
10741 window: &mut Window,
10742 cx: &mut Context<Self>,
10743 ) {
10744 self.duplicate(false, true, window, cx);
10745 }
10746
10747 pub fn duplicate_selection(
10748 &mut self,
10749 _: &DuplicateSelection,
10750 window: &mut Window,
10751 cx: &mut Context<Self>,
10752 ) {
10753 self.duplicate(false, false, window, cx);
10754 }
10755
10756 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10757 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10758
10759 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10760 let buffer = self.buffer.read(cx).snapshot(cx);
10761
10762 let mut edits = Vec::new();
10763 let mut unfold_ranges = Vec::new();
10764 let mut refold_creases = Vec::new();
10765
10766 let selections = self.selections.all::<Point>(cx);
10767 let mut selections = selections.iter().peekable();
10768 let mut contiguous_row_selections = Vec::new();
10769 let mut new_selections = Vec::new();
10770
10771 while let Some(selection) = selections.next() {
10772 // Find all the selections that span a contiguous row range
10773 let (start_row, end_row) = consume_contiguous_rows(
10774 &mut contiguous_row_selections,
10775 selection,
10776 &display_map,
10777 &mut selections,
10778 );
10779
10780 // Move the text spanned by the row range to be before the line preceding the row range
10781 if start_row.0 > 0 {
10782 let range_to_move = Point::new(
10783 start_row.previous_row().0,
10784 buffer.line_len(start_row.previous_row()),
10785 )
10786 ..Point::new(
10787 end_row.previous_row().0,
10788 buffer.line_len(end_row.previous_row()),
10789 );
10790 let insertion_point = display_map
10791 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10792 .0;
10793
10794 // Don't move lines across excerpts
10795 if buffer
10796 .excerpt_containing(insertion_point..range_to_move.end)
10797 .is_some()
10798 {
10799 let text = buffer
10800 .text_for_range(range_to_move.clone())
10801 .flat_map(|s| s.chars())
10802 .skip(1)
10803 .chain(['\n'])
10804 .collect::<String>();
10805
10806 edits.push((
10807 buffer.anchor_after(range_to_move.start)
10808 ..buffer.anchor_before(range_to_move.end),
10809 String::new(),
10810 ));
10811 let insertion_anchor = buffer.anchor_after(insertion_point);
10812 edits.push((insertion_anchor..insertion_anchor, text));
10813
10814 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10815
10816 // Move selections up
10817 new_selections.extend(contiguous_row_selections.drain(..).map(
10818 |mut selection| {
10819 selection.start.row -= row_delta;
10820 selection.end.row -= row_delta;
10821 selection
10822 },
10823 ));
10824
10825 // Move folds up
10826 unfold_ranges.push(range_to_move.clone());
10827 for fold in display_map.folds_in_range(
10828 buffer.anchor_before(range_to_move.start)
10829 ..buffer.anchor_after(range_to_move.end),
10830 ) {
10831 let mut start = fold.range.start.to_point(&buffer);
10832 let mut end = fold.range.end.to_point(&buffer);
10833 start.row -= row_delta;
10834 end.row -= row_delta;
10835 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10836 }
10837 }
10838 }
10839
10840 // If we didn't move line(s), preserve the existing selections
10841 new_selections.append(&mut contiguous_row_selections);
10842 }
10843
10844 self.transact(window, cx, |this, window, cx| {
10845 this.unfold_ranges(&unfold_ranges, true, true, cx);
10846 this.buffer.update(cx, |buffer, cx| {
10847 for (range, text) in edits {
10848 buffer.edit([(range, text)], None, cx);
10849 }
10850 });
10851 this.fold_creases(refold_creases, true, window, cx);
10852 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10853 s.select(new_selections);
10854 })
10855 });
10856 }
10857
10858 pub fn move_line_down(
10859 &mut self,
10860 _: &MoveLineDown,
10861 window: &mut Window,
10862 cx: &mut Context<Self>,
10863 ) {
10864 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10865
10866 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10867 let buffer = self.buffer.read(cx).snapshot(cx);
10868
10869 let mut edits = Vec::new();
10870 let mut unfold_ranges = Vec::new();
10871 let mut refold_creases = Vec::new();
10872
10873 let selections = self.selections.all::<Point>(cx);
10874 let mut selections = selections.iter().peekable();
10875 let mut contiguous_row_selections = Vec::new();
10876 let mut new_selections = Vec::new();
10877
10878 while let Some(selection) = selections.next() {
10879 // Find all the selections that span a contiguous row range
10880 let (start_row, end_row) = consume_contiguous_rows(
10881 &mut contiguous_row_selections,
10882 selection,
10883 &display_map,
10884 &mut selections,
10885 );
10886
10887 // Move the text spanned by the row range to be after the last line of the row range
10888 if end_row.0 <= buffer.max_point().row {
10889 let range_to_move =
10890 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10891 let insertion_point = display_map
10892 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10893 .0;
10894
10895 // Don't move lines across excerpt boundaries
10896 if buffer
10897 .excerpt_containing(range_to_move.start..insertion_point)
10898 .is_some()
10899 {
10900 let mut text = String::from("\n");
10901 text.extend(buffer.text_for_range(range_to_move.clone()));
10902 text.pop(); // Drop trailing newline
10903 edits.push((
10904 buffer.anchor_after(range_to_move.start)
10905 ..buffer.anchor_before(range_to_move.end),
10906 String::new(),
10907 ));
10908 let insertion_anchor = buffer.anchor_after(insertion_point);
10909 edits.push((insertion_anchor..insertion_anchor, text));
10910
10911 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10912
10913 // Move selections down
10914 new_selections.extend(contiguous_row_selections.drain(..).map(
10915 |mut selection| {
10916 selection.start.row += row_delta;
10917 selection.end.row += row_delta;
10918 selection
10919 },
10920 ));
10921
10922 // Move folds down
10923 unfold_ranges.push(range_to_move.clone());
10924 for fold in display_map.folds_in_range(
10925 buffer.anchor_before(range_to_move.start)
10926 ..buffer.anchor_after(range_to_move.end),
10927 ) {
10928 let mut start = fold.range.start.to_point(&buffer);
10929 let mut end = fold.range.end.to_point(&buffer);
10930 start.row += row_delta;
10931 end.row += row_delta;
10932 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10933 }
10934 }
10935 }
10936
10937 // If we didn't move line(s), preserve the existing selections
10938 new_selections.append(&mut contiguous_row_selections);
10939 }
10940
10941 self.transact(window, cx, |this, window, cx| {
10942 this.unfold_ranges(&unfold_ranges, true, true, cx);
10943 this.buffer.update(cx, |buffer, cx| {
10944 for (range, text) in edits {
10945 buffer.edit([(range, text)], None, cx);
10946 }
10947 });
10948 this.fold_creases(refold_creases, true, window, cx);
10949 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10950 s.select(new_selections)
10951 });
10952 });
10953 }
10954
10955 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10956 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10957 let text_layout_details = &self.text_layout_details(window);
10958 self.transact(window, cx, |this, window, cx| {
10959 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10960 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10961 s.move_with(|display_map, selection| {
10962 if !selection.is_empty() {
10963 return;
10964 }
10965
10966 let mut head = selection.head();
10967 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10968 if head.column() == display_map.line_len(head.row()) {
10969 transpose_offset = display_map
10970 .buffer_snapshot
10971 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10972 }
10973
10974 if transpose_offset == 0 {
10975 return;
10976 }
10977
10978 *head.column_mut() += 1;
10979 head = display_map.clip_point(head, Bias::Right);
10980 let goal = SelectionGoal::HorizontalPosition(
10981 display_map
10982 .x_for_display_point(head, text_layout_details)
10983 .into(),
10984 );
10985 selection.collapse_to(head, goal);
10986
10987 let transpose_start = display_map
10988 .buffer_snapshot
10989 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10990 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10991 let transpose_end = display_map
10992 .buffer_snapshot
10993 .clip_offset(transpose_offset + 1, Bias::Right);
10994 if let Some(ch) =
10995 display_map.buffer_snapshot.chars_at(transpose_start).next()
10996 {
10997 edits.push((transpose_start..transpose_offset, String::new()));
10998 edits.push((transpose_end..transpose_end, ch.to_string()));
10999 }
11000 }
11001 });
11002 edits
11003 });
11004 this.buffer
11005 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11006 let selections = this.selections.all::<usize>(cx);
11007 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11008 s.select(selections);
11009 });
11010 });
11011 }
11012
11013 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11014 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11015 self.rewrap_impl(RewrapOptions::default(), cx)
11016 }
11017
11018 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11019 let buffer = self.buffer.read(cx).snapshot(cx);
11020 let selections = self.selections.all::<Point>(cx);
11021
11022 // Shrink and split selections to respect paragraph boundaries.
11023 let ranges = selections.into_iter().flat_map(|selection| {
11024 let language_settings = buffer.language_settings_at(selection.head(), cx);
11025 let language_scope = buffer.language_scope_at(selection.head());
11026
11027 let Some(start_row) = (selection.start.row..=selection.end.row)
11028 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11029 else {
11030 return vec![];
11031 };
11032 let Some(end_row) = (selection.start.row..=selection.end.row)
11033 .rev()
11034 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11035 else {
11036 return vec![];
11037 };
11038
11039 let mut row = start_row;
11040 let mut ranges = Vec::new();
11041 while let Some(blank_row) =
11042 (row..end_row).find(|row| buffer.is_line_blank(MultiBufferRow(*row)))
11043 {
11044 let next_paragraph_start = (blank_row + 1..=end_row)
11045 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11046 .unwrap();
11047 ranges.push((
11048 language_settings.clone(),
11049 language_scope.clone(),
11050 Point::new(row, 0)..Point::new(blank_row - 1, 0),
11051 ));
11052 row = next_paragraph_start;
11053 }
11054 ranges.push((
11055 language_settings.clone(),
11056 language_scope.clone(),
11057 Point::new(row, 0)..Point::new(end_row, 0),
11058 ));
11059
11060 ranges
11061 });
11062
11063 let mut edits = Vec::new();
11064 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11065
11066 for (language_settings, language_scope, range) in ranges {
11067 let mut start_row = range.start.row;
11068 let mut end_row = range.end.row;
11069
11070 // Skip selections that overlap with a range that has already been rewrapped.
11071 let selection_range = start_row..end_row;
11072 if rewrapped_row_ranges
11073 .iter()
11074 .any(|range| range.overlaps(&selection_range))
11075 {
11076 continue;
11077 }
11078
11079 let tab_size = language_settings.tab_size;
11080
11081 // Since not all lines in the selection may be at the same indent
11082 // level, choose the indent size that is the most common between all
11083 // of the lines.
11084 //
11085 // If there is a tie, we use the deepest indent.
11086 let (indent_size, indent_end) = {
11087 let mut indent_size_occurrences = HashMap::default();
11088 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
11089
11090 for row in start_row..=end_row {
11091 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11092 rows_by_indent_size.entry(indent).or_default().push(row);
11093 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
11094 }
11095
11096 let indent_size = indent_size_occurrences
11097 .into_iter()
11098 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
11099 .map(|(indent, _)| indent)
11100 .unwrap_or_default();
11101 let row = rows_by_indent_size[&indent_size][0];
11102 let indent_end = Point::new(row, indent_size.len);
11103
11104 (indent_size, indent_end)
11105 };
11106
11107 let mut line_prefix = indent_size.chars().collect::<String>();
11108
11109 let mut inside_comment = false;
11110 if let Some(comment_prefix) = language_scope.and_then(|language| {
11111 language
11112 .line_comment_prefixes()
11113 .iter()
11114 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11115 .cloned()
11116 }) {
11117 line_prefix.push_str(&comment_prefix);
11118 inside_comment = true;
11119 }
11120
11121 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11122 RewrapBehavior::InComments => inside_comment,
11123 RewrapBehavior::InSelections => !range.is_empty(),
11124 RewrapBehavior::Anywhere => true,
11125 };
11126
11127 let should_rewrap = options.override_language_settings
11128 || allow_rewrap_based_on_language
11129 || self.hard_wrap.is_some();
11130 if !should_rewrap {
11131 continue;
11132 }
11133
11134 if range.is_empty() {
11135 'expand_upwards: while start_row > 0 {
11136 let prev_row = start_row - 1;
11137 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11138 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11139 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11140 {
11141 start_row = prev_row;
11142 } else {
11143 break 'expand_upwards;
11144 }
11145 }
11146
11147 'expand_downwards: while end_row < buffer.max_point().row {
11148 let next_row = end_row + 1;
11149 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11150 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11151 && !buffer.is_line_blank(MultiBufferRow(next_row))
11152 {
11153 end_row = next_row;
11154 } else {
11155 break 'expand_downwards;
11156 }
11157 }
11158 }
11159
11160 let start = Point::new(start_row, 0);
11161 let start_offset = start.to_offset(&buffer);
11162 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11163 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11164 let Some(lines_without_prefixes) = selection_text
11165 .lines()
11166 .map(|line| {
11167 line.strip_prefix(&line_prefix)
11168 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
11169 .with_context(|| {
11170 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11171 })
11172 })
11173 .collect::<Result<Vec<_>, _>>()
11174 .log_err()
11175 else {
11176 continue;
11177 };
11178
11179 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11180 buffer
11181 .language_settings_at(Point::new(start_row, 0), cx)
11182 .preferred_line_length as usize
11183 });
11184 let wrapped_text = wrap_with_prefix(
11185 line_prefix,
11186 lines_without_prefixes.join("\n"),
11187 wrap_column,
11188 tab_size,
11189 options.preserve_existing_whitespace,
11190 );
11191
11192 // TODO: should always use char-based diff while still supporting cursor behavior that
11193 // matches vim.
11194 let mut diff_options = DiffOptions::default();
11195 if options.override_language_settings {
11196 diff_options.max_word_diff_len = 0;
11197 diff_options.max_word_diff_line_count = 0;
11198 } else {
11199 diff_options.max_word_diff_len = usize::MAX;
11200 diff_options.max_word_diff_line_count = usize::MAX;
11201 }
11202
11203 for (old_range, new_text) in
11204 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11205 {
11206 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11207 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11208 edits.push((edit_start..edit_end, new_text));
11209 }
11210
11211 rewrapped_row_ranges.push(start_row..=end_row);
11212 }
11213
11214 self.buffer
11215 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11216 }
11217
11218 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11219 let mut text = String::new();
11220 let buffer = self.buffer.read(cx).snapshot(cx);
11221 let mut selections = self.selections.all::<Point>(cx);
11222 let mut clipboard_selections = Vec::with_capacity(selections.len());
11223 {
11224 let max_point = buffer.max_point();
11225 let mut is_first = true;
11226 for selection in &mut selections {
11227 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11228 if is_entire_line {
11229 selection.start = Point::new(selection.start.row, 0);
11230 if !selection.is_empty() && selection.end.column == 0 {
11231 selection.end = cmp::min(max_point, selection.end);
11232 } else {
11233 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11234 }
11235 selection.goal = SelectionGoal::None;
11236 }
11237 if is_first {
11238 is_first = false;
11239 } else {
11240 text += "\n";
11241 }
11242 let mut len = 0;
11243 for chunk in buffer.text_for_range(selection.start..selection.end) {
11244 text.push_str(chunk);
11245 len += chunk.len();
11246 }
11247 clipboard_selections.push(ClipboardSelection {
11248 len,
11249 is_entire_line,
11250 first_line_indent: buffer
11251 .indent_size_for_line(MultiBufferRow(selection.start.row))
11252 .len,
11253 });
11254 }
11255 }
11256
11257 self.transact(window, cx, |this, window, cx| {
11258 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11259 s.select(selections);
11260 });
11261 this.insert("", window, cx);
11262 });
11263 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11264 }
11265
11266 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11267 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11268 let item = self.cut_common(window, cx);
11269 cx.write_to_clipboard(item);
11270 }
11271
11272 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11273 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11274 self.change_selections(None, window, cx, |s| {
11275 s.move_with(|snapshot, sel| {
11276 if sel.is_empty() {
11277 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11278 }
11279 });
11280 });
11281 let item = self.cut_common(window, cx);
11282 cx.set_global(KillRing(item))
11283 }
11284
11285 pub fn kill_ring_yank(
11286 &mut self,
11287 _: &KillRingYank,
11288 window: &mut Window,
11289 cx: &mut Context<Self>,
11290 ) {
11291 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11292 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11293 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11294 (kill_ring.text().to_string(), kill_ring.metadata_json())
11295 } else {
11296 return;
11297 }
11298 } else {
11299 return;
11300 };
11301 self.do_paste(&text, metadata, false, window, cx);
11302 }
11303
11304 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11305 self.do_copy(true, cx);
11306 }
11307
11308 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11309 self.do_copy(false, cx);
11310 }
11311
11312 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11313 let selections = self.selections.all::<Point>(cx);
11314 let buffer = self.buffer.read(cx).read(cx);
11315 let mut text = String::new();
11316
11317 let mut clipboard_selections = Vec::with_capacity(selections.len());
11318 {
11319 let max_point = buffer.max_point();
11320 let mut is_first = true;
11321 for selection in &selections {
11322 let mut start = selection.start;
11323 let mut end = selection.end;
11324 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11325 if is_entire_line {
11326 start = Point::new(start.row, 0);
11327 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11328 }
11329
11330 let mut trimmed_selections = Vec::new();
11331 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11332 let row = MultiBufferRow(start.row);
11333 let first_indent = buffer.indent_size_for_line(row);
11334 if first_indent.len == 0 || start.column > first_indent.len {
11335 trimmed_selections.push(start..end);
11336 } else {
11337 trimmed_selections.push(
11338 Point::new(row.0, first_indent.len)
11339 ..Point::new(row.0, buffer.line_len(row)),
11340 );
11341 for row in start.row + 1..=end.row {
11342 let mut line_len = buffer.line_len(MultiBufferRow(row));
11343 if row == end.row {
11344 line_len = end.column;
11345 }
11346 if line_len == 0 {
11347 trimmed_selections
11348 .push(Point::new(row, 0)..Point::new(row, line_len));
11349 continue;
11350 }
11351 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11352 if row_indent_size.len >= first_indent.len {
11353 trimmed_selections.push(
11354 Point::new(row, first_indent.len)..Point::new(row, line_len),
11355 );
11356 } else {
11357 trimmed_selections.clear();
11358 trimmed_selections.push(start..end);
11359 break;
11360 }
11361 }
11362 }
11363 } else {
11364 trimmed_selections.push(start..end);
11365 }
11366
11367 for trimmed_range in trimmed_selections {
11368 if is_first {
11369 is_first = false;
11370 } else {
11371 text += "\n";
11372 }
11373 let mut len = 0;
11374 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11375 text.push_str(chunk);
11376 len += chunk.len();
11377 }
11378 clipboard_selections.push(ClipboardSelection {
11379 len,
11380 is_entire_line,
11381 first_line_indent: buffer
11382 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11383 .len,
11384 });
11385 }
11386 }
11387 }
11388
11389 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11390 text,
11391 clipboard_selections,
11392 ));
11393 }
11394
11395 pub fn do_paste(
11396 &mut self,
11397 text: &String,
11398 clipboard_selections: Option<Vec<ClipboardSelection>>,
11399 handle_entire_lines: bool,
11400 window: &mut Window,
11401 cx: &mut Context<Self>,
11402 ) {
11403 if self.read_only(cx) {
11404 return;
11405 }
11406
11407 let clipboard_text = Cow::Borrowed(text);
11408
11409 self.transact(window, cx, |this, window, cx| {
11410 if let Some(mut clipboard_selections) = clipboard_selections {
11411 let old_selections = this.selections.all::<usize>(cx);
11412 let all_selections_were_entire_line =
11413 clipboard_selections.iter().all(|s| s.is_entire_line);
11414 let first_selection_indent_column =
11415 clipboard_selections.first().map(|s| s.first_line_indent);
11416 if clipboard_selections.len() != old_selections.len() {
11417 clipboard_selections.drain(..);
11418 }
11419 let cursor_offset = this.selections.last::<usize>(cx).head();
11420 let mut auto_indent_on_paste = true;
11421
11422 this.buffer.update(cx, |buffer, cx| {
11423 let snapshot = buffer.read(cx);
11424 auto_indent_on_paste = snapshot
11425 .language_settings_at(cursor_offset, cx)
11426 .auto_indent_on_paste;
11427
11428 let mut start_offset = 0;
11429 let mut edits = Vec::new();
11430 let mut original_indent_columns = Vec::new();
11431 for (ix, selection) in old_selections.iter().enumerate() {
11432 let to_insert;
11433 let entire_line;
11434 let original_indent_column;
11435 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11436 let end_offset = start_offset + clipboard_selection.len;
11437 to_insert = &clipboard_text[start_offset..end_offset];
11438 entire_line = clipboard_selection.is_entire_line;
11439 start_offset = end_offset + 1;
11440 original_indent_column = Some(clipboard_selection.first_line_indent);
11441 } else {
11442 to_insert = clipboard_text.as_str();
11443 entire_line = all_selections_were_entire_line;
11444 original_indent_column = first_selection_indent_column
11445 }
11446
11447 // If the corresponding selection was empty when this slice of the
11448 // clipboard text was written, then the entire line containing the
11449 // selection was copied. If this selection is also currently empty,
11450 // then paste the line before the current line of the buffer.
11451 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11452 let column = selection.start.to_point(&snapshot).column as usize;
11453 let line_start = selection.start - column;
11454 line_start..line_start
11455 } else {
11456 selection.range()
11457 };
11458
11459 edits.push((range, to_insert));
11460 original_indent_columns.push(original_indent_column);
11461 }
11462 drop(snapshot);
11463
11464 buffer.edit(
11465 edits,
11466 if auto_indent_on_paste {
11467 Some(AutoindentMode::Block {
11468 original_indent_columns,
11469 })
11470 } else {
11471 None
11472 },
11473 cx,
11474 );
11475 });
11476
11477 let selections = this.selections.all::<usize>(cx);
11478 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11479 s.select(selections)
11480 });
11481 } else {
11482 this.insert(&clipboard_text, window, cx);
11483 }
11484 });
11485 }
11486
11487 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11488 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11489 if let Some(item) = cx.read_from_clipboard() {
11490 let entries = item.entries();
11491
11492 match entries.first() {
11493 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11494 // of all the pasted entries.
11495 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11496 .do_paste(
11497 clipboard_string.text(),
11498 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11499 true,
11500 window,
11501 cx,
11502 ),
11503 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11504 }
11505 }
11506 }
11507
11508 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11509 if self.read_only(cx) {
11510 return;
11511 }
11512
11513 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11514
11515 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11516 if let Some((selections, _)) =
11517 self.selection_history.transaction(transaction_id).cloned()
11518 {
11519 self.change_selections(None, window, cx, |s| {
11520 s.select_anchors(selections.to_vec());
11521 });
11522 } else {
11523 log::error!(
11524 "No entry in selection_history found for undo. \
11525 This may correspond to a bug where undo does not update the selection. \
11526 If this is occurring, please add details to \
11527 https://github.com/zed-industries/zed/issues/22692"
11528 );
11529 }
11530 self.request_autoscroll(Autoscroll::fit(), cx);
11531 self.unmark_text(window, cx);
11532 self.refresh_inline_completion(true, false, window, cx);
11533 cx.emit(EditorEvent::Edited { transaction_id });
11534 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11535 }
11536 }
11537
11538 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11539 if self.read_only(cx) {
11540 return;
11541 }
11542
11543 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11544
11545 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11546 if let Some((_, Some(selections))) =
11547 self.selection_history.transaction(transaction_id).cloned()
11548 {
11549 self.change_selections(None, window, cx, |s| {
11550 s.select_anchors(selections.to_vec());
11551 });
11552 } else {
11553 log::error!(
11554 "No entry in selection_history found for redo. \
11555 This may correspond to a bug where undo does not update the selection. \
11556 If this is occurring, please add details to \
11557 https://github.com/zed-industries/zed/issues/22692"
11558 );
11559 }
11560 self.request_autoscroll(Autoscroll::fit(), cx);
11561 self.unmark_text(window, cx);
11562 self.refresh_inline_completion(true, false, window, cx);
11563 cx.emit(EditorEvent::Edited { transaction_id });
11564 }
11565 }
11566
11567 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11568 self.buffer
11569 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11570 }
11571
11572 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11573 self.buffer
11574 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11575 }
11576
11577 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11578 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11579 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11580 s.move_with(|map, selection| {
11581 let cursor = if selection.is_empty() {
11582 movement::left(map, selection.start)
11583 } else {
11584 selection.start
11585 };
11586 selection.collapse_to(cursor, SelectionGoal::None);
11587 });
11588 })
11589 }
11590
11591 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11592 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11593 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11594 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11595 })
11596 }
11597
11598 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11599 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11600 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11601 s.move_with(|map, selection| {
11602 let cursor = if selection.is_empty() {
11603 movement::right(map, selection.end)
11604 } else {
11605 selection.end
11606 };
11607 selection.collapse_to(cursor, SelectionGoal::None)
11608 });
11609 })
11610 }
11611
11612 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11613 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11614 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11615 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11616 })
11617 }
11618
11619 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11620 if self.take_rename(true, window, cx).is_some() {
11621 return;
11622 }
11623
11624 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11625 cx.propagate();
11626 return;
11627 }
11628
11629 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11630
11631 let text_layout_details = &self.text_layout_details(window);
11632 let selection_count = self.selections.count();
11633 let first_selection = self.selections.first_anchor();
11634
11635 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11636 s.move_with(|map, selection| {
11637 if !selection.is_empty() {
11638 selection.goal = SelectionGoal::None;
11639 }
11640 let (cursor, goal) = movement::up(
11641 map,
11642 selection.start,
11643 selection.goal,
11644 false,
11645 text_layout_details,
11646 );
11647 selection.collapse_to(cursor, goal);
11648 });
11649 });
11650
11651 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11652 {
11653 cx.propagate();
11654 }
11655 }
11656
11657 pub fn move_up_by_lines(
11658 &mut self,
11659 action: &MoveUpByLines,
11660 window: &mut Window,
11661 cx: &mut Context<Self>,
11662 ) {
11663 if self.take_rename(true, window, cx).is_some() {
11664 return;
11665 }
11666
11667 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11668 cx.propagate();
11669 return;
11670 }
11671
11672 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11673
11674 let text_layout_details = &self.text_layout_details(window);
11675
11676 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11677 s.move_with(|map, selection| {
11678 if !selection.is_empty() {
11679 selection.goal = SelectionGoal::None;
11680 }
11681 let (cursor, goal) = movement::up_by_rows(
11682 map,
11683 selection.start,
11684 action.lines,
11685 selection.goal,
11686 false,
11687 text_layout_details,
11688 );
11689 selection.collapse_to(cursor, goal);
11690 });
11691 })
11692 }
11693
11694 pub fn move_down_by_lines(
11695 &mut self,
11696 action: &MoveDownByLines,
11697 window: &mut Window,
11698 cx: &mut Context<Self>,
11699 ) {
11700 if self.take_rename(true, window, cx).is_some() {
11701 return;
11702 }
11703
11704 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11705 cx.propagate();
11706 return;
11707 }
11708
11709 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11710
11711 let text_layout_details = &self.text_layout_details(window);
11712
11713 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11714 s.move_with(|map, selection| {
11715 if !selection.is_empty() {
11716 selection.goal = SelectionGoal::None;
11717 }
11718 let (cursor, goal) = movement::down_by_rows(
11719 map,
11720 selection.start,
11721 action.lines,
11722 selection.goal,
11723 false,
11724 text_layout_details,
11725 );
11726 selection.collapse_to(cursor, goal);
11727 });
11728 })
11729 }
11730
11731 pub fn select_down_by_lines(
11732 &mut self,
11733 action: &SelectDownByLines,
11734 window: &mut Window,
11735 cx: &mut Context<Self>,
11736 ) {
11737 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11738 let text_layout_details = &self.text_layout_details(window);
11739 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11740 s.move_heads_with(|map, head, goal| {
11741 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11742 })
11743 })
11744 }
11745
11746 pub fn select_up_by_lines(
11747 &mut self,
11748 action: &SelectUpByLines,
11749 window: &mut Window,
11750 cx: &mut Context<Self>,
11751 ) {
11752 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11753 let text_layout_details = &self.text_layout_details(window);
11754 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11755 s.move_heads_with(|map, head, goal| {
11756 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11757 })
11758 })
11759 }
11760
11761 pub fn select_page_up(
11762 &mut self,
11763 _: &SelectPageUp,
11764 window: &mut Window,
11765 cx: &mut Context<Self>,
11766 ) {
11767 let Some(row_count) = self.visible_row_count() else {
11768 return;
11769 };
11770
11771 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11772
11773 let text_layout_details = &self.text_layout_details(window);
11774
11775 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11776 s.move_heads_with(|map, head, goal| {
11777 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11778 })
11779 })
11780 }
11781
11782 pub fn move_page_up(
11783 &mut self,
11784 action: &MovePageUp,
11785 window: &mut Window,
11786 cx: &mut Context<Self>,
11787 ) {
11788 if self.take_rename(true, window, cx).is_some() {
11789 return;
11790 }
11791
11792 if self
11793 .context_menu
11794 .borrow_mut()
11795 .as_mut()
11796 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
11797 .unwrap_or(false)
11798 {
11799 return;
11800 }
11801
11802 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11803 cx.propagate();
11804 return;
11805 }
11806
11807 let Some(row_count) = self.visible_row_count() else {
11808 return;
11809 };
11810
11811 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11812
11813 let autoscroll = if action.center_cursor {
11814 Autoscroll::center()
11815 } else {
11816 Autoscroll::fit()
11817 };
11818
11819 let text_layout_details = &self.text_layout_details(window);
11820
11821 self.change_selections(Some(autoscroll), window, cx, |s| {
11822 s.move_with(|map, selection| {
11823 if !selection.is_empty() {
11824 selection.goal = SelectionGoal::None;
11825 }
11826 let (cursor, goal) = movement::up_by_rows(
11827 map,
11828 selection.end,
11829 row_count,
11830 selection.goal,
11831 false,
11832 text_layout_details,
11833 );
11834 selection.collapse_to(cursor, goal);
11835 });
11836 });
11837 }
11838
11839 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11840 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11841 let text_layout_details = &self.text_layout_details(window);
11842 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11843 s.move_heads_with(|map, head, goal| {
11844 movement::up(map, head, goal, false, text_layout_details)
11845 })
11846 })
11847 }
11848
11849 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11850 self.take_rename(true, window, cx);
11851
11852 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11853 cx.propagate();
11854 return;
11855 }
11856
11857 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11858
11859 let text_layout_details = &self.text_layout_details(window);
11860 let selection_count = self.selections.count();
11861 let first_selection = self.selections.first_anchor();
11862
11863 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11864 s.move_with(|map, selection| {
11865 if !selection.is_empty() {
11866 selection.goal = SelectionGoal::None;
11867 }
11868 let (cursor, goal) = movement::down(
11869 map,
11870 selection.end,
11871 selection.goal,
11872 false,
11873 text_layout_details,
11874 );
11875 selection.collapse_to(cursor, goal);
11876 });
11877 });
11878
11879 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11880 {
11881 cx.propagate();
11882 }
11883 }
11884
11885 pub fn select_page_down(
11886 &mut self,
11887 _: &SelectPageDown,
11888 window: &mut Window,
11889 cx: &mut Context<Self>,
11890 ) {
11891 let Some(row_count) = self.visible_row_count() else {
11892 return;
11893 };
11894
11895 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11896
11897 let text_layout_details = &self.text_layout_details(window);
11898
11899 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11900 s.move_heads_with(|map, head, goal| {
11901 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11902 })
11903 })
11904 }
11905
11906 pub fn move_page_down(
11907 &mut self,
11908 action: &MovePageDown,
11909 window: &mut Window,
11910 cx: &mut Context<Self>,
11911 ) {
11912 if self.take_rename(true, window, cx).is_some() {
11913 return;
11914 }
11915
11916 if self
11917 .context_menu
11918 .borrow_mut()
11919 .as_mut()
11920 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
11921 .unwrap_or(false)
11922 {
11923 return;
11924 }
11925
11926 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11927 cx.propagate();
11928 return;
11929 }
11930
11931 let Some(row_count) = self.visible_row_count() else {
11932 return;
11933 };
11934
11935 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11936
11937 let autoscroll = if action.center_cursor {
11938 Autoscroll::center()
11939 } else {
11940 Autoscroll::fit()
11941 };
11942
11943 let text_layout_details = &self.text_layout_details(window);
11944 self.change_selections(Some(autoscroll), window, cx, |s| {
11945 s.move_with(|map, selection| {
11946 if !selection.is_empty() {
11947 selection.goal = SelectionGoal::None;
11948 }
11949 let (cursor, goal) = movement::down_by_rows(
11950 map,
11951 selection.end,
11952 row_count,
11953 selection.goal,
11954 false,
11955 text_layout_details,
11956 );
11957 selection.collapse_to(cursor, goal);
11958 });
11959 });
11960 }
11961
11962 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11963 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11964 let text_layout_details = &self.text_layout_details(window);
11965 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11966 s.move_heads_with(|map, head, goal| {
11967 movement::down(map, head, goal, false, text_layout_details)
11968 })
11969 });
11970 }
11971
11972 pub fn context_menu_first(
11973 &mut self,
11974 _: &ContextMenuFirst,
11975 window: &mut Window,
11976 cx: &mut Context<Self>,
11977 ) {
11978 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11979 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
11980 }
11981 }
11982
11983 pub fn context_menu_prev(
11984 &mut self,
11985 _: &ContextMenuPrevious,
11986 window: &mut Window,
11987 cx: &mut Context<Self>,
11988 ) {
11989 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11990 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
11991 }
11992 }
11993
11994 pub fn context_menu_next(
11995 &mut self,
11996 _: &ContextMenuNext,
11997 window: &mut Window,
11998 cx: &mut Context<Self>,
11999 ) {
12000 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12001 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12002 }
12003 }
12004
12005 pub fn context_menu_last(
12006 &mut self,
12007 _: &ContextMenuLast,
12008 window: &mut Window,
12009 cx: &mut Context<Self>,
12010 ) {
12011 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12012 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12013 }
12014 }
12015
12016 pub fn move_to_previous_word_start(
12017 &mut self,
12018 _: &MoveToPreviousWordStart,
12019 window: &mut Window,
12020 cx: &mut Context<Self>,
12021 ) {
12022 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12023 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12024 s.move_cursors_with(|map, head, _| {
12025 (
12026 movement::previous_word_start(map, head),
12027 SelectionGoal::None,
12028 )
12029 });
12030 })
12031 }
12032
12033 pub fn move_to_previous_subword_start(
12034 &mut self,
12035 _: &MoveToPreviousSubwordStart,
12036 window: &mut Window,
12037 cx: &mut Context<Self>,
12038 ) {
12039 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12040 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12041 s.move_cursors_with(|map, head, _| {
12042 (
12043 movement::previous_subword_start(map, head),
12044 SelectionGoal::None,
12045 )
12046 });
12047 })
12048 }
12049
12050 pub fn select_to_previous_word_start(
12051 &mut self,
12052 _: &SelectToPreviousWordStart,
12053 window: &mut Window,
12054 cx: &mut Context<Self>,
12055 ) {
12056 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12057 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12058 s.move_heads_with(|map, head, _| {
12059 (
12060 movement::previous_word_start(map, head),
12061 SelectionGoal::None,
12062 )
12063 });
12064 })
12065 }
12066
12067 pub fn select_to_previous_subword_start(
12068 &mut self,
12069 _: &SelectToPreviousSubwordStart,
12070 window: &mut Window,
12071 cx: &mut Context<Self>,
12072 ) {
12073 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12074 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12075 s.move_heads_with(|map, head, _| {
12076 (
12077 movement::previous_subword_start(map, head),
12078 SelectionGoal::None,
12079 )
12080 });
12081 })
12082 }
12083
12084 pub fn delete_to_previous_word_start(
12085 &mut self,
12086 action: &DeleteToPreviousWordStart,
12087 window: &mut Window,
12088 cx: &mut Context<Self>,
12089 ) {
12090 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12091 self.transact(window, cx, |this, window, cx| {
12092 this.select_autoclose_pair(window, cx);
12093 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12094 s.move_with(|map, selection| {
12095 if selection.is_empty() {
12096 let cursor = if action.ignore_newlines {
12097 movement::previous_word_start(map, selection.head())
12098 } else {
12099 movement::previous_word_start_or_newline(map, selection.head())
12100 };
12101 selection.set_head(cursor, SelectionGoal::None);
12102 }
12103 });
12104 });
12105 this.insert("", window, cx);
12106 });
12107 }
12108
12109 pub fn delete_to_previous_subword_start(
12110 &mut self,
12111 _: &DeleteToPreviousSubwordStart,
12112 window: &mut Window,
12113 cx: &mut Context<Self>,
12114 ) {
12115 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12116 self.transact(window, cx, |this, window, cx| {
12117 this.select_autoclose_pair(window, cx);
12118 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12119 s.move_with(|map, selection| {
12120 if selection.is_empty() {
12121 let cursor = movement::previous_subword_start(map, selection.head());
12122 selection.set_head(cursor, SelectionGoal::None);
12123 }
12124 });
12125 });
12126 this.insert("", window, cx);
12127 });
12128 }
12129
12130 pub fn move_to_next_word_end(
12131 &mut self,
12132 _: &MoveToNextWordEnd,
12133 window: &mut Window,
12134 cx: &mut Context<Self>,
12135 ) {
12136 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12137 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12138 s.move_cursors_with(|map, head, _| {
12139 (movement::next_word_end(map, head), SelectionGoal::None)
12140 });
12141 })
12142 }
12143
12144 pub fn move_to_next_subword_end(
12145 &mut self,
12146 _: &MoveToNextSubwordEnd,
12147 window: &mut Window,
12148 cx: &mut Context<Self>,
12149 ) {
12150 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12151 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12152 s.move_cursors_with(|map, head, _| {
12153 (movement::next_subword_end(map, head), SelectionGoal::None)
12154 });
12155 })
12156 }
12157
12158 pub fn select_to_next_word_end(
12159 &mut self,
12160 _: &SelectToNextWordEnd,
12161 window: &mut Window,
12162 cx: &mut Context<Self>,
12163 ) {
12164 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12165 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12166 s.move_heads_with(|map, head, _| {
12167 (movement::next_word_end(map, head), SelectionGoal::None)
12168 });
12169 })
12170 }
12171
12172 pub fn select_to_next_subword_end(
12173 &mut self,
12174 _: &SelectToNextSubwordEnd,
12175 window: &mut Window,
12176 cx: &mut Context<Self>,
12177 ) {
12178 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12179 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12180 s.move_heads_with(|map, head, _| {
12181 (movement::next_subword_end(map, head), SelectionGoal::None)
12182 });
12183 })
12184 }
12185
12186 pub fn delete_to_next_word_end(
12187 &mut self,
12188 action: &DeleteToNextWordEnd,
12189 window: &mut Window,
12190 cx: &mut Context<Self>,
12191 ) {
12192 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12193 self.transact(window, cx, |this, window, cx| {
12194 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12195 s.move_with(|map, selection| {
12196 if selection.is_empty() {
12197 let cursor = if action.ignore_newlines {
12198 movement::next_word_end(map, selection.head())
12199 } else {
12200 movement::next_word_end_or_newline(map, selection.head())
12201 };
12202 selection.set_head(cursor, SelectionGoal::None);
12203 }
12204 });
12205 });
12206 this.insert("", window, cx);
12207 });
12208 }
12209
12210 pub fn delete_to_next_subword_end(
12211 &mut self,
12212 _: &DeleteToNextSubwordEnd,
12213 window: &mut Window,
12214 cx: &mut Context<Self>,
12215 ) {
12216 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12217 self.transact(window, cx, |this, window, cx| {
12218 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12219 s.move_with(|map, selection| {
12220 if selection.is_empty() {
12221 let cursor = movement::next_subword_end(map, selection.head());
12222 selection.set_head(cursor, SelectionGoal::None);
12223 }
12224 });
12225 });
12226 this.insert("", window, cx);
12227 });
12228 }
12229
12230 pub fn move_to_beginning_of_line(
12231 &mut self,
12232 action: &MoveToBeginningOfLine,
12233 window: &mut Window,
12234 cx: &mut Context<Self>,
12235 ) {
12236 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12237 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12238 s.move_cursors_with(|map, head, _| {
12239 (
12240 movement::indented_line_beginning(
12241 map,
12242 head,
12243 action.stop_at_soft_wraps,
12244 action.stop_at_indent,
12245 ),
12246 SelectionGoal::None,
12247 )
12248 });
12249 })
12250 }
12251
12252 pub fn select_to_beginning_of_line(
12253 &mut self,
12254 action: &SelectToBeginningOfLine,
12255 window: &mut Window,
12256 cx: &mut Context<Self>,
12257 ) {
12258 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12259 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12260 s.move_heads_with(|map, head, _| {
12261 (
12262 movement::indented_line_beginning(
12263 map,
12264 head,
12265 action.stop_at_soft_wraps,
12266 action.stop_at_indent,
12267 ),
12268 SelectionGoal::None,
12269 )
12270 });
12271 });
12272 }
12273
12274 pub fn delete_to_beginning_of_line(
12275 &mut self,
12276 action: &DeleteToBeginningOfLine,
12277 window: &mut Window,
12278 cx: &mut Context<Self>,
12279 ) {
12280 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12281 self.transact(window, cx, |this, window, cx| {
12282 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12283 s.move_with(|_, selection| {
12284 selection.reversed = true;
12285 });
12286 });
12287
12288 this.select_to_beginning_of_line(
12289 &SelectToBeginningOfLine {
12290 stop_at_soft_wraps: false,
12291 stop_at_indent: action.stop_at_indent,
12292 },
12293 window,
12294 cx,
12295 );
12296 this.backspace(&Backspace, window, cx);
12297 });
12298 }
12299
12300 pub fn move_to_end_of_line(
12301 &mut self,
12302 action: &MoveToEndOfLine,
12303 window: &mut Window,
12304 cx: &mut Context<Self>,
12305 ) {
12306 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12307 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12308 s.move_cursors_with(|map, head, _| {
12309 (
12310 movement::line_end(map, head, action.stop_at_soft_wraps),
12311 SelectionGoal::None,
12312 )
12313 });
12314 })
12315 }
12316
12317 pub fn select_to_end_of_line(
12318 &mut self,
12319 action: &SelectToEndOfLine,
12320 window: &mut Window,
12321 cx: &mut Context<Self>,
12322 ) {
12323 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12324 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12325 s.move_heads_with(|map, head, _| {
12326 (
12327 movement::line_end(map, head, action.stop_at_soft_wraps),
12328 SelectionGoal::None,
12329 )
12330 });
12331 })
12332 }
12333
12334 pub fn delete_to_end_of_line(
12335 &mut self,
12336 _: &DeleteToEndOfLine,
12337 window: &mut Window,
12338 cx: &mut Context<Self>,
12339 ) {
12340 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12341 self.transact(window, cx, |this, window, cx| {
12342 this.select_to_end_of_line(
12343 &SelectToEndOfLine {
12344 stop_at_soft_wraps: false,
12345 },
12346 window,
12347 cx,
12348 );
12349 this.delete(&Delete, window, cx);
12350 });
12351 }
12352
12353 pub fn cut_to_end_of_line(
12354 &mut self,
12355 _: &CutToEndOfLine,
12356 window: &mut Window,
12357 cx: &mut Context<Self>,
12358 ) {
12359 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12360 self.transact(window, cx, |this, window, cx| {
12361 this.select_to_end_of_line(
12362 &SelectToEndOfLine {
12363 stop_at_soft_wraps: false,
12364 },
12365 window,
12366 cx,
12367 );
12368 this.cut(&Cut, window, cx);
12369 });
12370 }
12371
12372 pub fn move_to_start_of_paragraph(
12373 &mut self,
12374 _: &MoveToStartOfParagraph,
12375 window: &mut Window,
12376 cx: &mut Context<Self>,
12377 ) {
12378 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12379 cx.propagate();
12380 return;
12381 }
12382 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12383 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12384 s.move_with(|map, selection| {
12385 selection.collapse_to(
12386 movement::start_of_paragraph(map, selection.head(), 1),
12387 SelectionGoal::None,
12388 )
12389 });
12390 })
12391 }
12392
12393 pub fn move_to_end_of_paragraph(
12394 &mut self,
12395 _: &MoveToEndOfParagraph,
12396 window: &mut Window,
12397 cx: &mut Context<Self>,
12398 ) {
12399 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12400 cx.propagate();
12401 return;
12402 }
12403 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12404 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12405 s.move_with(|map, selection| {
12406 selection.collapse_to(
12407 movement::end_of_paragraph(map, selection.head(), 1),
12408 SelectionGoal::None,
12409 )
12410 });
12411 })
12412 }
12413
12414 pub fn select_to_start_of_paragraph(
12415 &mut self,
12416 _: &SelectToStartOfParagraph,
12417 window: &mut Window,
12418 cx: &mut Context<Self>,
12419 ) {
12420 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12421 cx.propagate();
12422 return;
12423 }
12424 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12425 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12426 s.move_heads_with(|map, head, _| {
12427 (
12428 movement::start_of_paragraph(map, head, 1),
12429 SelectionGoal::None,
12430 )
12431 });
12432 })
12433 }
12434
12435 pub fn select_to_end_of_paragraph(
12436 &mut self,
12437 _: &SelectToEndOfParagraph,
12438 window: &mut Window,
12439 cx: &mut Context<Self>,
12440 ) {
12441 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12442 cx.propagate();
12443 return;
12444 }
12445 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12446 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12447 s.move_heads_with(|map, head, _| {
12448 (
12449 movement::end_of_paragraph(map, head, 1),
12450 SelectionGoal::None,
12451 )
12452 });
12453 })
12454 }
12455
12456 pub fn move_to_start_of_excerpt(
12457 &mut self,
12458 _: &MoveToStartOfExcerpt,
12459 window: &mut Window,
12460 cx: &mut Context<Self>,
12461 ) {
12462 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12463 cx.propagate();
12464 return;
12465 }
12466 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12467 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12468 s.move_with(|map, selection| {
12469 selection.collapse_to(
12470 movement::start_of_excerpt(
12471 map,
12472 selection.head(),
12473 workspace::searchable::Direction::Prev,
12474 ),
12475 SelectionGoal::None,
12476 )
12477 });
12478 })
12479 }
12480
12481 pub fn move_to_start_of_next_excerpt(
12482 &mut self,
12483 _: &MoveToStartOfNextExcerpt,
12484 window: &mut Window,
12485 cx: &mut Context<Self>,
12486 ) {
12487 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12488 cx.propagate();
12489 return;
12490 }
12491
12492 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12493 s.move_with(|map, selection| {
12494 selection.collapse_to(
12495 movement::start_of_excerpt(
12496 map,
12497 selection.head(),
12498 workspace::searchable::Direction::Next,
12499 ),
12500 SelectionGoal::None,
12501 )
12502 });
12503 })
12504 }
12505
12506 pub fn move_to_end_of_excerpt(
12507 &mut self,
12508 _: &MoveToEndOfExcerpt,
12509 window: &mut Window,
12510 cx: &mut Context<Self>,
12511 ) {
12512 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12513 cx.propagate();
12514 return;
12515 }
12516 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12517 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12518 s.move_with(|map, selection| {
12519 selection.collapse_to(
12520 movement::end_of_excerpt(
12521 map,
12522 selection.head(),
12523 workspace::searchable::Direction::Next,
12524 ),
12525 SelectionGoal::None,
12526 )
12527 });
12528 })
12529 }
12530
12531 pub fn move_to_end_of_previous_excerpt(
12532 &mut self,
12533 _: &MoveToEndOfPreviousExcerpt,
12534 window: &mut Window,
12535 cx: &mut Context<Self>,
12536 ) {
12537 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12538 cx.propagate();
12539 return;
12540 }
12541 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12542 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12543 s.move_with(|map, selection| {
12544 selection.collapse_to(
12545 movement::end_of_excerpt(
12546 map,
12547 selection.head(),
12548 workspace::searchable::Direction::Prev,
12549 ),
12550 SelectionGoal::None,
12551 )
12552 });
12553 })
12554 }
12555
12556 pub fn select_to_start_of_excerpt(
12557 &mut self,
12558 _: &SelectToStartOfExcerpt,
12559 window: &mut Window,
12560 cx: &mut Context<Self>,
12561 ) {
12562 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12563 cx.propagate();
12564 return;
12565 }
12566 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12567 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12568 s.move_heads_with(|map, head, _| {
12569 (
12570 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12571 SelectionGoal::None,
12572 )
12573 });
12574 })
12575 }
12576
12577 pub fn select_to_start_of_next_excerpt(
12578 &mut self,
12579 _: &SelectToStartOfNextExcerpt,
12580 window: &mut Window,
12581 cx: &mut Context<Self>,
12582 ) {
12583 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12584 cx.propagate();
12585 return;
12586 }
12587 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12588 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12589 s.move_heads_with(|map, head, _| {
12590 (
12591 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12592 SelectionGoal::None,
12593 )
12594 });
12595 })
12596 }
12597
12598 pub fn select_to_end_of_excerpt(
12599 &mut self,
12600 _: &SelectToEndOfExcerpt,
12601 window: &mut Window,
12602 cx: &mut Context<Self>,
12603 ) {
12604 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12605 cx.propagate();
12606 return;
12607 }
12608 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12609 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12610 s.move_heads_with(|map, head, _| {
12611 (
12612 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12613 SelectionGoal::None,
12614 )
12615 });
12616 })
12617 }
12618
12619 pub fn select_to_end_of_previous_excerpt(
12620 &mut self,
12621 _: &SelectToEndOfPreviousExcerpt,
12622 window: &mut Window,
12623 cx: &mut Context<Self>,
12624 ) {
12625 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12626 cx.propagate();
12627 return;
12628 }
12629 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12630 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12631 s.move_heads_with(|map, head, _| {
12632 (
12633 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12634 SelectionGoal::None,
12635 )
12636 });
12637 })
12638 }
12639
12640 pub fn move_to_beginning(
12641 &mut self,
12642 _: &MoveToBeginning,
12643 window: &mut Window,
12644 cx: &mut Context<Self>,
12645 ) {
12646 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12647 cx.propagate();
12648 return;
12649 }
12650 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12651 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12652 s.select_ranges(vec![0..0]);
12653 });
12654 }
12655
12656 pub fn select_to_beginning(
12657 &mut self,
12658 _: &SelectToBeginning,
12659 window: &mut Window,
12660 cx: &mut Context<Self>,
12661 ) {
12662 let mut selection = self.selections.last::<Point>(cx);
12663 selection.set_head(Point::zero(), SelectionGoal::None);
12664 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12665 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12666 s.select(vec![selection]);
12667 });
12668 }
12669
12670 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12671 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12672 cx.propagate();
12673 return;
12674 }
12675 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12676 let cursor = self.buffer.read(cx).read(cx).len();
12677 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12678 s.select_ranges(vec![cursor..cursor])
12679 });
12680 }
12681
12682 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12683 self.nav_history = nav_history;
12684 }
12685
12686 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12687 self.nav_history.as_ref()
12688 }
12689
12690 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12691 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12692 }
12693
12694 fn push_to_nav_history(
12695 &mut self,
12696 cursor_anchor: Anchor,
12697 new_position: Option<Point>,
12698 is_deactivate: bool,
12699 cx: &mut Context<Self>,
12700 ) {
12701 if let Some(nav_history) = self.nav_history.as_mut() {
12702 let buffer = self.buffer.read(cx).read(cx);
12703 let cursor_position = cursor_anchor.to_point(&buffer);
12704 let scroll_state = self.scroll_manager.anchor();
12705 let scroll_top_row = scroll_state.top_row(&buffer);
12706 drop(buffer);
12707
12708 if let Some(new_position) = new_position {
12709 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12710 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12711 return;
12712 }
12713 }
12714
12715 nav_history.push(
12716 Some(NavigationData {
12717 cursor_anchor,
12718 cursor_position,
12719 scroll_anchor: scroll_state,
12720 scroll_top_row,
12721 }),
12722 cx,
12723 );
12724 cx.emit(EditorEvent::PushedToNavHistory {
12725 anchor: cursor_anchor,
12726 is_deactivate,
12727 })
12728 }
12729 }
12730
12731 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12732 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12733 let buffer = self.buffer.read(cx).snapshot(cx);
12734 let mut selection = self.selections.first::<usize>(cx);
12735 selection.set_head(buffer.len(), SelectionGoal::None);
12736 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12737 s.select(vec![selection]);
12738 });
12739 }
12740
12741 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12742 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12743 let end = self.buffer.read(cx).read(cx).len();
12744 self.change_selections(None, window, cx, |s| {
12745 s.select_ranges(vec![0..end]);
12746 });
12747 }
12748
12749 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12750 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12751 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12752 let mut selections = self.selections.all::<Point>(cx);
12753 let max_point = display_map.buffer_snapshot.max_point();
12754 for selection in &mut selections {
12755 let rows = selection.spanned_rows(true, &display_map);
12756 selection.start = Point::new(rows.start.0, 0);
12757 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12758 selection.reversed = false;
12759 }
12760 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12761 s.select(selections);
12762 });
12763 }
12764
12765 pub fn split_selection_into_lines(
12766 &mut self,
12767 _: &SplitSelectionIntoLines,
12768 window: &mut Window,
12769 cx: &mut Context<Self>,
12770 ) {
12771 let selections = self
12772 .selections
12773 .all::<Point>(cx)
12774 .into_iter()
12775 .map(|selection| selection.start..selection.end)
12776 .collect::<Vec<_>>();
12777 self.unfold_ranges(&selections, true, true, cx);
12778
12779 let mut new_selection_ranges = Vec::new();
12780 {
12781 let buffer = self.buffer.read(cx).read(cx);
12782 for selection in selections {
12783 for row in selection.start.row..selection.end.row {
12784 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12785 new_selection_ranges.push(cursor..cursor);
12786 }
12787
12788 let is_multiline_selection = selection.start.row != selection.end.row;
12789 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12790 // so this action feels more ergonomic when paired with other selection operations
12791 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12792 if !should_skip_last {
12793 new_selection_ranges.push(selection.end..selection.end);
12794 }
12795 }
12796 }
12797 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12798 s.select_ranges(new_selection_ranges);
12799 });
12800 }
12801
12802 pub fn add_selection_above(
12803 &mut self,
12804 _: &AddSelectionAbove,
12805 window: &mut Window,
12806 cx: &mut Context<Self>,
12807 ) {
12808 self.add_selection(true, window, cx);
12809 }
12810
12811 pub fn add_selection_below(
12812 &mut self,
12813 _: &AddSelectionBelow,
12814 window: &mut Window,
12815 cx: &mut Context<Self>,
12816 ) {
12817 self.add_selection(false, window, cx);
12818 }
12819
12820 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12821 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12822
12823 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12824 let all_selections = self.selections.all::<Point>(cx);
12825 let text_layout_details = self.text_layout_details(window);
12826
12827 let (mut columnar_selections, new_selections_to_columnarize) = {
12828 if let Some(state) = self.add_selections_state.as_ref() {
12829 let columnar_selection_ids: HashSet<_> = state
12830 .groups
12831 .iter()
12832 .flat_map(|group| group.stack.iter())
12833 .copied()
12834 .collect();
12835
12836 all_selections
12837 .into_iter()
12838 .partition(|s| columnar_selection_ids.contains(&s.id))
12839 } else {
12840 (Vec::new(), all_selections)
12841 }
12842 };
12843
12844 let mut state = self
12845 .add_selections_state
12846 .take()
12847 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
12848
12849 for selection in new_selections_to_columnarize {
12850 let range = selection.display_range(&display_map).sorted();
12851 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12852 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12853 let positions = start_x.min(end_x)..start_x.max(end_x);
12854 let mut stack = Vec::new();
12855 for row in range.start.row().0..=range.end.row().0 {
12856 if let Some(selection) = self.selections.build_columnar_selection(
12857 &display_map,
12858 DisplayRow(row),
12859 &positions,
12860 selection.reversed,
12861 &text_layout_details,
12862 ) {
12863 stack.push(selection.id);
12864 columnar_selections.push(selection);
12865 }
12866 }
12867 if !stack.is_empty() {
12868 if above {
12869 stack.reverse();
12870 }
12871 state.groups.push(AddSelectionsGroup { above, stack });
12872 }
12873 }
12874
12875 let mut final_selections = Vec::new();
12876 let end_row = if above {
12877 DisplayRow(0)
12878 } else {
12879 display_map.max_point().row()
12880 };
12881
12882 let mut last_added_item_per_group = HashMap::default();
12883 for group in state.groups.iter_mut() {
12884 if let Some(last_id) = group.stack.last() {
12885 last_added_item_per_group.insert(*last_id, group);
12886 }
12887 }
12888
12889 for selection in columnar_selections {
12890 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
12891 if above == group.above {
12892 let range = selection.display_range(&display_map).sorted();
12893 debug_assert_eq!(range.start.row(), range.end.row());
12894 let mut row = range.start.row();
12895 let positions =
12896 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12897 px(start)..px(end)
12898 } else {
12899 let start_x =
12900 display_map.x_for_display_point(range.start, &text_layout_details);
12901 let end_x =
12902 display_map.x_for_display_point(range.end, &text_layout_details);
12903 start_x.min(end_x)..start_x.max(end_x)
12904 };
12905
12906 let mut maybe_new_selection = None;
12907 while row != end_row {
12908 if above {
12909 row.0 -= 1;
12910 } else {
12911 row.0 += 1;
12912 }
12913 if let Some(new_selection) = self.selections.build_columnar_selection(
12914 &display_map,
12915 row,
12916 &positions,
12917 selection.reversed,
12918 &text_layout_details,
12919 ) {
12920 maybe_new_selection = Some(new_selection);
12921 break;
12922 }
12923 }
12924
12925 if let Some(new_selection) = maybe_new_selection {
12926 group.stack.push(new_selection.id);
12927 if above {
12928 final_selections.push(new_selection);
12929 final_selections.push(selection);
12930 } else {
12931 final_selections.push(selection);
12932 final_selections.push(new_selection);
12933 }
12934 } else {
12935 final_selections.push(selection);
12936 }
12937 } else {
12938 group.stack.pop();
12939 }
12940 } else {
12941 final_selections.push(selection);
12942 }
12943 }
12944
12945 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12946 s.select(final_selections);
12947 });
12948
12949 let final_selection_ids: HashSet<_> = self
12950 .selections
12951 .all::<Point>(cx)
12952 .iter()
12953 .map(|s| s.id)
12954 .collect();
12955 state.groups.retain_mut(|group| {
12956 // selections might get merged above so we remove invalid items from stacks
12957 group.stack.retain(|id| final_selection_ids.contains(id));
12958
12959 // single selection in stack can be treated as initial state
12960 group.stack.len() > 1
12961 });
12962
12963 if !state.groups.is_empty() {
12964 self.add_selections_state = Some(state);
12965 }
12966 }
12967
12968 fn select_match_ranges(
12969 &mut self,
12970 range: Range<usize>,
12971 reversed: bool,
12972 replace_newest: bool,
12973 auto_scroll: Option<Autoscroll>,
12974 window: &mut Window,
12975 cx: &mut Context<Editor>,
12976 ) {
12977 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12978 self.change_selections(auto_scroll, window, cx, |s| {
12979 if replace_newest {
12980 s.delete(s.newest_anchor().id);
12981 }
12982 if reversed {
12983 s.insert_range(range.end..range.start);
12984 } else {
12985 s.insert_range(range);
12986 }
12987 });
12988 }
12989
12990 pub fn select_next_match_internal(
12991 &mut self,
12992 display_map: &DisplaySnapshot,
12993 replace_newest: bool,
12994 autoscroll: Option<Autoscroll>,
12995 window: &mut Window,
12996 cx: &mut Context<Self>,
12997 ) -> Result<()> {
12998 let buffer = &display_map.buffer_snapshot;
12999 let mut selections = self.selections.all::<usize>(cx);
13000 if let Some(mut select_next_state) = self.select_next_state.take() {
13001 let query = &select_next_state.query;
13002 if !select_next_state.done {
13003 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13004 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13005 let mut next_selected_range = None;
13006
13007 let bytes_after_last_selection =
13008 buffer.bytes_in_range(last_selection.end..buffer.len());
13009 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13010 let query_matches = query
13011 .stream_find_iter(bytes_after_last_selection)
13012 .map(|result| (last_selection.end, result))
13013 .chain(
13014 query
13015 .stream_find_iter(bytes_before_first_selection)
13016 .map(|result| (0, result)),
13017 );
13018
13019 for (start_offset, query_match) in query_matches {
13020 let query_match = query_match.unwrap(); // can only fail due to I/O
13021 let offset_range =
13022 start_offset + query_match.start()..start_offset + query_match.end();
13023 let display_range = offset_range.start.to_display_point(display_map)
13024 ..offset_range.end.to_display_point(display_map);
13025
13026 if !select_next_state.wordwise
13027 || (!movement::is_inside_word(display_map, display_range.start)
13028 && !movement::is_inside_word(display_map, display_range.end))
13029 {
13030 // TODO: This is n^2, because we might check all the selections
13031 if !selections
13032 .iter()
13033 .any(|selection| selection.range().overlaps(&offset_range))
13034 {
13035 next_selected_range = Some(offset_range);
13036 break;
13037 }
13038 }
13039 }
13040
13041 if let Some(next_selected_range) = next_selected_range {
13042 self.select_match_ranges(
13043 next_selected_range,
13044 last_selection.reversed,
13045 replace_newest,
13046 autoscroll,
13047 window,
13048 cx,
13049 );
13050 } else {
13051 select_next_state.done = true;
13052 }
13053 }
13054
13055 self.select_next_state = Some(select_next_state);
13056 } else {
13057 let mut only_carets = true;
13058 let mut same_text_selected = true;
13059 let mut selected_text = None;
13060
13061 let mut selections_iter = selections.iter().peekable();
13062 while let Some(selection) = selections_iter.next() {
13063 if selection.start != selection.end {
13064 only_carets = false;
13065 }
13066
13067 if same_text_selected {
13068 if selected_text.is_none() {
13069 selected_text =
13070 Some(buffer.text_for_range(selection.range()).collect::<String>());
13071 }
13072
13073 if let Some(next_selection) = selections_iter.peek() {
13074 if next_selection.range().len() == selection.range().len() {
13075 let next_selected_text = buffer
13076 .text_for_range(next_selection.range())
13077 .collect::<String>();
13078 if Some(next_selected_text) != selected_text {
13079 same_text_selected = false;
13080 selected_text = None;
13081 }
13082 } else {
13083 same_text_selected = false;
13084 selected_text = None;
13085 }
13086 }
13087 }
13088 }
13089
13090 if only_carets {
13091 for selection in &mut selections {
13092 let word_range = movement::surrounding_word(
13093 display_map,
13094 selection.start.to_display_point(display_map),
13095 );
13096 selection.start = word_range.start.to_offset(display_map, Bias::Left);
13097 selection.end = word_range.end.to_offset(display_map, Bias::Left);
13098 selection.goal = SelectionGoal::None;
13099 selection.reversed = false;
13100 self.select_match_ranges(
13101 selection.start..selection.end,
13102 selection.reversed,
13103 replace_newest,
13104 autoscroll,
13105 window,
13106 cx,
13107 );
13108 }
13109
13110 if selections.len() == 1 {
13111 let selection = selections
13112 .last()
13113 .expect("ensured that there's only one selection");
13114 let query = buffer
13115 .text_for_range(selection.start..selection.end)
13116 .collect::<String>();
13117 let is_empty = query.is_empty();
13118 let select_state = SelectNextState {
13119 query: AhoCorasick::new(&[query])?,
13120 wordwise: true,
13121 done: is_empty,
13122 };
13123 self.select_next_state = Some(select_state);
13124 } else {
13125 self.select_next_state = None;
13126 }
13127 } else if let Some(selected_text) = selected_text {
13128 self.select_next_state = Some(SelectNextState {
13129 query: AhoCorasick::new(&[selected_text])?,
13130 wordwise: false,
13131 done: false,
13132 });
13133 self.select_next_match_internal(
13134 display_map,
13135 replace_newest,
13136 autoscroll,
13137 window,
13138 cx,
13139 )?;
13140 }
13141 }
13142 Ok(())
13143 }
13144
13145 pub fn select_all_matches(
13146 &mut self,
13147 _action: &SelectAllMatches,
13148 window: &mut Window,
13149 cx: &mut Context<Self>,
13150 ) -> Result<()> {
13151 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13152
13153 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13154
13155 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13156 let Some(select_next_state) = self.select_next_state.as_mut() else {
13157 return Ok(());
13158 };
13159 if select_next_state.done {
13160 return Ok(());
13161 }
13162
13163 let mut new_selections = Vec::new();
13164
13165 let reversed = self.selections.oldest::<usize>(cx).reversed;
13166 let buffer = &display_map.buffer_snapshot;
13167 let query_matches = select_next_state
13168 .query
13169 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13170
13171 for query_match in query_matches.into_iter() {
13172 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13173 let offset_range = if reversed {
13174 query_match.end()..query_match.start()
13175 } else {
13176 query_match.start()..query_match.end()
13177 };
13178 let display_range = offset_range.start.to_display_point(&display_map)
13179 ..offset_range.end.to_display_point(&display_map);
13180
13181 if !select_next_state.wordwise
13182 || (!movement::is_inside_word(&display_map, display_range.start)
13183 && !movement::is_inside_word(&display_map, display_range.end))
13184 {
13185 new_selections.push(offset_range.start..offset_range.end);
13186 }
13187 }
13188
13189 select_next_state.done = true;
13190 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13191 self.change_selections(None, window, cx, |selections| {
13192 selections.select_ranges(new_selections)
13193 });
13194
13195 Ok(())
13196 }
13197
13198 pub fn select_next(
13199 &mut self,
13200 action: &SelectNext,
13201 window: &mut Window,
13202 cx: &mut Context<Self>,
13203 ) -> Result<()> {
13204 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13205 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13206 self.select_next_match_internal(
13207 &display_map,
13208 action.replace_newest,
13209 Some(Autoscroll::newest()),
13210 window,
13211 cx,
13212 )?;
13213 Ok(())
13214 }
13215
13216 pub fn select_previous(
13217 &mut self,
13218 action: &SelectPrevious,
13219 window: &mut Window,
13220 cx: &mut Context<Self>,
13221 ) -> Result<()> {
13222 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13223 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13224 let buffer = &display_map.buffer_snapshot;
13225 let mut selections = self.selections.all::<usize>(cx);
13226 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13227 let query = &select_prev_state.query;
13228 if !select_prev_state.done {
13229 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13230 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13231 let mut next_selected_range = None;
13232 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13233 let bytes_before_last_selection =
13234 buffer.reversed_bytes_in_range(0..last_selection.start);
13235 let bytes_after_first_selection =
13236 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13237 let query_matches = query
13238 .stream_find_iter(bytes_before_last_selection)
13239 .map(|result| (last_selection.start, result))
13240 .chain(
13241 query
13242 .stream_find_iter(bytes_after_first_selection)
13243 .map(|result| (buffer.len(), result)),
13244 );
13245 for (end_offset, query_match) in query_matches {
13246 let query_match = query_match.unwrap(); // can only fail due to I/O
13247 let offset_range =
13248 end_offset - query_match.end()..end_offset - query_match.start();
13249 let display_range = offset_range.start.to_display_point(&display_map)
13250 ..offset_range.end.to_display_point(&display_map);
13251
13252 if !select_prev_state.wordwise
13253 || (!movement::is_inside_word(&display_map, display_range.start)
13254 && !movement::is_inside_word(&display_map, display_range.end))
13255 {
13256 next_selected_range = Some(offset_range);
13257 break;
13258 }
13259 }
13260
13261 if let Some(next_selected_range) = next_selected_range {
13262 self.select_match_ranges(
13263 next_selected_range,
13264 last_selection.reversed,
13265 action.replace_newest,
13266 Some(Autoscroll::newest()),
13267 window,
13268 cx,
13269 );
13270 } else {
13271 select_prev_state.done = true;
13272 }
13273 }
13274
13275 self.select_prev_state = Some(select_prev_state);
13276 } else {
13277 let mut only_carets = true;
13278 let mut same_text_selected = true;
13279 let mut selected_text = None;
13280
13281 let mut selections_iter = selections.iter().peekable();
13282 while let Some(selection) = selections_iter.next() {
13283 if selection.start != selection.end {
13284 only_carets = false;
13285 }
13286
13287 if same_text_selected {
13288 if selected_text.is_none() {
13289 selected_text =
13290 Some(buffer.text_for_range(selection.range()).collect::<String>());
13291 }
13292
13293 if let Some(next_selection) = selections_iter.peek() {
13294 if next_selection.range().len() == selection.range().len() {
13295 let next_selected_text = buffer
13296 .text_for_range(next_selection.range())
13297 .collect::<String>();
13298 if Some(next_selected_text) != selected_text {
13299 same_text_selected = false;
13300 selected_text = None;
13301 }
13302 } else {
13303 same_text_selected = false;
13304 selected_text = None;
13305 }
13306 }
13307 }
13308 }
13309
13310 if only_carets {
13311 for selection in &mut selections {
13312 let word_range = movement::surrounding_word(
13313 &display_map,
13314 selection.start.to_display_point(&display_map),
13315 );
13316 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
13317 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
13318 selection.goal = SelectionGoal::None;
13319 selection.reversed = false;
13320 self.select_match_ranges(
13321 selection.start..selection.end,
13322 selection.reversed,
13323 action.replace_newest,
13324 Some(Autoscroll::newest()),
13325 window,
13326 cx,
13327 );
13328 }
13329 if selections.len() == 1 {
13330 let selection = selections
13331 .last()
13332 .expect("ensured that there's only one selection");
13333 let query = buffer
13334 .text_for_range(selection.start..selection.end)
13335 .collect::<String>();
13336 let is_empty = query.is_empty();
13337 let select_state = SelectNextState {
13338 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13339 wordwise: true,
13340 done: is_empty,
13341 };
13342 self.select_prev_state = Some(select_state);
13343 } else {
13344 self.select_prev_state = None;
13345 }
13346 } else if let Some(selected_text) = selected_text {
13347 self.select_prev_state = Some(SelectNextState {
13348 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13349 wordwise: false,
13350 done: false,
13351 });
13352 self.select_previous(action, window, cx)?;
13353 }
13354 }
13355 Ok(())
13356 }
13357
13358 pub fn find_next_match(
13359 &mut self,
13360 _: &FindNextMatch,
13361 window: &mut Window,
13362 cx: &mut Context<Self>,
13363 ) -> Result<()> {
13364 let selections = self.selections.disjoint_anchors();
13365 match selections.first() {
13366 Some(first) if selections.len() >= 2 => {
13367 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13368 s.select_ranges([first.range()]);
13369 });
13370 }
13371 _ => self.select_next(
13372 &SelectNext {
13373 replace_newest: true,
13374 },
13375 window,
13376 cx,
13377 )?,
13378 }
13379 Ok(())
13380 }
13381
13382 pub fn find_previous_match(
13383 &mut self,
13384 _: &FindPreviousMatch,
13385 window: &mut Window,
13386 cx: &mut Context<Self>,
13387 ) -> Result<()> {
13388 let selections = self.selections.disjoint_anchors();
13389 match selections.last() {
13390 Some(last) if selections.len() >= 2 => {
13391 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13392 s.select_ranges([last.range()]);
13393 });
13394 }
13395 _ => self.select_previous(
13396 &SelectPrevious {
13397 replace_newest: true,
13398 },
13399 window,
13400 cx,
13401 )?,
13402 }
13403 Ok(())
13404 }
13405
13406 pub fn toggle_comments(
13407 &mut self,
13408 action: &ToggleComments,
13409 window: &mut Window,
13410 cx: &mut Context<Self>,
13411 ) {
13412 if self.read_only(cx) {
13413 return;
13414 }
13415 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13416 let text_layout_details = &self.text_layout_details(window);
13417 self.transact(window, cx, |this, window, cx| {
13418 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13419 let mut edits = Vec::new();
13420 let mut selection_edit_ranges = Vec::new();
13421 let mut last_toggled_row = None;
13422 let snapshot = this.buffer.read(cx).read(cx);
13423 let empty_str: Arc<str> = Arc::default();
13424 let mut suffixes_inserted = Vec::new();
13425 let ignore_indent = action.ignore_indent;
13426
13427 fn comment_prefix_range(
13428 snapshot: &MultiBufferSnapshot,
13429 row: MultiBufferRow,
13430 comment_prefix: &str,
13431 comment_prefix_whitespace: &str,
13432 ignore_indent: bool,
13433 ) -> Range<Point> {
13434 let indent_size = if ignore_indent {
13435 0
13436 } else {
13437 snapshot.indent_size_for_line(row).len
13438 };
13439
13440 let start = Point::new(row.0, indent_size);
13441
13442 let mut line_bytes = snapshot
13443 .bytes_in_range(start..snapshot.max_point())
13444 .flatten()
13445 .copied();
13446
13447 // If this line currently begins with the line comment prefix, then record
13448 // the range containing the prefix.
13449 if line_bytes
13450 .by_ref()
13451 .take(comment_prefix.len())
13452 .eq(comment_prefix.bytes())
13453 {
13454 // Include any whitespace that matches the comment prefix.
13455 let matching_whitespace_len = line_bytes
13456 .zip(comment_prefix_whitespace.bytes())
13457 .take_while(|(a, b)| a == b)
13458 .count() as u32;
13459 let end = Point::new(
13460 start.row,
13461 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13462 );
13463 start..end
13464 } else {
13465 start..start
13466 }
13467 }
13468
13469 fn comment_suffix_range(
13470 snapshot: &MultiBufferSnapshot,
13471 row: MultiBufferRow,
13472 comment_suffix: &str,
13473 comment_suffix_has_leading_space: bool,
13474 ) -> Range<Point> {
13475 let end = Point::new(row.0, snapshot.line_len(row));
13476 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13477
13478 let mut line_end_bytes = snapshot
13479 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13480 .flatten()
13481 .copied();
13482
13483 let leading_space_len = if suffix_start_column > 0
13484 && line_end_bytes.next() == Some(b' ')
13485 && comment_suffix_has_leading_space
13486 {
13487 1
13488 } else {
13489 0
13490 };
13491
13492 // If this line currently begins with the line comment prefix, then record
13493 // the range containing the prefix.
13494 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13495 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13496 start..end
13497 } else {
13498 end..end
13499 }
13500 }
13501
13502 // TODO: Handle selections that cross excerpts
13503 for selection in &mut selections {
13504 let start_column = snapshot
13505 .indent_size_for_line(MultiBufferRow(selection.start.row))
13506 .len;
13507 let language = if let Some(language) =
13508 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13509 {
13510 language
13511 } else {
13512 continue;
13513 };
13514
13515 selection_edit_ranges.clear();
13516
13517 // If multiple selections contain a given row, avoid processing that
13518 // row more than once.
13519 let mut start_row = MultiBufferRow(selection.start.row);
13520 if last_toggled_row == Some(start_row) {
13521 start_row = start_row.next_row();
13522 }
13523 let end_row =
13524 if selection.end.row > selection.start.row && selection.end.column == 0 {
13525 MultiBufferRow(selection.end.row - 1)
13526 } else {
13527 MultiBufferRow(selection.end.row)
13528 };
13529 last_toggled_row = Some(end_row);
13530
13531 if start_row > end_row {
13532 continue;
13533 }
13534
13535 // If the language has line comments, toggle those.
13536 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13537
13538 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13539 if ignore_indent {
13540 full_comment_prefixes = full_comment_prefixes
13541 .into_iter()
13542 .map(|s| Arc::from(s.trim_end()))
13543 .collect();
13544 }
13545
13546 if !full_comment_prefixes.is_empty() {
13547 let first_prefix = full_comment_prefixes
13548 .first()
13549 .expect("prefixes is non-empty");
13550 let prefix_trimmed_lengths = full_comment_prefixes
13551 .iter()
13552 .map(|p| p.trim_end_matches(' ').len())
13553 .collect::<SmallVec<[usize; 4]>>();
13554
13555 let mut all_selection_lines_are_comments = true;
13556
13557 for row in start_row.0..=end_row.0 {
13558 let row = MultiBufferRow(row);
13559 if start_row < end_row && snapshot.is_line_blank(row) {
13560 continue;
13561 }
13562
13563 let prefix_range = full_comment_prefixes
13564 .iter()
13565 .zip(prefix_trimmed_lengths.iter().copied())
13566 .map(|(prefix, trimmed_prefix_len)| {
13567 comment_prefix_range(
13568 snapshot.deref(),
13569 row,
13570 &prefix[..trimmed_prefix_len],
13571 &prefix[trimmed_prefix_len..],
13572 ignore_indent,
13573 )
13574 })
13575 .max_by_key(|range| range.end.column - range.start.column)
13576 .expect("prefixes is non-empty");
13577
13578 if prefix_range.is_empty() {
13579 all_selection_lines_are_comments = false;
13580 }
13581
13582 selection_edit_ranges.push(prefix_range);
13583 }
13584
13585 if all_selection_lines_are_comments {
13586 edits.extend(
13587 selection_edit_ranges
13588 .iter()
13589 .cloned()
13590 .map(|range| (range, empty_str.clone())),
13591 );
13592 } else {
13593 let min_column = selection_edit_ranges
13594 .iter()
13595 .map(|range| range.start.column)
13596 .min()
13597 .unwrap_or(0);
13598 edits.extend(selection_edit_ranges.iter().map(|range| {
13599 let position = Point::new(range.start.row, min_column);
13600 (position..position, first_prefix.clone())
13601 }));
13602 }
13603 } else if let Some((full_comment_prefix, comment_suffix)) =
13604 language.block_comment_delimiters()
13605 {
13606 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13607 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13608 let prefix_range = comment_prefix_range(
13609 snapshot.deref(),
13610 start_row,
13611 comment_prefix,
13612 comment_prefix_whitespace,
13613 ignore_indent,
13614 );
13615 let suffix_range = comment_suffix_range(
13616 snapshot.deref(),
13617 end_row,
13618 comment_suffix.trim_start_matches(' '),
13619 comment_suffix.starts_with(' '),
13620 );
13621
13622 if prefix_range.is_empty() || suffix_range.is_empty() {
13623 edits.push((
13624 prefix_range.start..prefix_range.start,
13625 full_comment_prefix.clone(),
13626 ));
13627 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13628 suffixes_inserted.push((end_row, comment_suffix.len()));
13629 } else {
13630 edits.push((prefix_range, empty_str.clone()));
13631 edits.push((suffix_range, empty_str.clone()));
13632 }
13633 } else {
13634 continue;
13635 }
13636 }
13637
13638 drop(snapshot);
13639 this.buffer.update(cx, |buffer, cx| {
13640 buffer.edit(edits, None, cx);
13641 });
13642
13643 // Adjust selections so that they end before any comment suffixes that
13644 // were inserted.
13645 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13646 let mut selections = this.selections.all::<Point>(cx);
13647 let snapshot = this.buffer.read(cx).read(cx);
13648 for selection in &mut selections {
13649 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13650 match row.cmp(&MultiBufferRow(selection.end.row)) {
13651 Ordering::Less => {
13652 suffixes_inserted.next();
13653 continue;
13654 }
13655 Ordering::Greater => break,
13656 Ordering::Equal => {
13657 if selection.end.column == snapshot.line_len(row) {
13658 if selection.is_empty() {
13659 selection.start.column -= suffix_len as u32;
13660 }
13661 selection.end.column -= suffix_len as u32;
13662 }
13663 break;
13664 }
13665 }
13666 }
13667 }
13668
13669 drop(snapshot);
13670 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13671 s.select(selections)
13672 });
13673
13674 let selections = this.selections.all::<Point>(cx);
13675 let selections_on_single_row = selections.windows(2).all(|selections| {
13676 selections[0].start.row == selections[1].start.row
13677 && selections[0].end.row == selections[1].end.row
13678 && selections[0].start.row == selections[0].end.row
13679 });
13680 let selections_selecting = selections
13681 .iter()
13682 .any(|selection| selection.start != selection.end);
13683 let advance_downwards = action.advance_downwards
13684 && selections_on_single_row
13685 && !selections_selecting
13686 && !matches!(this.mode, EditorMode::SingleLine { .. });
13687
13688 if advance_downwards {
13689 let snapshot = this.buffer.read(cx).snapshot(cx);
13690
13691 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13692 s.move_cursors_with(|display_snapshot, display_point, _| {
13693 let mut point = display_point.to_point(display_snapshot);
13694 point.row += 1;
13695 point = snapshot.clip_point(point, Bias::Left);
13696 let display_point = point.to_display_point(display_snapshot);
13697 let goal = SelectionGoal::HorizontalPosition(
13698 display_snapshot
13699 .x_for_display_point(display_point, text_layout_details)
13700 .into(),
13701 );
13702 (display_point, goal)
13703 })
13704 });
13705 }
13706 });
13707 }
13708
13709 pub fn select_enclosing_symbol(
13710 &mut self,
13711 _: &SelectEnclosingSymbol,
13712 window: &mut Window,
13713 cx: &mut Context<Self>,
13714 ) {
13715 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13716
13717 let buffer = self.buffer.read(cx).snapshot(cx);
13718 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13719
13720 fn update_selection(
13721 selection: &Selection<usize>,
13722 buffer_snap: &MultiBufferSnapshot,
13723 ) -> Option<Selection<usize>> {
13724 let cursor = selection.head();
13725 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13726 for symbol in symbols.iter().rev() {
13727 let start = symbol.range.start.to_offset(buffer_snap);
13728 let end = symbol.range.end.to_offset(buffer_snap);
13729 let new_range = start..end;
13730 if start < selection.start || end > selection.end {
13731 return Some(Selection {
13732 id: selection.id,
13733 start: new_range.start,
13734 end: new_range.end,
13735 goal: SelectionGoal::None,
13736 reversed: selection.reversed,
13737 });
13738 }
13739 }
13740 None
13741 }
13742
13743 let mut selected_larger_symbol = false;
13744 let new_selections = old_selections
13745 .iter()
13746 .map(|selection| match update_selection(selection, &buffer) {
13747 Some(new_selection) => {
13748 if new_selection.range() != selection.range() {
13749 selected_larger_symbol = true;
13750 }
13751 new_selection
13752 }
13753 None => selection.clone(),
13754 })
13755 .collect::<Vec<_>>();
13756
13757 if selected_larger_symbol {
13758 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13759 s.select(new_selections);
13760 });
13761 }
13762 }
13763
13764 pub fn select_larger_syntax_node(
13765 &mut self,
13766 _: &SelectLargerSyntaxNode,
13767 window: &mut Window,
13768 cx: &mut Context<Self>,
13769 ) {
13770 let Some(visible_row_count) = self.visible_row_count() else {
13771 return;
13772 };
13773 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13774 if old_selections.is_empty() {
13775 return;
13776 }
13777
13778 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13779
13780 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13781 let buffer = self.buffer.read(cx).snapshot(cx);
13782
13783 let mut selected_larger_node = false;
13784 let mut new_selections = old_selections
13785 .iter()
13786 .map(|selection| {
13787 let old_range = selection.start..selection.end;
13788
13789 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13790 // manually select word at selection
13791 if ["string_content", "inline"].contains(&node.kind()) {
13792 let word_range = {
13793 let display_point = buffer
13794 .offset_to_point(old_range.start)
13795 .to_display_point(&display_map);
13796 let Range { start, end } =
13797 movement::surrounding_word(&display_map, display_point);
13798 start.to_point(&display_map).to_offset(&buffer)
13799 ..end.to_point(&display_map).to_offset(&buffer)
13800 };
13801 // ignore if word is already selected
13802 if !word_range.is_empty() && old_range != word_range {
13803 let last_word_range = {
13804 let display_point = buffer
13805 .offset_to_point(old_range.end)
13806 .to_display_point(&display_map);
13807 let Range { start, end } =
13808 movement::surrounding_word(&display_map, display_point);
13809 start.to_point(&display_map).to_offset(&buffer)
13810 ..end.to_point(&display_map).to_offset(&buffer)
13811 };
13812 // only select word if start and end point belongs to same word
13813 if word_range == last_word_range {
13814 selected_larger_node = true;
13815 return Selection {
13816 id: selection.id,
13817 start: word_range.start,
13818 end: word_range.end,
13819 goal: SelectionGoal::None,
13820 reversed: selection.reversed,
13821 };
13822 }
13823 }
13824 }
13825 }
13826
13827 let mut new_range = old_range.clone();
13828 while let Some((_node, containing_range)) =
13829 buffer.syntax_ancestor(new_range.clone())
13830 {
13831 new_range = match containing_range {
13832 MultiOrSingleBufferOffsetRange::Single(_) => break,
13833 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13834 };
13835 if !display_map.intersects_fold(new_range.start)
13836 && !display_map.intersects_fold(new_range.end)
13837 {
13838 break;
13839 }
13840 }
13841
13842 selected_larger_node |= new_range != old_range;
13843 Selection {
13844 id: selection.id,
13845 start: new_range.start,
13846 end: new_range.end,
13847 goal: SelectionGoal::None,
13848 reversed: selection.reversed,
13849 }
13850 })
13851 .collect::<Vec<_>>();
13852
13853 if !selected_larger_node {
13854 return; // don't put this call in the history
13855 }
13856
13857 // scroll based on transformation done to the last selection created by the user
13858 let (last_old, last_new) = old_selections
13859 .last()
13860 .zip(new_selections.last().cloned())
13861 .expect("old_selections isn't empty");
13862
13863 // revert selection
13864 let is_selection_reversed = {
13865 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13866 new_selections.last_mut().expect("checked above").reversed =
13867 should_newest_selection_be_reversed;
13868 should_newest_selection_be_reversed
13869 };
13870
13871 if selected_larger_node {
13872 self.select_syntax_node_history.disable_clearing = true;
13873 self.change_selections(None, window, cx, |s| {
13874 s.select(new_selections.clone());
13875 });
13876 self.select_syntax_node_history.disable_clearing = false;
13877 }
13878
13879 let start_row = last_new.start.to_display_point(&display_map).row().0;
13880 let end_row = last_new.end.to_display_point(&display_map).row().0;
13881 let selection_height = end_row - start_row + 1;
13882 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13883
13884 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13885 let scroll_behavior = if fits_on_the_screen {
13886 self.request_autoscroll(Autoscroll::fit(), cx);
13887 SelectSyntaxNodeScrollBehavior::FitSelection
13888 } else if is_selection_reversed {
13889 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13890 SelectSyntaxNodeScrollBehavior::CursorTop
13891 } else {
13892 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13893 SelectSyntaxNodeScrollBehavior::CursorBottom
13894 };
13895
13896 self.select_syntax_node_history.push((
13897 old_selections,
13898 scroll_behavior,
13899 is_selection_reversed,
13900 ));
13901 }
13902
13903 pub fn select_smaller_syntax_node(
13904 &mut self,
13905 _: &SelectSmallerSyntaxNode,
13906 window: &mut Window,
13907 cx: &mut Context<Self>,
13908 ) {
13909 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13910
13911 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13912 self.select_syntax_node_history.pop()
13913 {
13914 if let Some(selection) = selections.last_mut() {
13915 selection.reversed = is_selection_reversed;
13916 }
13917
13918 self.select_syntax_node_history.disable_clearing = true;
13919 self.change_selections(None, window, cx, |s| {
13920 s.select(selections.to_vec());
13921 });
13922 self.select_syntax_node_history.disable_clearing = false;
13923
13924 match scroll_behavior {
13925 SelectSyntaxNodeScrollBehavior::CursorTop => {
13926 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13927 }
13928 SelectSyntaxNodeScrollBehavior::FitSelection => {
13929 self.request_autoscroll(Autoscroll::fit(), cx);
13930 }
13931 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13932 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13933 }
13934 }
13935 }
13936 }
13937
13938 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13939 if !EditorSettings::get_global(cx).gutter.runnables {
13940 self.clear_tasks();
13941 return Task::ready(());
13942 }
13943 let project = self.project.as_ref().map(Entity::downgrade);
13944 let task_sources = self.lsp_task_sources(cx);
13945 let multi_buffer = self.buffer.downgrade();
13946 cx.spawn_in(window, async move |editor, cx| {
13947 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13948 let Some(project) = project.and_then(|p| p.upgrade()) else {
13949 return;
13950 };
13951 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13952 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13953 }) else {
13954 return;
13955 };
13956
13957 let hide_runnables = project
13958 .update(cx, |project, cx| {
13959 // Do not display any test indicators in non-dev server remote projects.
13960 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13961 })
13962 .unwrap_or(true);
13963 if hide_runnables {
13964 return;
13965 }
13966 let new_rows =
13967 cx.background_spawn({
13968 let snapshot = display_snapshot.clone();
13969 async move {
13970 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13971 }
13972 })
13973 .await;
13974 let Ok(lsp_tasks) =
13975 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13976 else {
13977 return;
13978 };
13979 let lsp_tasks = lsp_tasks.await;
13980
13981 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13982 lsp_tasks
13983 .into_iter()
13984 .flat_map(|(kind, tasks)| {
13985 tasks.into_iter().filter_map(move |(location, task)| {
13986 Some((kind.clone(), location?, task))
13987 })
13988 })
13989 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13990 let buffer = location.target.buffer;
13991 let buffer_snapshot = buffer.read(cx).snapshot();
13992 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13993 |(excerpt_id, snapshot, _)| {
13994 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13995 display_snapshot
13996 .buffer_snapshot
13997 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13998 } else {
13999 None
14000 }
14001 },
14002 );
14003 if let Some(offset) = offset {
14004 let task_buffer_range =
14005 location.target.range.to_point(&buffer_snapshot);
14006 let context_buffer_range =
14007 task_buffer_range.to_offset(&buffer_snapshot);
14008 let context_range = BufferOffset(context_buffer_range.start)
14009 ..BufferOffset(context_buffer_range.end);
14010
14011 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14012 .or_insert_with(|| RunnableTasks {
14013 templates: Vec::new(),
14014 offset,
14015 column: task_buffer_range.start.column,
14016 extra_variables: HashMap::default(),
14017 context_range,
14018 })
14019 .templates
14020 .push((kind, task.original_task().clone()));
14021 }
14022
14023 acc
14024 })
14025 }) else {
14026 return;
14027 };
14028
14029 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14030 buffer.language_settings(cx).tasks.prefer_lsp
14031 }) else {
14032 return;
14033 };
14034
14035 let rows = Self::runnable_rows(
14036 project,
14037 display_snapshot,
14038 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14039 new_rows,
14040 cx.clone(),
14041 )
14042 .await;
14043 editor
14044 .update(cx, |editor, _| {
14045 editor.clear_tasks();
14046 for (key, mut value) in rows {
14047 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14048 value.templates.extend(lsp_tasks.templates);
14049 }
14050
14051 editor.insert_tasks(key, value);
14052 }
14053 for (key, value) in lsp_tasks_by_rows {
14054 editor.insert_tasks(key, value);
14055 }
14056 })
14057 .ok();
14058 })
14059 }
14060 fn fetch_runnable_ranges(
14061 snapshot: &DisplaySnapshot,
14062 range: Range<Anchor>,
14063 ) -> Vec<language::RunnableRange> {
14064 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14065 }
14066
14067 fn runnable_rows(
14068 project: Entity<Project>,
14069 snapshot: DisplaySnapshot,
14070 prefer_lsp: bool,
14071 runnable_ranges: Vec<RunnableRange>,
14072 cx: AsyncWindowContext,
14073 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14074 cx.spawn(async move |cx| {
14075 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14076 for mut runnable in runnable_ranges {
14077 let Some(tasks) = cx
14078 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14079 .ok()
14080 else {
14081 continue;
14082 };
14083 let mut tasks = tasks.await;
14084
14085 if prefer_lsp {
14086 tasks.retain(|(task_kind, _)| {
14087 !matches!(task_kind, TaskSourceKind::Language { .. })
14088 });
14089 }
14090 if tasks.is_empty() {
14091 continue;
14092 }
14093
14094 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14095 let Some(row) = snapshot
14096 .buffer_snapshot
14097 .buffer_line_for_row(MultiBufferRow(point.row))
14098 .map(|(_, range)| range.start.row)
14099 else {
14100 continue;
14101 };
14102
14103 let context_range =
14104 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14105 runnable_rows.push((
14106 (runnable.buffer_id, row),
14107 RunnableTasks {
14108 templates: tasks,
14109 offset: snapshot
14110 .buffer_snapshot
14111 .anchor_before(runnable.run_range.start),
14112 context_range,
14113 column: point.column,
14114 extra_variables: runnable.extra_captures,
14115 },
14116 ));
14117 }
14118 runnable_rows
14119 })
14120 }
14121
14122 fn templates_with_tags(
14123 project: &Entity<Project>,
14124 runnable: &mut Runnable,
14125 cx: &mut App,
14126 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14127 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14128 let (worktree_id, file) = project
14129 .buffer_for_id(runnable.buffer, cx)
14130 .and_then(|buffer| buffer.read(cx).file())
14131 .map(|file| (file.worktree_id(cx), file.clone()))
14132 .unzip();
14133
14134 (
14135 project.task_store().read(cx).task_inventory().cloned(),
14136 worktree_id,
14137 file,
14138 )
14139 });
14140
14141 let tags = mem::take(&mut runnable.tags);
14142 let language = runnable.language.clone();
14143 cx.spawn(async move |cx| {
14144 let mut templates_with_tags = Vec::new();
14145 if let Some(inventory) = inventory {
14146 for RunnableTag(tag) in tags {
14147 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14148 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14149 }) else {
14150 return templates_with_tags;
14151 };
14152 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14153 move |(_, template)| {
14154 template.tags.iter().any(|source_tag| source_tag == &tag)
14155 },
14156 ));
14157 }
14158 }
14159 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14160
14161 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14162 // Strongest source wins; if we have worktree tag binding, prefer that to
14163 // global and language bindings;
14164 // if we have a global binding, prefer that to language binding.
14165 let first_mismatch = templates_with_tags
14166 .iter()
14167 .position(|(tag_source, _)| tag_source != leading_tag_source);
14168 if let Some(index) = first_mismatch {
14169 templates_with_tags.truncate(index);
14170 }
14171 }
14172
14173 templates_with_tags
14174 })
14175 }
14176
14177 pub fn move_to_enclosing_bracket(
14178 &mut self,
14179 _: &MoveToEnclosingBracket,
14180 window: &mut Window,
14181 cx: &mut Context<Self>,
14182 ) {
14183 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14184 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14185 s.move_offsets_with(|snapshot, selection| {
14186 let Some(enclosing_bracket_ranges) =
14187 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14188 else {
14189 return;
14190 };
14191
14192 let mut best_length = usize::MAX;
14193 let mut best_inside = false;
14194 let mut best_in_bracket_range = false;
14195 let mut best_destination = None;
14196 for (open, close) in enclosing_bracket_ranges {
14197 let close = close.to_inclusive();
14198 let length = close.end() - open.start;
14199 let inside = selection.start >= open.end && selection.end <= *close.start();
14200 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14201 || close.contains(&selection.head());
14202
14203 // If best is next to a bracket and current isn't, skip
14204 if !in_bracket_range && best_in_bracket_range {
14205 continue;
14206 }
14207
14208 // Prefer smaller lengths unless best is inside and current isn't
14209 if length > best_length && (best_inside || !inside) {
14210 continue;
14211 }
14212
14213 best_length = length;
14214 best_inside = inside;
14215 best_in_bracket_range = in_bracket_range;
14216 best_destination = Some(
14217 if close.contains(&selection.start) && close.contains(&selection.end) {
14218 if inside { open.end } else { open.start }
14219 } else if inside {
14220 *close.start()
14221 } else {
14222 *close.end()
14223 },
14224 );
14225 }
14226
14227 if let Some(destination) = best_destination {
14228 selection.collapse_to(destination, SelectionGoal::None);
14229 }
14230 })
14231 });
14232 }
14233
14234 pub fn undo_selection(
14235 &mut self,
14236 _: &UndoSelection,
14237 window: &mut Window,
14238 cx: &mut Context<Self>,
14239 ) {
14240 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14241 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14242 self.selection_history.mode = SelectionHistoryMode::Undoing;
14243 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14244 this.end_selection(window, cx);
14245 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14246 s.select_anchors(entry.selections.to_vec())
14247 });
14248 });
14249 self.selection_history.mode = SelectionHistoryMode::Normal;
14250
14251 self.select_next_state = entry.select_next_state;
14252 self.select_prev_state = entry.select_prev_state;
14253 self.add_selections_state = entry.add_selections_state;
14254 }
14255 }
14256
14257 pub fn redo_selection(
14258 &mut self,
14259 _: &RedoSelection,
14260 window: &mut Window,
14261 cx: &mut Context<Self>,
14262 ) {
14263 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14264 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14265 self.selection_history.mode = SelectionHistoryMode::Redoing;
14266 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14267 this.end_selection(window, cx);
14268 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14269 s.select_anchors(entry.selections.to_vec())
14270 });
14271 });
14272 self.selection_history.mode = SelectionHistoryMode::Normal;
14273
14274 self.select_next_state = entry.select_next_state;
14275 self.select_prev_state = entry.select_prev_state;
14276 self.add_selections_state = entry.add_selections_state;
14277 }
14278 }
14279
14280 pub fn expand_excerpts(
14281 &mut self,
14282 action: &ExpandExcerpts,
14283 _: &mut Window,
14284 cx: &mut Context<Self>,
14285 ) {
14286 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14287 }
14288
14289 pub fn expand_excerpts_down(
14290 &mut self,
14291 action: &ExpandExcerptsDown,
14292 _: &mut Window,
14293 cx: &mut Context<Self>,
14294 ) {
14295 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14296 }
14297
14298 pub fn expand_excerpts_up(
14299 &mut self,
14300 action: &ExpandExcerptsUp,
14301 _: &mut Window,
14302 cx: &mut Context<Self>,
14303 ) {
14304 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14305 }
14306
14307 pub fn expand_excerpts_for_direction(
14308 &mut self,
14309 lines: u32,
14310 direction: ExpandExcerptDirection,
14311
14312 cx: &mut Context<Self>,
14313 ) {
14314 let selections = self.selections.disjoint_anchors();
14315
14316 let lines = if lines == 0 {
14317 EditorSettings::get_global(cx).expand_excerpt_lines
14318 } else {
14319 lines
14320 };
14321
14322 self.buffer.update(cx, |buffer, cx| {
14323 let snapshot = buffer.snapshot(cx);
14324 let mut excerpt_ids = selections
14325 .iter()
14326 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14327 .collect::<Vec<_>>();
14328 excerpt_ids.sort();
14329 excerpt_ids.dedup();
14330 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14331 })
14332 }
14333
14334 pub fn expand_excerpt(
14335 &mut self,
14336 excerpt: ExcerptId,
14337 direction: ExpandExcerptDirection,
14338 window: &mut Window,
14339 cx: &mut Context<Self>,
14340 ) {
14341 let current_scroll_position = self.scroll_position(cx);
14342 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14343 let mut should_scroll_up = false;
14344
14345 if direction == ExpandExcerptDirection::Down {
14346 let multi_buffer = self.buffer.read(cx);
14347 let snapshot = multi_buffer.snapshot(cx);
14348 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14349 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14350 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14351 let buffer_snapshot = buffer.read(cx).snapshot();
14352 let excerpt_end_row =
14353 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14354 let last_row = buffer_snapshot.max_point().row;
14355 let lines_below = last_row.saturating_sub(excerpt_end_row);
14356 should_scroll_up = lines_below >= lines_to_expand;
14357 }
14358 }
14359 }
14360 }
14361
14362 self.buffer.update(cx, |buffer, cx| {
14363 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14364 });
14365
14366 if should_scroll_up {
14367 let new_scroll_position =
14368 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14369 self.set_scroll_position(new_scroll_position, window, cx);
14370 }
14371 }
14372
14373 pub fn go_to_singleton_buffer_point(
14374 &mut self,
14375 point: Point,
14376 window: &mut Window,
14377 cx: &mut Context<Self>,
14378 ) {
14379 self.go_to_singleton_buffer_range(point..point, window, cx);
14380 }
14381
14382 pub fn go_to_singleton_buffer_range(
14383 &mut self,
14384 range: Range<Point>,
14385 window: &mut Window,
14386 cx: &mut Context<Self>,
14387 ) {
14388 let multibuffer = self.buffer().read(cx);
14389 let Some(buffer) = multibuffer.as_singleton() else {
14390 return;
14391 };
14392 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14393 return;
14394 };
14395 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14396 return;
14397 };
14398 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
14399 s.select_anchor_ranges([start..end])
14400 });
14401 }
14402
14403 pub fn go_to_diagnostic(
14404 &mut self,
14405 _: &GoToDiagnostic,
14406 window: &mut Window,
14407 cx: &mut Context<Self>,
14408 ) {
14409 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14410 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14411 }
14412
14413 pub fn go_to_prev_diagnostic(
14414 &mut self,
14415 _: &GoToPreviousDiagnostic,
14416 window: &mut Window,
14417 cx: &mut Context<Self>,
14418 ) {
14419 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14420 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14421 }
14422
14423 pub fn go_to_diagnostic_impl(
14424 &mut self,
14425 direction: Direction,
14426 window: &mut Window,
14427 cx: &mut Context<Self>,
14428 ) {
14429 let buffer = self.buffer.read(cx).snapshot(cx);
14430 let selection = self.selections.newest::<usize>(cx);
14431
14432 let mut active_group_id = None;
14433 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14434 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14435 active_group_id = Some(active_group.group_id);
14436 }
14437 }
14438
14439 fn filtered(
14440 snapshot: EditorSnapshot,
14441 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14442 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14443 diagnostics
14444 .filter(|entry| entry.range.start != entry.range.end)
14445 .filter(|entry| !entry.diagnostic.is_unnecessary)
14446 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14447 }
14448
14449 let snapshot = self.snapshot(window, cx);
14450 let before = filtered(
14451 snapshot.clone(),
14452 buffer
14453 .diagnostics_in_range(0..selection.start)
14454 .filter(|entry| entry.range.start <= selection.start),
14455 );
14456 let after = filtered(
14457 snapshot,
14458 buffer
14459 .diagnostics_in_range(selection.start..buffer.len())
14460 .filter(|entry| entry.range.start >= selection.start),
14461 );
14462
14463 let mut found: Option<DiagnosticEntry<usize>> = None;
14464 if direction == Direction::Prev {
14465 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14466 {
14467 for diagnostic in prev_diagnostics.into_iter().rev() {
14468 if diagnostic.range.start != selection.start
14469 || active_group_id
14470 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14471 {
14472 found = Some(diagnostic);
14473 break 'outer;
14474 }
14475 }
14476 }
14477 } else {
14478 for diagnostic in after.chain(before) {
14479 if diagnostic.range.start != selection.start
14480 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14481 {
14482 found = Some(diagnostic);
14483 break;
14484 }
14485 }
14486 }
14487 let Some(next_diagnostic) = found else {
14488 return;
14489 };
14490
14491 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14492 return;
14493 };
14494 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14495 s.select_ranges(vec![
14496 next_diagnostic.range.start..next_diagnostic.range.start,
14497 ])
14498 });
14499 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14500 self.refresh_inline_completion(false, true, window, cx);
14501 }
14502
14503 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14504 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14505 let snapshot = self.snapshot(window, cx);
14506 let selection = self.selections.newest::<Point>(cx);
14507 self.go_to_hunk_before_or_after_position(
14508 &snapshot,
14509 selection.head(),
14510 Direction::Next,
14511 window,
14512 cx,
14513 );
14514 }
14515
14516 pub fn go_to_hunk_before_or_after_position(
14517 &mut self,
14518 snapshot: &EditorSnapshot,
14519 position: Point,
14520 direction: Direction,
14521 window: &mut Window,
14522 cx: &mut Context<Editor>,
14523 ) {
14524 let row = if direction == Direction::Next {
14525 self.hunk_after_position(snapshot, position)
14526 .map(|hunk| hunk.row_range.start)
14527 } else {
14528 self.hunk_before_position(snapshot, position)
14529 };
14530
14531 if let Some(row) = row {
14532 let destination = Point::new(row.0, 0);
14533 let autoscroll = Autoscroll::center();
14534
14535 self.unfold_ranges(&[destination..destination], false, false, cx);
14536 self.change_selections(Some(autoscroll), window, cx, |s| {
14537 s.select_ranges([destination..destination]);
14538 });
14539 }
14540 }
14541
14542 fn hunk_after_position(
14543 &mut self,
14544 snapshot: &EditorSnapshot,
14545 position: Point,
14546 ) -> Option<MultiBufferDiffHunk> {
14547 snapshot
14548 .buffer_snapshot
14549 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14550 .find(|hunk| hunk.row_range.start.0 > position.row)
14551 .or_else(|| {
14552 snapshot
14553 .buffer_snapshot
14554 .diff_hunks_in_range(Point::zero()..position)
14555 .find(|hunk| hunk.row_range.end.0 < position.row)
14556 })
14557 }
14558
14559 fn go_to_prev_hunk(
14560 &mut self,
14561 _: &GoToPreviousHunk,
14562 window: &mut Window,
14563 cx: &mut Context<Self>,
14564 ) {
14565 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14566 let snapshot = self.snapshot(window, cx);
14567 let selection = self.selections.newest::<Point>(cx);
14568 self.go_to_hunk_before_or_after_position(
14569 &snapshot,
14570 selection.head(),
14571 Direction::Prev,
14572 window,
14573 cx,
14574 );
14575 }
14576
14577 fn hunk_before_position(
14578 &mut self,
14579 snapshot: &EditorSnapshot,
14580 position: Point,
14581 ) -> Option<MultiBufferRow> {
14582 snapshot
14583 .buffer_snapshot
14584 .diff_hunk_before(position)
14585 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14586 }
14587
14588 fn go_to_next_change(
14589 &mut self,
14590 _: &GoToNextChange,
14591 window: &mut Window,
14592 cx: &mut Context<Self>,
14593 ) {
14594 if let Some(selections) = self
14595 .change_list
14596 .next_change(1, Direction::Next)
14597 .map(|s| s.to_vec())
14598 {
14599 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14600 let map = s.display_map();
14601 s.select_display_ranges(selections.iter().map(|a| {
14602 let point = a.to_display_point(&map);
14603 point..point
14604 }))
14605 })
14606 }
14607 }
14608
14609 fn go_to_previous_change(
14610 &mut self,
14611 _: &GoToPreviousChange,
14612 window: &mut Window,
14613 cx: &mut Context<Self>,
14614 ) {
14615 if let Some(selections) = self
14616 .change_list
14617 .next_change(1, Direction::Prev)
14618 .map(|s| s.to_vec())
14619 {
14620 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14621 let map = s.display_map();
14622 s.select_display_ranges(selections.iter().map(|a| {
14623 let point = a.to_display_point(&map);
14624 point..point
14625 }))
14626 })
14627 }
14628 }
14629
14630 fn go_to_line<T: 'static>(
14631 &mut self,
14632 position: Anchor,
14633 highlight_color: Option<Hsla>,
14634 window: &mut Window,
14635 cx: &mut Context<Self>,
14636 ) {
14637 let snapshot = self.snapshot(window, cx).display_snapshot;
14638 let position = position.to_point(&snapshot.buffer_snapshot);
14639 let start = snapshot
14640 .buffer_snapshot
14641 .clip_point(Point::new(position.row, 0), Bias::Left);
14642 let end = start + Point::new(1, 0);
14643 let start = snapshot.buffer_snapshot.anchor_before(start);
14644 let end = snapshot.buffer_snapshot.anchor_before(end);
14645
14646 self.highlight_rows::<T>(
14647 start..end,
14648 highlight_color
14649 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14650 Default::default(),
14651 cx,
14652 );
14653
14654 if self.buffer.read(cx).is_singleton() {
14655 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14656 }
14657 }
14658
14659 pub fn go_to_definition(
14660 &mut self,
14661 _: &GoToDefinition,
14662 window: &mut Window,
14663 cx: &mut Context<Self>,
14664 ) -> Task<Result<Navigated>> {
14665 let definition =
14666 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14667 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14668 cx.spawn_in(window, async move |editor, cx| {
14669 if definition.await? == Navigated::Yes {
14670 return Ok(Navigated::Yes);
14671 }
14672 match fallback_strategy {
14673 GoToDefinitionFallback::None => Ok(Navigated::No),
14674 GoToDefinitionFallback::FindAllReferences => {
14675 match editor.update_in(cx, |editor, window, cx| {
14676 editor.find_all_references(&FindAllReferences, window, cx)
14677 })? {
14678 Some(references) => references.await,
14679 None => Ok(Navigated::No),
14680 }
14681 }
14682 }
14683 })
14684 }
14685
14686 pub fn go_to_declaration(
14687 &mut self,
14688 _: &GoToDeclaration,
14689 window: &mut Window,
14690 cx: &mut Context<Self>,
14691 ) -> Task<Result<Navigated>> {
14692 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14693 }
14694
14695 pub fn go_to_declaration_split(
14696 &mut self,
14697 _: &GoToDeclaration,
14698 window: &mut Window,
14699 cx: &mut Context<Self>,
14700 ) -> Task<Result<Navigated>> {
14701 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14702 }
14703
14704 pub fn go_to_implementation(
14705 &mut self,
14706 _: &GoToImplementation,
14707 window: &mut Window,
14708 cx: &mut Context<Self>,
14709 ) -> Task<Result<Navigated>> {
14710 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14711 }
14712
14713 pub fn go_to_implementation_split(
14714 &mut self,
14715 _: &GoToImplementationSplit,
14716 window: &mut Window,
14717 cx: &mut Context<Self>,
14718 ) -> Task<Result<Navigated>> {
14719 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14720 }
14721
14722 pub fn go_to_type_definition(
14723 &mut self,
14724 _: &GoToTypeDefinition,
14725 window: &mut Window,
14726 cx: &mut Context<Self>,
14727 ) -> Task<Result<Navigated>> {
14728 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14729 }
14730
14731 pub fn go_to_definition_split(
14732 &mut self,
14733 _: &GoToDefinitionSplit,
14734 window: &mut Window,
14735 cx: &mut Context<Self>,
14736 ) -> Task<Result<Navigated>> {
14737 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14738 }
14739
14740 pub fn go_to_type_definition_split(
14741 &mut self,
14742 _: &GoToTypeDefinitionSplit,
14743 window: &mut Window,
14744 cx: &mut Context<Self>,
14745 ) -> Task<Result<Navigated>> {
14746 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14747 }
14748
14749 fn go_to_definition_of_kind(
14750 &mut self,
14751 kind: GotoDefinitionKind,
14752 split: bool,
14753 window: &mut Window,
14754 cx: &mut Context<Self>,
14755 ) -> Task<Result<Navigated>> {
14756 let Some(provider) = self.semantics_provider.clone() else {
14757 return Task::ready(Ok(Navigated::No));
14758 };
14759 let head = self.selections.newest::<usize>(cx).head();
14760 let buffer = self.buffer.read(cx);
14761 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14762 text_anchor
14763 } else {
14764 return Task::ready(Ok(Navigated::No));
14765 };
14766
14767 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14768 return Task::ready(Ok(Navigated::No));
14769 };
14770
14771 cx.spawn_in(window, async move |editor, cx| {
14772 let definitions = definitions.await?;
14773 let navigated = editor
14774 .update_in(cx, |editor, window, cx| {
14775 editor.navigate_to_hover_links(
14776 Some(kind),
14777 definitions
14778 .into_iter()
14779 .filter(|location| {
14780 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14781 })
14782 .map(HoverLink::Text)
14783 .collect::<Vec<_>>(),
14784 split,
14785 window,
14786 cx,
14787 )
14788 })?
14789 .await?;
14790 anyhow::Ok(navigated)
14791 })
14792 }
14793
14794 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14795 let selection = self.selections.newest_anchor();
14796 let head = selection.head();
14797 let tail = selection.tail();
14798
14799 let Some((buffer, start_position)) =
14800 self.buffer.read(cx).text_anchor_for_position(head, cx)
14801 else {
14802 return;
14803 };
14804
14805 let end_position = if head != tail {
14806 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14807 return;
14808 };
14809 Some(pos)
14810 } else {
14811 None
14812 };
14813
14814 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14815 let url = if let Some(end_pos) = end_position {
14816 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14817 } else {
14818 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14819 };
14820
14821 if let Some(url) = url {
14822 editor.update(cx, |_, cx| {
14823 cx.open_url(&url);
14824 })
14825 } else {
14826 Ok(())
14827 }
14828 });
14829
14830 url_finder.detach();
14831 }
14832
14833 pub fn open_selected_filename(
14834 &mut self,
14835 _: &OpenSelectedFilename,
14836 window: &mut Window,
14837 cx: &mut Context<Self>,
14838 ) {
14839 let Some(workspace) = self.workspace() else {
14840 return;
14841 };
14842
14843 let position = self.selections.newest_anchor().head();
14844
14845 let Some((buffer, buffer_position)) =
14846 self.buffer.read(cx).text_anchor_for_position(position, cx)
14847 else {
14848 return;
14849 };
14850
14851 let project = self.project.clone();
14852
14853 cx.spawn_in(window, async move |_, cx| {
14854 let result = find_file(&buffer, project, buffer_position, cx).await;
14855
14856 if let Some((_, path)) = result {
14857 workspace
14858 .update_in(cx, |workspace, window, cx| {
14859 workspace.open_resolved_path(path, window, cx)
14860 })?
14861 .await?;
14862 }
14863 anyhow::Ok(())
14864 })
14865 .detach();
14866 }
14867
14868 pub(crate) fn navigate_to_hover_links(
14869 &mut self,
14870 kind: Option<GotoDefinitionKind>,
14871 mut definitions: Vec<HoverLink>,
14872 split: bool,
14873 window: &mut Window,
14874 cx: &mut Context<Editor>,
14875 ) -> Task<Result<Navigated>> {
14876 // If there is one definition, just open it directly
14877 if definitions.len() == 1 {
14878 let definition = definitions.pop().unwrap();
14879
14880 enum TargetTaskResult {
14881 Location(Option<Location>),
14882 AlreadyNavigated,
14883 }
14884
14885 let target_task = match definition {
14886 HoverLink::Text(link) => {
14887 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14888 }
14889 HoverLink::InlayHint(lsp_location, server_id) => {
14890 let computation =
14891 self.compute_target_location(lsp_location, server_id, window, cx);
14892 cx.background_spawn(async move {
14893 let location = computation.await?;
14894 Ok(TargetTaskResult::Location(location))
14895 })
14896 }
14897 HoverLink::Url(url) => {
14898 cx.open_url(&url);
14899 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14900 }
14901 HoverLink::File(path) => {
14902 if let Some(workspace) = self.workspace() {
14903 cx.spawn_in(window, async move |_, cx| {
14904 workspace
14905 .update_in(cx, |workspace, window, cx| {
14906 workspace.open_resolved_path(path, window, cx)
14907 })?
14908 .await
14909 .map(|_| TargetTaskResult::AlreadyNavigated)
14910 })
14911 } else {
14912 Task::ready(Ok(TargetTaskResult::Location(None)))
14913 }
14914 }
14915 };
14916 cx.spawn_in(window, async move |editor, cx| {
14917 let target = match target_task.await.context("target resolution task")? {
14918 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14919 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14920 TargetTaskResult::Location(Some(target)) => target,
14921 };
14922
14923 editor.update_in(cx, |editor, window, cx| {
14924 let Some(workspace) = editor.workspace() else {
14925 return Navigated::No;
14926 };
14927 let pane = workspace.read(cx).active_pane().clone();
14928
14929 let range = target.range.to_point(target.buffer.read(cx));
14930 let range = editor.range_for_match(&range);
14931 let range = collapse_multiline_range(range);
14932
14933 if !split
14934 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14935 {
14936 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14937 } else {
14938 window.defer(cx, move |window, cx| {
14939 let target_editor: Entity<Self> =
14940 workspace.update(cx, |workspace, cx| {
14941 let pane = if split {
14942 workspace.adjacent_pane(window, cx)
14943 } else {
14944 workspace.active_pane().clone()
14945 };
14946
14947 workspace.open_project_item(
14948 pane,
14949 target.buffer.clone(),
14950 true,
14951 true,
14952 window,
14953 cx,
14954 )
14955 });
14956 target_editor.update(cx, |target_editor, cx| {
14957 // When selecting a definition in a different buffer, disable the nav history
14958 // to avoid creating a history entry at the previous cursor location.
14959 pane.update(cx, |pane, _| pane.disable_history());
14960 target_editor.go_to_singleton_buffer_range(range, window, cx);
14961 pane.update(cx, |pane, _| pane.enable_history());
14962 });
14963 });
14964 }
14965 Navigated::Yes
14966 })
14967 })
14968 } else if !definitions.is_empty() {
14969 cx.spawn_in(window, async move |editor, cx| {
14970 let (title, location_tasks, workspace) = editor
14971 .update_in(cx, |editor, window, cx| {
14972 let tab_kind = match kind {
14973 Some(GotoDefinitionKind::Implementation) => "Implementations",
14974 _ => "Definitions",
14975 };
14976 let title = definitions
14977 .iter()
14978 .find_map(|definition| match definition {
14979 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14980 let buffer = origin.buffer.read(cx);
14981 format!(
14982 "{} for {}",
14983 tab_kind,
14984 buffer
14985 .text_for_range(origin.range.clone())
14986 .collect::<String>()
14987 )
14988 }),
14989 HoverLink::InlayHint(_, _) => None,
14990 HoverLink::Url(_) => None,
14991 HoverLink::File(_) => None,
14992 })
14993 .unwrap_or(tab_kind.to_string());
14994 let location_tasks = definitions
14995 .into_iter()
14996 .map(|definition| match definition {
14997 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14998 HoverLink::InlayHint(lsp_location, server_id) => editor
14999 .compute_target_location(lsp_location, server_id, window, cx),
15000 HoverLink::Url(_) => Task::ready(Ok(None)),
15001 HoverLink::File(_) => Task::ready(Ok(None)),
15002 })
15003 .collect::<Vec<_>>();
15004 (title, location_tasks, editor.workspace().clone())
15005 })
15006 .context("location tasks preparation")?;
15007
15008 let locations = future::join_all(location_tasks)
15009 .await
15010 .into_iter()
15011 .filter_map(|location| location.transpose())
15012 .collect::<Result<_>>()
15013 .context("location tasks")?;
15014
15015 let Some(workspace) = workspace else {
15016 return Ok(Navigated::No);
15017 };
15018 let opened = workspace
15019 .update_in(cx, |workspace, window, cx| {
15020 Self::open_locations_in_multibuffer(
15021 workspace,
15022 locations,
15023 title,
15024 split,
15025 MultibufferSelectionMode::First,
15026 window,
15027 cx,
15028 )
15029 })
15030 .ok();
15031
15032 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15033 })
15034 } else {
15035 Task::ready(Ok(Navigated::No))
15036 }
15037 }
15038
15039 fn compute_target_location(
15040 &self,
15041 lsp_location: lsp::Location,
15042 server_id: LanguageServerId,
15043 window: &mut Window,
15044 cx: &mut Context<Self>,
15045 ) -> Task<anyhow::Result<Option<Location>>> {
15046 let Some(project) = self.project.clone() else {
15047 return Task::ready(Ok(None));
15048 };
15049
15050 cx.spawn_in(window, async move |editor, cx| {
15051 let location_task = editor.update(cx, |_, cx| {
15052 project.update(cx, |project, cx| {
15053 let language_server_name = project
15054 .language_server_statuses(cx)
15055 .find(|(id, _)| server_id == *id)
15056 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15057 language_server_name.map(|language_server_name| {
15058 project.open_local_buffer_via_lsp(
15059 lsp_location.uri.clone(),
15060 server_id,
15061 language_server_name,
15062 cx,
15063 )
15064 })
15065 })
15066 })?;
15067 let location = match location_task {
15068 Some(task) => Some({
15069 let target_buffer_handle = task.await.context("open local buffer")?;
15070 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15071 let target_start = target_buffer
15072 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15073 let target_end = target_buffer
15074 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15075 target_buffer.anchor_after(target_start)
15076 ..target_buffer.anchor_before(target_end)
15077 })?;
15078 Location {
15079 buffer: target_buffer_handle,
15080 range,
15081 }
15082 }),
15083 None => None,
15084 };
15085 Ok(location)
15086 })
15087 }
15088
15089 pub fn find_all_references(
15090 &mut self,
15091 _: &FindAllReferences,
15092 window: &mut Window,
15093 cx: &mut Context<Self>,
15094 ) -> Option<Task<Result<Navigated>>> {
15095 let selection = self.selections.newest::<usize>(cx);
15096 let multi_buffer = self.buffer.read(cx);
15097 let head = selection.head();
15098
15099 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15100 let head_anchor = multi_buffer_snapshot.anchor_at(
15101 head,
15102 if head < selection.tail() {
15103 Bias::Right
15104 } else {
15105 Bias::Left
15106 },
15107 );
15108
15109 match self
15110 .find_all_references_task_sources
15111 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15112 {
15113 Ok(_) => {
15114 log::info!(
15115 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15116 );
15117 return None;
15118 }
15119 Err(i) => {
15120 self.find_all_references_task_sources.insert(i, head_anchor);
15121 }
15122 }
15123
15124 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15125 let workspace = self.workspace()?;
15126 let project = workspace.read(cx).project().clone();
15127 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15128 Some(cx.spawn_in(window, async move |editor, cx| {
15129 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15130 if let Ok(i) = editor
15131 .find_all_references_task_sources
15132 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15133 {
15134 editor.find_all_references_task_sources.remove(i);
15135 }
15136 });
15137
15138 let locations = references.await?;
15139 if locations.is_empty() {
15140 return anyhow::Ok(Navigated::No);
15141 }
15142
15143 workspace.update_in(cx, |workspace, window, cx| {
15144 let title = locations
15145 .first()
15146 .as_ref()
15147 .map(|location| {
15148 let buffer = location.buffer.read(cx);
15149 format!(
15150 "References to `{}`",
15151 buffer
15152 .text_for_range(location.range.clone())
15153 .collect::<String>()
15154 )
15155 })
15156 .unwrap();
15157 Self::open_locations_in_multibuffer(
15158 workspace,
15159 locations,
15160 title,
15161 false,
15162 MultibufferSelectionMode::First,
15163 window,
15164 cx,
15165 );
15166 Navigated::Yes
15167 })
15168 }))
15169 }
15170
15171 /// Opens a multibuffer with the given project locations in it
15172 pub fn open_locations_in_multibuffer(
15173 workspace: &mut Workspace,
15174 mut locations: Vec<Location>,
15175 title: String,
15176 split: bool,
15177 multibuffer_selection_mode: MultibufferSelectionMode,
15178 window: &mut Window,
15179 cx: &mut Context<Workspace>,
15180 ) {
15181 // If there are multiple definitions, open them in a multibuffer
15182 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15183 let mut locations = locations.into_iter().peekable();
15184 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15185 let capability = workspace.project().read(cx).capability();
15186
15187 let excerpt_buffer = cx.new(|cx| {
15188 let mut multibuffer = MultiBuffer::new(capability);
15189 while let Some(location) = locations.next() {
15190 let buffer = location.buffer.read(cx);
15191 let mut ranges_for_buffer = Vec::new();
15192 let range = location.range.to_point(buffer);
15193 ranges_for_buffer.push(range.clone());
15194
15195 while let Some(next_location) = locations.peek() {
15196 if next_location.buffer == location.buffer {
15197 ranges_for_buffer.push(next_location.range.to_point(buffer));
15198 locations.next();
15199 } else {
15200 break;
15201 }
15202 }
15203
15204 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15205 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15206 PathKey::for_buffer(&location.buffer, cx),
15207 location.buffer.clone(),
15208 ranges_for_buffer,
15209 DEFAULT_MULTIBUFFER_CONTEXT,
15210 cx,
15211 );
15212 ranges.extend(new_ranges)
15213 }
15214
15215 multibuffer.with_title(title)
15216 });
15217
15218 let editor = cx.new(|cx| {
15219 Editor::for_multibuffer(
15220 excerpt_buffer,
15221 Some(workspace.project().clone()),
15222 window,
15223 cx,
15224 )
15225 });
15226 editor.update(cx, |editor, cx| {
15227 match multibuffer_selection_mode {
15228 MultibufferSelectionMode::First => {
15229 if let Some(first_range) = ranges.first() {
15230 editor.change_selections(None, window, cx, |selections| {
15231 selections.clear_disjoint();
15232 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
15233 });
15234 }
15235 editor.highlight_background::<Self>(
15236 &ranges,
15237 |theme| theme.editor_highlighted_line_background,
15238 cx,
15239 );
15240 }
15241 MultibufferSelectionMode::All => {
15242 editor.change_selections(None, window, cx, |selections| {
15243 selections.clear_disjoint();
15244 selections.select_anchor_ranges(ranges);
15245 });
15246 }
15247 }
15248 editor.register_buffers_with_language_servers(cx);
15249 });
15250
15251 let item = Box::new(editor);
15252 let item_id = item.item_id();
15253
15254 if split {
15255 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15256 } else {
15257 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15258 let (preview_item_id, preview_item_idx) =
15259 workspace.active_pane().read_with(cx, |pane, _| {
15260 (pane.preview_item_id(), pane.preview_item_idx())
15261 });
15262
15263 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15264
15265 if let Some(preview_item_id) = preview_item_id {
15266 workspace.active_pane().update(cx, |pane, cx| {
15267 pane.remove_item(preview_item_id, false, false, window, cx);
15268 });
15269 }
15270 } else {
15271 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15272 }
15273 }
15274 workspace.active_pane().update(cx, |pane, cx| {
15275 pane.set_preview_item_id(Some(item_id), cx);
15276 });
15277 }
15278
15279 pub fn rename(
15280 &mut self,
15281 _: &Rename,
15282 window: &mut Window,
15283 cx: &mut Context<Self>,
15284 ) -> Option<Task<Result<()>>> {
15285 use language::ToOffset as _;
15286
15287 let provider = self.semantics_provider.clone()?;
15288 let selection = self.selections.newest_anchor().clone();
15289 let (cursor_buffer, cursor_buffer_position) = self
15290 .buffer
15291 .read(cx)
15292 .text_anchor_for_position(selection.head(), cx)?;
15293 let (tail_buffer, cursor_buffer_position_end) = self
15294 .buffer
15295 .read(cx)
15296 .text_anchor_for_position(selection.tail(), cx)?;
15297 if tail_buffer != cursor_buffer {
15298 return None;
15299 }
15300
15301 let snapshot = cursor_buffer.read(cx).snapshot();
15302 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15303 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15304 let prepare_rename = provider
15305 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15306 .unwrap_or_else(|| Task::ready(Ok(None)));
15307 drop(snapshot);
15308
15309 Some(cx.spawn_in(window, async move |this, cx| {
15310 let rename_range = if let Some(range) = prepare_rename.await? {
15311 Some(range)
15312 } else {
15313 this.update(cx, |this, cx| {
15314 let buffer = this.buffer.read(cx).snapshot(cx);
15315 let mut buffer_highlights = this
15316 .document_highlights_for_position(selection.head(), &buffer)
15317 .filter(|highlight| {
15318 highlight.start.excerpt_id == selection.head().excerpt_id
15319 && highlight.end.excerpt_id == selection.head().excerpt_id
15320 });
15321 buffer_highlights
15322 .next()
15323 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15324 })?
15325 };
15326 if let Some(rename_range) = rename_range {
15327 this.update_in(cx, |this, window, cx| {
15328 let snapshot = cursor_buffer.read(cx).snapshot();
15329 let rename_buffer_range = rename_range.to_offset(&snapshot);
15330 let cursor_offset_in_rename_range =
15331 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15332 let cursor_offset_in_rename_range_end =
15333 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15334
15335 this.take_rename(false, window, cx);
15336 let buffer = this.buffer.read(cx).read(cx);
15337 let cursor_offset = selection.head().to_offset(&buffer);
15338 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15339 let rename_end = rename_start + rename_buffer_range.len();
15340 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15341 let mut old_highlight_id = None;
15342 let old_name: Arc<str> = buffer
15343 .chunks(rename_start..rename_end, true)
15344 .map(|chunk| {
15345 if old_highlight_id.is_none() {
15346 old_highlight_id = chunk.syntax_highlight_id;
15347 }
15348 chunk.text
15349 })
15350 .collect::<String>()
15351 .into();
15352
15353 drop(buffer);
15354
15355 // Position the selection in the rename editor so that it matches the current selection.
15356 this.show_local_selections = false;
15357 let rename_editor = cx.new(|cx| {
15358 let mut editor = Editor::single_line(window, cx);
15359 editor.buffer.update(cx, |buffer, cx| {
15360 buffer.edit([(0..0, old_name.clone())], None, cx)
15361 });
15362 let rename_selection_range = match cursor_offset_in_rename_range
15363 .cmp(&cursor_offset_in_rename_range_end)
15364 {
15365 Ordering::Equal => {
15366 editor.select_all(&SelectAll, window, cx);
15367 return editor;
15368 }
15369 Ordering::Less => {
15370 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15371 }
15372 Ordering::Greater => {
15373 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15374 }
15375 };
15376 if rename_selection_range.end > old_name.len() {
15377 editor.select_all(&SelectAll, window, cx);
15378 } else {
15379 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15380 s.select_ranges([rename_selection_range]);
15381 });
15382 }
15383 editor
15384 });
15385 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15386 if e == &EditorEvent::Focused {
15387 cx.emit(EditorEvent::FocusedIn)
15388 }
15389 })
15390 .detach();
15391
15392 let write_highlights =
15393 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
15394 let read_highlights =
15395 this.clear_background_highlights::<DocumentHighlightRead>(cx);
15396 let ranges = write_highlights
15397 .iter()
15398 .flat_map(|(_, ranges)| ranges.iter())
15399 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
15400 .cloned()
15401 .collect();
15402
15403 this.highlight_text::<Rename>(
15404 ranges,
15405 HighlightStyle {
15406 fade_out: Some(0.6),
15407 ..Default::default()
15408 },
15409 cx,
15410 );
15411 let rename_focus_handle = rename_editor.focus_handle(cx);
15412 window.focus(&rename_focus_handle);
15413 let block_id = this.insert_blocks(
15414 [BlockProperties {
15415 style: BlockStyle::Flex,
15416 placement: BlockPlacement::Below(range.start),
15417 height: Some(1),
15418 render: Arc::new({
15419 let rename_editor = rename_editor.clone();
15420 move |cx: &mut BlockContext| {
15421 let mut text_style = cx.editor_style.text.clone();
15422 if let Some(highlight_style) = old_highlight_id
15423 .and_then(|h| h.style(&cx.editor_style.syntax))
15424 {
15425 text_style = text_style.highlight(highlight_style);
15426 }
15427 div()
15428 .block_mouse_except_scroll()
15429 .pl(cx.anchor_x)
15430 .child(EditorElement::new(
15431 &rename_editor,
15432 EditorStyle {
15433 background: cx.theme().system().transparent,
15434 local_player: cx.editor_style.local_player,
15435 text: text_style,
15436 scrollbar_width: cx.editor_style.scrollbar_width,
15437 syntax: cx.editor_style.syntax.clone(),
15438 status: cx.editor_style.status.clone(),
15439 inlay_hints_style: HighlightStyle {
15440 font_weight: Some(FontWeight::BOLD),
15441 ..make_inlay_hints_style(cx.app)
15442 },
15443 inline_completion_styles: make_suggestion_styles(
15444 cx.app,
15445 ),
15446 ..EditorStyle::default()
15447 },
15448 ))
15449 .into_any_element()
15450 }
15451 }),
15452 priority: 0,
15453 render_in_minimap: true,
15454 }],
15455 Some(Autoscroll::fit()),
15456 cx,
15457 )[0];
15458 this.pending_rename = Some(RenameState {
15459 range,
15460 old_name,
15461 editor: rename_editor,
15462 block_id,
15463 });
15464 })?;
15465 }
15466
15467 Ok(())
15468 }))
15469 }
15470
15471 pub fn confirm_rename(
15472 &mut self,
15473 _: &ConfirmRename,
15474 window: &mut Window,
15475 cx: &mut Context<Self>,
15476 ) -> Option<Task<Result<()>>> {
15477 let rename = self.take_rename(false, window, cx)?;
15478 let workspace = self.workspace()?.downgrade();
15479 let (buffer, start) = self
15480 .buffer
15481 .read(cx)
15482 .text_anchor_for_position(rename.range.start, cx)?;
15483 let (end_buffer, _) = self
15484 .buffer
15485 .read(cx)
15486 .text_anchor_for_position(rename.range.end, cx)?;
15487 if buffer != end_buffer {
15488 return None;
15489 }
15490
15491 let old_name = rename.old_name;
15492 let new_name = rename.editor.read(cx).text(cx);
15493
15494 let rename = self.semantics_provider.as_ref()?.perform_rename(
15495 &buffer,
15496 start,
15497 new_name.clone(),
15498 cx,
15499 )?;
15500
15501 Some(cx.spawn_in(window, async move |editor, cx| {
15502 let project_transaction = rename.await?;
15503 Self::open_project_transaction(
15504 &editor,
15505 workspace,
15506 project_transaction,
15507 format!("Rename: {} → {}", old_name, new_name),
15508 cx,
15509 )
15510 .await?;
15511
15512 editor.update(cx, |editor, cx| {
15513 editor.refresh_document_highlights(cx);
15514 })?;
15515 Ok(())
15516 }))
15517 }
15518
15519 fn take_rename(
15520 &mut self,
15521 moving_cursor: bool,
15522 window: &mut Window,
15523 cx: &mut Context<Self>,
15524 ) -> Option<RenameState> {
15525 let rename = self.pending_rename.take()?;
15526 if rename.editor.focus_handle(cx).is_focused(window) {
15527 window.focus(&self.focus_handle);
15528 }
15529
15530 self.remove_blocks(
15531 [rename.block_id].into_iter().collect(),
15532 Some(Autoscroll::fit()),
15533 cx,
15534 );
15535 self.clear_highlights::<Rename>(cx);
15536 self.show_local_selections = true;
15537
15538 if moving_cursor {
15539 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15540 editor.selections.newest::<usize>(cx).head()
15541 });
15542
15543 // Update the selection to match the position of the selection inside
15544 // the rename editor.
15545 let snapshot = self.buffer.read(cx).read(cx);
15546 let rename_range = rename.range.to_offset(&snapshot);
15547 let cursor_in_editor = snapshot
15548 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15549 .min(rename_range.end);
15550 drop(snapshot);
15551
15552 self.change_selections(None, window, cx, |s| {
15553 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15554 });
15555 } else {
15556 self.refresh_document_highlights(cx);
15557 }
15558
15559 Some(rename)
15560 }
15561
15562 pub fn pending_rename(&self) -> Option<&RenameState> {
15563 self.pending_rename.as_ref()
15564 }
15565
15566 fn format(
15567 &mut self,
15568 _: &Format,
15569 window: &mut Window,
15570 cx: &mut Context<Self>,
15571 ) -> Option<Task<Result<()>>> {
15572 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15573
15574 let project = match &self.project {
15575 Some(project) => project.clone(),
15576 None => return None,
15577 };
15578
15579 Some(self.perform_format(
15580 project,
15581 FormatTrigger::Manual,
15582 FormatTarget::Buffers,
15583 window,
15584 cx,
15585 ))
15586 }
15587
15588 fn format_selections(
15589 &mut self,
15590 _: &FormatSelections,
15591 window: &mut Window,
15592 cx: &mut Context<Self>,
15593 ) -> Option<Task<Result<()>>> {
15594 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15595
15596 let project = match &self.project {
15597 Some(project) => project.clone(),
15598 None => return None,
15599 };
15600
15601 let ranges = self
15602 .selections
15603 .all_adjusted(cx)
15604 .into_iter()
15605 .map(|selection| selection.range())
15606 .collect_vec();
15607
15608 Some(self.perform_format(
15609 project,
15610 FormatTrigger::Manual,
15611 FormatTarget::Ranges(ranges),
15612 window,
15613 cx,
15614 ))
15615 }
15616
15617 fn perform_format(
15618 &mut self,
15619 project: Entity<Project>,
15620 trigger: FormatTrigger,
15621 target: FormatTarget,
15622 window: &mut Window,
15623 cx: &mut Context<Self>,
15624 ) -> Task<Result<()>> {
15625 let buffer = self.buffer.clone();
15626 let (buffers, target) = match target {
15627 FormatTarget::Buffers => {
15628 let mut buffers = buffer.read(cx).all_buffers();
15629 if trigger == FormatTrigger::Save {
15630 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15631 }
15632 (buffers, LspFormatTarget::Buffers)
15633 }
15634 FormatTarget::Ranges(selection_ranges) => {
15635 let multi_buffer = buffer.read(cx);
15636 let snapshot = multi_buffer.read(cx);
15637 let mut buffers = HashSet::default();
15638 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15639 BTreeMap::new();
15640 for selection_range in selection_ranges {
15641 for (buffer, buffer_range, _) in
15642 snapshot.range_to_buffer_ranges(selection_range)
15643 {
15644 let buffer_id = buffer.remote_id();
15645 let start = buffer.anchor_before(buffer_range.start);
15646 let end = buffer.anchor_after(buffer_range.end);
15647 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15648 buffer_id_to_ranges
15649 .entry(buffer_id)
15650 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15651 .or_insert_with(|| vec![start..end]);
15652 }
15653 }
15654 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15655 }
15656 };
15657
15658 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15659 let selections_prev = transaction_id_prev
15660 .and_then(|transaction_id_prev| {
15661 // default to selections as they were after the last edit, if we have them,
15662 // instead of how they are now.
15663 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15664 // will take you back to where you made the last edit, instead of staying where you scrolled
15665 self.selection_history
15666 .transaction(transaction_id_prev)
15667 .map(|t| t.0.clone())
15668 })
15669 .unwrap_or_else(|| {
15670 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15671 self.selections.disjoint_anchors()
15672 });
15673
15674 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15675 let format = project.update(cx, |project, cx| {
15676 project.format(buffers, target, true, trigger, cx)
15677 });
15678
15679 cx.spawn_in(window, async move |editor, cx| {
15680 let transaction = futures::select_biased! {
15681 transaction = format.log_err().fuse() => transaction,
15682 () = timeout => {
15683 log::warn!("timed out waiting for formatting");
15684 None
15685 }
15686 };
15687
15688 buffer
15689 .update(cx, |buffer, cx| {
15690 if let Some(transaction) = transaction {
15691 if !buffer.is_singleton() {
15692 buffer.push_transaction(&transaction.0, cx);
15693 }
15694 }
15695 cx.notify();
15696 })
15697 .ok();
15698
15699 if let Some(transaction_id_now) =
15700 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15701 {
15702 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15703 if has_new_transaction {
15704 _ = editor.update(cx, |editor, _| {
15705 editor
15706 .selection_history
15707 .insert_transaction(transaction_id_now, selections_prev);
15708 });
15709 }
15710 }
15711
15712 Ok(())
15713 })
15714 }
15715
15716 fn organize_imports(
15717 &mut self,
15718 _: &OrganizeImports,
15719 window: &mut Window,
15720 cx: &mut Context<Self>,
15721 ) -> Option<Task<Result<()>>> {
15722 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15723 let project = match &self.project {
15724 Some(project) => project.clone(),
15725 None => return None,
15726 };
15727 Some(self.perform_code_action_kind(
15728 project,
15729 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15730 window,
15731 cx,
15732 ))
15733 }
15734
15735 fn perform_code_action_kind(
15736 &mut self,
15737 project: Entity<Project>,
15738 kind: CodeActionKind,
15739 window: &mut Window,
15740 cx: &mut Context<Self>,
15741 ) -> Task<Result<()>> {
15742 let buffer = self.buffer.clone();
15743 let buffers = buffer.read(cx).all_buffers();
15744 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15745 let apply_action = project.update(cx, |project, cx| {
15746 project.apply_code_action_kind(buffers, kind, true, cx)
15747 });
15748 cx.spawn_in(window, async move |_, cx| {
15749 let transaction = futures::select_biased! {
15750 () = timeout => {
15751 log::warn!("timed out waiting for executing code action");
15752 None
15753 }
15754 transaction = apply_action.log_err().fuse() => transaction,
15755 };
15756 buffer
15757 .update(cx, |buffer, cx| {
15758 // check if we need this
15759 if let Some(transaction) = transaction {
15760 if !buffer.is_singleton() {
15761 buffer.push_transaction(&transaction.0, cx);
15762 }
15763 }
15764 cx.notify();
15765 })
15766 .ok();
15767 Ok(())
15768 })
15769 }
15770
15771 fn restart_language_server(
15772 &mut self,
15773 _: &RestartLanguageServer,
15774 _: &mut Window,
15775 cx: &mut Context<Self>,
15776 ) {
15777 if let Some(project) = self.project.clone() {
15778 self.buffer.update(cx, |multi_buffer, cx| {
15779 project.update(cx, |project, cx| {
15780 project.restart_language_servers_for_buffers(
15781 multi_buffer.all_buffers().into_iter().collect(),
15782 cx,
15783 );
15784 });
15785 })
15786 }
15787 }
15788
15789 fn stop_language_server(
15790 &mut self,
15791 _: &StopLanguageServer,
15792 _: &mut Window,
15793 cx: &mut Context<Self>,
15794 ) {
15795 if let Some(project) = self.project.clone() {
15796 self.buffer.update(cx, |multi_buffer, cx| {
15797 project.update(cx, |project, cx| {
15798 project.stop_language_servers_for_buffers(
15799 multi_buffer.all_buffers().into_iter().collect(),
15800 cx,
15801 );
15802 cx.emit(project::Event::RefreshInlayHints);
15803 });
15804 });
15805 }
15806 }
15807
15808 fn cancel_language_server_work(
15809 workspace: &mut Workspace,
15810 _: &actions::CancelLanguageServerWork,
15811 _: &mut Window,
15812 cx: &mut Context<Workspace>,
15813 ) {
15814 let project = workspace.project();
15815 let buffers = workspace
15816 .active_item(cx)
15817 .and_then(|item| item.act_as::<Editor>(cx))
15818 .map_or(HashSet::default(), |editor| {
15819 editor.read(cx).buffer.read(cx).all_buffers()
15820 });
15821 project.update(cx, |project, cx| {
15822 project.cancel_language_server_work_for_buffers(buffers, cx);
15823 });
15824 }
15825
15826 fn show_character_palette(
15827 &mut self,
15828 _: &ShowCharacterPalette,
15829 window: &mut Window,
15830 _: &mut Context<Self>,
15831 ) {
15832 window.show_character_palette();
15833 }
15834
15835 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15836 if self.mode.is_minimap() {
15837 return;
15838 }
15839
15840 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15841 let buffer = self.buffer.read(cx).snapshot(cx);
15842 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15843 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15844 let is_valid = buffer
15845 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15846 .any(|entry| {
15847 entry.diagnostic.is_primary
15848 && !entry.range.is_empty()
15849 && entry.range.start == primary_range_start
15850 && entry.diagnostic.message == active_diagnostics.active_message
15851 });
15852
15853 if !is_valid {
15854 self.dismiss_diagnostics(cx);
15855 }
15856 }
15857 }
15858
15859 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15860 match &self.active_diagnostics {
15861 ActiveDiagnostic::Group(group) => Some(group),
15862 _ => None,
15863 }
15864 }
15865
15866 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15867 self.dismiss_diagnostics(cx);
15868 self.active_diagnostics = ActiveDiagnostic::All;
15869 }
15870
15871 fn activate_diagnostics(
15872 &mut self,
15873 buffer_id: BufferId,
15874 diagnostic: DiagnosticEntry<usize>,
15875 window: &mut Window,
15876 cx: &mut Context<Self>,
15877 ) {
15878 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15879 return;
15880 }
15881 self.dismiss_diagnostics(cx);
15882 let snapshot = self.snapshot(window, cx);
15883 let buffer = self.buffer.read(cx).snapshot(cx);
15884 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15885 return;
15886 };
15887
15888 let diagnostic_group = buffer
15889 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15890 .collect::<Vec<_>>();
15891
15892 let blocks =
15893 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15894
15895 let blocks = self.display_map.update(cx, |display_map, cx| {
15896 display_map.insert_blocks(blocks, cx).into_iter().collect()
15897 });
15898 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15899 active_range: buffer.anchor_before(diagnostic.range.start)
15900 ..buffer.anchor_after(diagnostic.range.end),
15901 active_message: diagnostic.diagnostic.message.clone(),
15902 group_id: diagnostic.diagnostic.group_id,
15903 blocks,
15904 });
15905 cx.notify();
15906 }
15907
15908 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15909 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15910 return;
15911 };
15912
15913 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15914 if let ActiveDiagnostic::Group(group) = prev {
15915 self.display_map.update(cx, |display_map, cx| {
15916 display_map.remove_blocks(group.blocks, cx);
15917 });
15918 cx.notify();
15919 }
15920 }
15921
15922 /// Disable inline diagnostics rendering for this editor.
15923 pub fn disable_inline_diagnostics(&mut self) {
15924 self.inline_diagnostics_enabled = false;
15925 self.inline_diagnostics_update = Task::ready(());
15926 self.inline_diagnostics.clear();
15927 }
15928
15929 pub fn diagnostics_enabled(&self) -> bool {
15930 self.mode.is_full()
15931 }
15932
15933 pub fn inline_diagnostics_enabled(&self) -> bool {
15934 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15935 }
15936
15937 pub fn show_inline_diagnostics(&self) -> bool {
15938 self.show_inline_diagnostics
15939 }
15940
15941 pub fn toggle_inline_diagnostics(
15942 &mut self,
15943 _: &ToggleInlineDiagnostics,
15944 window: &mut Window,
15945 cx: &mut Context<Editor>,
15946 ) {
15947 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15948 self.refresh_inline_diagnostics(false, window, cx);
15949 }
15950
15951 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15952 self.diagnostics_max_severity = severity;
15953 self.display_map.update(cx, |display_map, _| {
15954 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15955 });
15956 }
15957
15958 pub fn toggle_diagnostics(
15959 &mut self,
15960 _: &ToggleDiagnostics,
15961 window: &mut Window,
15962 cx: &mut Context<Editor>,
15963 ) {
15964 if !self.diagnostics_enabled() {
15965 return;
15966 }
15967
15968 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15969 EditorSettings::get_global(cx)
15970 .diagnostics_max_severity
15971 .filter(|severity| severity != &DiagnosticSeverity::Off)
15972 .unwrap_or(DiagnosticSeverity::Hint)
15973 } else {
15974 DiagnosticSeverity::Off
15975 };
15976 self.set_max_diagnostics_severity(new_severity, cx);
15977 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15978 self.active_diagnostics = ActiveDiagnostic::None;
15979 self.inline_diagnostics_update = Task::ready(());
15980 self.inline_diagnostics.clear();
15981 } else {
15982 self.refresh_inline_diagnostics(false, window, cx);
15983 }
15984
15985 cx.notify();
15986 }
15987
15988 pub fn toggle_minimap(
15989 &mut self,
15990 _: &ToggleMinimap,
15991 window: &mut Window,
15992 cx: &mut Context<Editor>,
15993 ) {
15994 if self.supports_minimap(cx) {
15995 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
15996 }
15997 }
15998
15999 fn refresh_inline_diagnostics(
16000 &mut self,
16001 debounce: bool,
16002 window: &mut Window,
16003 cx: &mut Context<Self>,
16004 ) {
16005 let max_severity = ProjectSettings::get_global(cx)
16006 .diagnostics
16007 .inline
16008 .max_severity
16009 .unwrap_or(self.diagnostics_max_severity);
16010
16011 if !self.inline_diagnostics_enabled()
16012 || !self.show_inline_diagnostics
16013 || max_severity == DiagnosticSeverity::Off
16014 {
16015 self.inline_diagnostics_update = Task::ready(());
16016 self.inline_diagnostics.clear();
16017 return;
16018 }
16019
16020 let debounce_ms = ProjectSettings::get_global(cx)
16021 .diagnostics
16022 .inline
16023 .update_debounce_ms;
16024 let debounce = if debounce && debounce_ms > 0 {
16025 Some(Duration::from_millis(debounce_ms))
16026 } else {
16027 None
16028 };
16029 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16030 if let Some(debounce) = debounce {
16031 cx.background_executor().timer(debounce).await;
16032 }
16033 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16034 editor
16035 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16036 .ok()
16037 }) else {
16038 return;
16039 };
16040
16041 let new_inline_diagnostics = cx
16042 .background_spawn(async move {
16043 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16044 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16045 let message = diagnostic_entry
16046 .diagnostic
16047 .message
16048 .split_once('\n')
16049 .map(|(line, _)| line)
16050 .map(SharedString::new)
16051 .unwrap_or_else(|| {
16052 SharedString::from(diagnostic_entry.diagnostic.message)
16053 });
16054 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16055 let (Ok(i) | Err(i)) = inline_diagnostics
16056 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16057 inline_diagnostics.insert(
16058 i,
16059 (
16060 start_anchor,
16061 InlineDiagnostic {
16062 message,
16063 group_id: diagnostic_entry.diagnostic.group_id,
16064 start: diagnostic_entry.range.start.to_point(&snapshot),
16065 is_primary: diagnostic_entry.diagnostic.is_primary,
16066 severity: diagnostic_entry.diagnostic.severity,
16067 },
16068 ),
16069 );
16070 }
16071 inline_diagnostics
16072 })
16073 .await;
16074
16075 editor
16076 .update(cx, |editor, cx| {
16077 editor.inline_diagnostics = new_inline_diagnostics;
16078 cx.notify();
16079 })
16080 .ok();
16081 });
16082 }
16083
16084 fn pull_diagnostics(
16085 &mut self,
16086 buffer_id: Option<BufferId>,
16087 window: &Window,
16088 cx: &mut Context<Self>,
16089 ) -> Option<()> {
16090 if !self.mode().is_full() {
16091 return None;
16092 }
16093 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16094 .diagnostics
16095 .lsp_pull_diagnostics;
16096 if !pull_diagnostics_settings.enabled {
16097 return None;
16098 }
16099 let project = self.project.as_ref()?.downgrade();
16100 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16101 let mut buffers = self.buffer.read(cx).all_buffers();
16102 if let Some(buffer_id) = buffer_id {
16103 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16104 }
16105
16106 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16107 cx.background_executor().timer(debounce).await;
16108
16109 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16110 buffers
16111 .into_iter()
16112 .flat_map(|buffer| {
16113 Some(project.upgrade()?.pull_diagnostics_for_buffer(buffer, cx))
16114 })
16115 .collect::<FuturesUnordered<_>>()
16116 }) else {
16117 return;
16118 };
16119
16120 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16121 match pull_task {
16122 Ok(()) => {
16123 if editor
16124 .update_in(cx, |editor, window, cx| {
16125 editor.update_diagnostics_state(window, cx);
16126 })
16127 .is_err()
16128 {
16129 return;
16130 }
16131 }
16132 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16133 }
16134 }
16135 });
16136
16137 Some(())
16138 }
16139
16140 pub fn set_selections_from_remote(
16141 &mut self,
16142 selections: Vec<Selection<Anchor>>,
16143 pending_selection: Option<Selection<Anchor>>,
16144 window: &mut Window,
16145 cx: &mut Context<Self>,
16146 ) {
16147 let old_cursor_position = self.selections.newest_anchor().head();
16148 self.selections.change_with(cx, |s| {
16149 s.select_anchors(selections);
16150 if let Some(pending_selection) = pending_selection {
16151 s.set_pending(pending_selection, SelectMode::Character);
16152 } else {
16153 s.clear_pending();
16154 }
16155 });
16156 self.selections_did_change(false, &old_cursor_position, true, window, cx);
16157 }
16158
16159 pub fn transact(
16160 &mut self,
16161 window: &mut Window,
16162 cx: &mut Context<Self>,
16163 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16164 ) -> Option<TransactionId> {
16165 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16166 this.start_transaction_at(Instant::now(), window, cx);
16167 update(this, window, cx);
16168 this.end_transaction_at(Instant::now(), cx)
16169 })
16170 }
16171
16172 pub fn start_transaction_at(
16173 &mut self,
16174 now: Instant,
16175 window: &mut Window,
16176 cx: &mut Context<Self>,
16177 ) {
16178 self.end_selection(window, cx);
16179 if let Some(tx_id) = self
16180 .buffer
16181 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16182 {
16183 self.selection_history
16184 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16185 cx.emit(EditorEvent::TransactionBegun {
16186 transaction_id: tx_id,
16187 })
16188 }
16189 }
16190
16191 pub fn end_transaction_at(
16192 &mut self,
16193 now: Instant,
16194 cx: &mut Context<Self>,
16195 ) -> Option<TransactionId> {
16196 if let Some(transaction_id) = self
16197 .buffer
16198 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16199 {
16200 if let Some((_, end_selections)) =
16201 self.selection_history.transaction_mut(transaction_id)
16202 {
16203 *end_selections = Some(self.selections.disjoint_anchors());
16204 } else {
16205 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16206 }
16207
16208 cx.emit(EditorEvent::Edited { transaction_id });
16209 Some(transaction_id)
16210 } else {
16211 None
16212 }
16213 }
16214
16215 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16216 if self.selection_mark_mode {
16217 self.change_selections(None, window, cx, |s| {
16218 s.move_with(|_, sel| {
16219 sel.collapse_to(sel.head(), SelectionGoal::None);
16220 });
16221 })
16222 }
16223 self.selection_mark_mode = true;
16224 cx.notify();
16225 }
16226
16227 pub fn swap_selection_ends(
16228 &mut self,
16229 _: &actions::SwapSelectionEnds,
16230 window: &mut Window,
16231 cx: &mut Context<Self>,
16232 ) {
16233 self.change_selections(None, window, cx, |s| {
16234 s.move_with(|_, sel| {
16235 if sel.start != sel.end {
16236 sel.reversed = !sel.reversed
16237 }
16238 });
16239 });
16240 self.request_autoscroll(Autoscroll::newest(), cx);
16241 cx.notify();
16242 }
16243
16244 pub fn toggle_fold(
16245 &mut self,
16246 _: &actions::ToggleFold,
16247 window: &mut Window,
16248 cx: &mut Context<Self>,
16249 ) {
16250 if self.is_singleton(cx) {
16251 let selection = self.selections.newest::<Point>(cx);
16252
16253 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16254 let range = if selection.is_empty() {
16255 let point = selection.head().to_display_point(&display_map);
16256 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16257 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16258 .to_point(&display_map);
16259 start..end
16260 } else {
16261 selection.range()
16262 };
16263 if display_map.folds_in_range(range).next().is_some() {
16264 self.unfold_lines(&Default::default(), window, cx)
16265 } else {
16266 self.fold(&Default::default(), window, cx)
16267 }
16268 } else {
16269 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16270 let buffer_ids: HashSet<_> = self
16271 .selections
16272 .disjoint_anchor_ranges()
16273 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16274 .collect();
16275
16276 let should_unfold = buffer_ids
16277 .iter()
16278 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16279
16280 for buffer_id in buffer_ids {
16281 if should_unfold {
16282 self.unfold_buffer(buffer_id, cx);
16283 } else {
16284 self.fold_buffer(buffer_id, cx);
16285 }
16286 }
16287 }
16288 }
16289
16290 pub fn toggle_fold_recursive(
16291 &mut self,
16292 _: &actions::ToggleFoldRecursive,
16293 window: &mut Window,
16294 cx: &mut Context<Self>,
16295 ) {
16296 let selection = self.selections.newest::<Point>(cx);
16297
16298 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16299 let range = if selection.is_empty() {
16300 let point = selection.head().to_display_point(&display_map);
16301 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16302 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16303 .to_point(&display_map);
16304 start..end
16305 } else {
16306 selection.range()
16307 };
16308 if display_map.folds_in_range(range).next().is_some() {
16309 self.unfold_recursive(&Default::default(), window, cx)
16310 } else {
16311 self.fold_recursive(&Default::default(), window, cx)
16312 }
16313 }
16314
16315 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16316 if self.is_singleton(cx) {
16317 let mut to_fold = Vec::new();
16318 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16319 let selections = self.selections.all_adjusted(cx);
16320
16321 for selection in selections {
16322 let range = selection.range().sorted();
16323 let buffer_start_row = range.start.row;
16324
16325 if range.start.row != range.end.row {
16326 let mut found = false;
16327 let mut row = range.start.row;
16328 while row <= range.end.row {
16329 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16330 {
16331 found = true;
16332 row = crease.range().end.row + 1;
16333 to_fold.push(crease);
16334 } else {
16335 row += 1
16336 }
16337 }
16338 if found {
16339 continue;
16340 }
16341 }
16342
16343 for row in (0..=range.start.row).rev() {
16344 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16345 if crease.range().end.row >= buffer_start_row {
16346 to_fold.push(crease);
16347 if row <= range.start.row {
16348 break;
16349 }
16350 }
16351 }
16352 }
16353 }
16354
16355 self.fold_creases(to_fold, true, window, cx);
16356 } else {
16357 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16358 let buffer_ids = self
16359 .selections
16360 .disjoint_anchor_ranges()
16361 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16362 .collect::<HashSet<_>>();
16363 for buffer_id in buffer_ids {
16364 self.fold_buffer(buffer_id, cx);
16365 }
16366 }
16367 }
16368
16369 fn fold_at_level(
16370 &mut self,
16371 fold_at: &FoldAtLevel,
16372 window: &mut Window,
16373 cx: &mut Context<Self>,
16374 ) {
16375 if !self.buffer.read(cx).is_singleton() {
16376 return;
16377 }
16378
16379 let fold_at_level = fold_at.0;
16380 let snapshot = self.buffer.read(cx).snapshot(cx);
16381 let mut to_fold = Vec::new();
16382 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16383
16384 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16385 while start_row < end_row {
16386 match self
16387 .snapshot(window, cx)
16388 .crease_for_buffer_row(MultiBufferRow(start_row))
16389 {
16390 Some(crease) => {
16391 let nested_start_row = crease.range().start.row + 1;
16392 let nested_end_row = crease.range().end.row;
16393
16394 if current_level < fold_at_level {
16395 stack.push((nested_start_row, nested_end_row, current_level + 1));
16396 } else if current_level == fold_at_level {
16397 to_fold.push(crease);
16398 }
16399
16400 start_row = nested_end_row + 1;
16401 }
16402 None => start_row += 1,
16403 }
16404 }
16405 }
16406
16407 self.fold_creases(to_fold, true, window, cx);
16408 }
16409
16410 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16411 if self.buffer.read(cx).is_singleton() {
16412 let mut fold_ranges = Vec::new();
16413 let snapshot = self.buffer.read(cx).snapshot(cx);
16414
16415 for row in 0..snapshot.max_row().0 {
16416 if let Some(foldable_range) = self
16417 .snapshot(window, cx)
16418 .crease_for_buffer_row(MultiBufferRow(row))
16419 {
16420 fold_ranges.push(foldable_range);
16421 }
16422 }
16423
16424 self.fold_creases(fold_ranges, true, window, cx);
16425 } else {
16426 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16427 editor
16428 .update_in(cx, |editor, _, cx| {
16429 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16430 editor.fold_buffer(buffer_id, cx);
16431 }
16432 })
16433 .ok();
16434 });
16435 }
16436 }
16437
16438 pub fn fold_function_bodies(
16439 &mut self,
16440 _: &actions::FoldFunctionBodies,
16441 window: &mut Window,
16442 cx: &mut Context<Self>,
16443 ) {
16444 let snapshot = self.buffer.read(cx).snapshot(cx);
16445
16446 let ranges = snapshot
16447 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16448 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16449 .collect::<Vec<_>>();
16450
16451 let creases = ranges
16452 .into_iter()
16453 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16454 .collect();
16455
16456 self.fold_creases(creases, true, window, cx);
16457 }
16458
16459 pub fn fold_recursive(
16460 &mut self,
16461 _: &actions::FoldRecursive,
16462 window: &mut Window,
16463 cx: &mut Context<Self>,
16464 ) {
16465 let mut to_fold = Vec::new();
16466 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16467 let selections = self.selections.all_adjusted(cx);
16468
16469 for selection in selections {
16470 let range = selection.range().sorted();
16471 let buffer_start_row = range.start.row;
16472
16473 if range.start.row != range.end.row {
16474 let mut found = false;
16475 for row in range.start.row..=range.end.row {
16476 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16477 found = true;
16478 to_fold.push(crease);
16479 }
16480 }
16481 if found {
16482 continue;
16483 }
16484 }
16485
16486 for row in (0..=range.start.row).rev() {
16487 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16488 if crease.range().end.row >= buffer_start_row {
16489 to_fold.push(crease);
16490 } else {
16491 break;
16492 }
16493 }
16494 }
16495 }
16496
16497 self.fold_creases(to_fold, true, window, cx);
16498 }
16499
16500 pub fn fold_at(
16501 &mut self,
16502 buffer_row: MultiBufferRow,
16503 window: &mut Window,
16504 cx: &mut Context<Self>,
16505 ) {
16506 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16507
16508 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16509 let autoscroll = self
16510 .selections
16511 .all::<Point>(cx)
16512 .iter()
16513 .any(|selection| crease.range().overlaps(&selection.range()));
16514
16515 self.fold_creases(vec![crease], autoscroll, window, cx);
16516 }
16517 }
16518
16519 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16520 if self.is_singleton(cx) {
16521 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16522 let buffer = &display_map.buffer_snapshot;
16523 let selections = self.selections.all::<Point>(cx);
16524 let ranges = selections
16525 .iter()
16526 .map(|s| {
16527 let range = s.display_range(&display_map).sorted();
16528 let mut start = range.start.to_point(&display_map);
16529 let mut end = range.end.to_point(&display_map);
16530 start.column = 0;
16531 end.column = buffer.line_len(MultiBufferRow(end.row));
16532 start..end
16533 })
16534 .collect::<Vec<_>>();
16535
16536 self.unfold_ranges(&ranges, true, true, cx);
16537 } else {
16538 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16539 let buffer_ids = self
16540 .selections
16541 .disjoint_anchor_ranges()
16542 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16543 .collect::<HashSet<_>>();
16544 for buffer_id in buffer_ids {
16545 self.unfold_buffer(buffer_id, cx);
16546 }
16547 }
16548 }
16549
16550 pub fn unfold_recursive(
16551 &mut self,
16552 _: &UnfoldRecursive,
16553 _window: &mut Window,
16554 cx: &mut Context<Self>,
16555 ) {
16556 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16557 let selections = self.selections.all::<Point>(cx);
16558 let ranges = selections
16559 .iter()
16560 .map(|s| {
16561 let mut range = s.display_range(&display_map).sorted();
16562 *range.start.column_mut() = 0;
16563 *range.end.column_mut() = display_map.line_len(range.end.row());
16564 let start = range.start.to_point(&display_map);
16565 let end = range.end.to_point(&display_map);
16566 start..end
16567 })
16568 .collect::<Vec<_>>();
16569
16570 self.unfold_ranges(&ranges, true, true, cx);
16571 }
16572
16573 pub fn unfold_at(
16574 &mut self,
16575 buffer_row: MultiBufferRow,
16576 _window: &mut Window,
16577 cx: &mut Context<Self>,
16578 ) {
16579 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16580
16581 let intersection_range = Point::new(buffer_row.0, 0)
16582 ..Point::new(
16583 buffer_row.0,
16584 display_map.buffer_snapshot.line_len(buffer_row),
16585 );
16586
16587 let autoscroll = self
16588 .selections
16589 .all::<Point>(cx)
16590 .iter()
16591 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16592
16593 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16594 }
16595
16596 pub fn unfold_all(
16597 &mut self,
16598 _: &actions::UnfoldAll,
16599 _window: &mut Window,
16600 cx: &mut Context<Self>,
16601 ) {
16602 if self.buffer.read(cx).is_singleton() {
16603 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16604 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16605 } else {
16606 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16607 editor
16608 .update(cx, |editor, cx| {
16609 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16610 editor.unfold_buffer(buffer_id, cx);
16611 }
16612 })
16613 .ok();
16614 });
16615 }
16616 }
16617
16618 pub fn fold_selected_ranges(
16619 &mut self,
16620 _: &FoldSelectedRanges,
16621 window: &mut Window,
16622 cx: &mut Context<Self>,
16623 ) {
16624 let selections = self.selections.all_adjusted(cx);
16625 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16626 let ranges = selections
16627 .into_iter()
16628 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16629 .collect::<Vec<_>>();
16630 self.fold_creases(ranges, true, window, cx);
16631 }
16632
16633 pub fn fold_ranges<T: ToOffset + Clone>(
16634 &mut self,
16635 ranges: Vec<Range<T>>,
16636 auto_scroll: bool,
16637 window: &mut Window,
16638 cx: &mut Context<Self>,
16639 ) {
16640 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16641 let ranges = ranges
16642 .into_iter()
16643 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16644 .collect::<Vec<_>>();
16645 self.fold_creases(ranges, auto_scroll, window, cx);
16646 }
16647
16648 pub fn fold_creases<T: ToOffset + Clone>(
16649 &mut self,
16650 creases: Vec<Crease<T>>,
16651 auto_scroll: bool,
16652 _window: &mut Window,
16653 cx: &mut Context<Self>,
16654 ) {
16655 if creases.is_empty() {
16656 return;
16657 }
16658
16659 let mut buffers_affected = HashSet::default();
16660 let multi_buffer = self.buffer().read(cx);
16661 for crease in &creases {
16662 if let Some((_, buffer, _)) =
16663 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16664 {
16665 buffers_affected.insert(buffer.read(cx).remote_id());
16666 };
16667 }
16668
16669 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16670
16671 if auto_scroll {
16672 self.request_autoscroll(Autoscroll::fit(), cx);
16673 }
16674
16675 cx.notify();
16676
16677 self.scrollbar_marker_state.dirty = true;
16678 self.folds_did_change(cx);
16679 }
16680
16681 /// Removes any folds whose ranges intersect any of the given ranges.
16682 pub fn unfold_ranges<T: ToOffset + Clone>(
16683 &mut self,
16684 ranges: &[Range<T>],
16685 inclusive: bool,
16686 auto_scroll: bool,
16687 cx: &mut Context<Self>,
16688 ) {
16689 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16690 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16691 });
16692 self.folds_did_change(cx);
16693 }
16694
16695 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16696 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16697 return;
16698 }
16699 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16700 self.display_map.update(cx, |display_map, cx| {
16701 display_map.fold_buffers([buffer_id], cx)
16702 });
16703 cx.emit(EditorEvent::BufferFoldToggled {
16704 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16705 folded: true,
16706 });
16707 cx.notify();
16708 }
16709
16710 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16711 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16712 return;
16713 }
16714 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16715 self.display_map.update(cx, |display_map, cx| {
16716 display_map.unfold_buffers([buffer_id], cx);
16717 });
16718 cx.emit(EditorEvent::BufferFoldToggled {
16719 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16720 folded: false,
16721 });
16722 cx.notify();
16723 }
16724
16725 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16726 self.display_map.read(cx).is_buffer_folded(buffer)
16727 }
16728
16729 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16730 self.display_map.read(cx).folded_buffers()
16731 }
16732
16733 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16734 self.display_map.update(cx, |display_map, cx| {
16735 display_map.disable_header_for_buffer(buffer_id, cx);
16736 });
16737 cx.notify();
16738 }
16739
16740 /// Removes any folds with the given ranges.
16741 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16742 &mut self,
16743 ranges: &[Range<T>],
16744 type_id: TypeId,
16745 auto_scroll: bool,
16746 cx: &mut Context<Self>,
16747 ) {
16748 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16749 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16750 });
16751 self.folds_did_change(cx);
16752 }
16753
16754 fn remove_folds_with<T: ToOffset + Clone>(
16755 &mut self,
16756 ranges: &[Range<T>],
16757 auto_scroll: bool,
16758 cx: &mut Context<Self>,
16759 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16760 ) {
16761 if ranges.is_empty() {
16762 return;
16763 }
16764
16765 let mut buffers_affected = HashSet::default();
16766 let multi_buffer = self.buffer().read(cx);
16767 for range in ranges {
16768 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16769 buffers_affected.insert(buffer.read(cx).remote_id());
16770 };
16771 }
16772
16773 self.display_map.update(cx, update);
16774
16775 if auto_scroll {
16776 self.request_autoscroll(Autoscroll::fit(), cx);
16777 }
16778
16779 cx.notify();
16780 self.scrollbar_marker_state.dirty = true;
16781 self.active_indent_guides_state.dirty = true;
16782 }
16783
16784 pub fn update_fold_widths(
16785 &mut self,
16786 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16787 cx: &mut Context<Self>,
16788 ) -> bool {
16789 self.display_map
16790 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16791 }
16792
16793 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16794 self.display_map.read(cx).fold_placeholder.clone()
16795 }
16796
16797 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16798 self.buffer.update(cx, |buffer, cx| {
16799 buffer.set_all_diff_hunks_expanded(cx);
16800 });
16801 }
16802
16803 pub fn expand_all_diff_hunks(
16804 &mut self,
16805 _: &ExpandAllDiffHunks,
16806 _window: &mut Window,
16807 cx: &mut Context<Self>,
16808 ) {
16809 self.buffer.update(cx, |buffer, cx| {
16810 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16811 });
16812 }
16813
16814 pub fn toggle_selected_diff_hunks(
16815 &mut self,
16816 _: &ToggleSelectedDiffHunks,
16817 _window: &mut Window,
16818 cx: &mut Context<Self>,
16819 ) {
16820 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16821 self.toggle_diff_hunks_in_ranges(ranges, cx);
16822 }
16823
16824 pub fn diff_hunks_in_ranges<'a>(
16825 &'a self,
16826 ranges: &'a [Range<Anchor>],
16827 buffer: &'a MultiBufferSnapshot,
16828 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16829 ranges.iter().flat_map(move |range| {
16830 let end_excerpt_id = range.end.excerpt_id;
16831 let range = range.to_point(buffer);
16832 let mut peek_end = range.end;
16833 if range.end.row < buffer.max_row().0 {
16834 peek_end = Point::new(range.end.row + 1, 0);
16835 }
16836 buffer
16837 .diff_hunks_in_range(range.start..peek_end)
16838 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16839 })
16840 }
16841
16842 pub fn has_stageable_diff_hunks_in_ranges(
16843 &self,
16844 ranges: &[Range<Anchor>],
16845 snapshot: &MultiBufferSnapshot,
16846 ) -> bool {
16847 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16848 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16849 }
16850
16851 pub fn toggle_staged_selected_diff_hunks(
16852 &mut self,
16853 _: &::git::ToggleStaged,
16854 _: &mut Window,
16855 cx: &mut Context<Self>,
16856 ) {
16857 let snapshot = self.buffer.read(cx).snapshot(cx);
16858 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16859 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16860 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16861 }
16862
16863 pub fn set_render_diff_hunk_controls(
16864 &mut self,
16865 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16866 cx: &mut Context<Self>,
16867 ) {
16868 self.render_diff_hunk_controls = render_diff_hunk_controls;
16869 cx.notify();
16870 }
16871
16872 pub fn stage_and_next(
16873 &mut self,
16874 _: &::git::StageAndNext,
16875 window: &mut Window,
16876 cx: &mut Context<Self>,
16877 ) {
16878 self.do_stage_or_unstage_and_next(true, window, cx);
16879 }
16880
16881 pub fn unstage_and_next(
16882 &mut self,
16883 _: &::git::UnstageAndNext,
16884 window: &mut Window,
16885 cx: &mut Context<Self>,
16886 ) {
16887 self.do_stage_or_unstage_and_next(false, window, cx);
16888 }
16889
16890 pub fn stage_or_unstage_diff_hunks(
16891 &mut self,
16892 stage: bool,
16893 ranges: Vec<Range<Anchor>>,
16894 cx: &mut Context<Self>,
16895 ) {
16896 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16897 cx.spawn(async move |this, cx| {
16898 task.await?;
16899 this.update(cx, |this, cx| {
16900 let snapshot = this.buffer.read(cx).snapshot(cx);
16901 let chunk_by = this
16902 .diff_hunks_in_ranges(&ranges, &snapshot)
16903 .chunk_by(|hunk| hunk.buffer_id);
16904 for (buffer_id, hunks) in &chunk_by {
16905 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16906 }
16907 })
16908 })
16909 .detach_and_log_err(cx);
16910 }
16911
16912 fn save_buffers_for_ranges_if_needed(
16913 &mut self,
16914 ranges: &[Range<Anchor>],
16915 cx: &mut Context<Editor>,
16916 ) -> Task<Result<()>> {
16917 let multibuffer = self.buffer.read(cx);
16918 let snapshot = multibuffer.read(cx);
16919 let buffer_ids: HashSet<_> = ranges
16920 .iter()
16921 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16922 .collect();
16923 drop(snapshot);
16924
16925 let mut buffers = HashSet::default();
16926 for buffer_id in buffer_ids {
16927 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16928 let buffer = buffer_entity.read(cx);
16929 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16930 {
16931 buffers.insert(buffer_entity);
16932 }
16933 }
16934 }
16935
16936 if let Some(project) = &self.project {
16937 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16938 } else {
16939 Task::ready(Ok(()))
16940 }
16941 }
16942
16943 fn do_stage_or_unstage_and_next(
16944 &mut self,
16945 stage: bool,
16946 window: &mut Window,
16947 cx: &mut Context<Self>,
16948 ) {
16949 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16950
16951 if ranges.iter().any(|range| range.start != range.end) {
16952 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16953 return;
16954 }
16955
16956 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16957 let snapshot = self.snapshot(window, cx);
16958 let position = self.selections.newest::<Point>(cx).head();
16959 let mut row = snapshot
16960 .buffer_snapshot
16961 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16962 .find(|hunk| hunk.row_range.start.0 > position.row)
16963 .map(|hunk| hunk.row_range.start);
16964
16965 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16966 // Outside of the project diff editor, wrap around to the beginning.
16967 if !all_diff_hunks_expanded {
16968 row = row.or_else(|| {
16969 snapshot
16970 .buffer_snapshot
16971 .diff_hunks_in_range(Point::zero()..position)
16972 .find(|hunk| hunk.row_range.end.0 < position.row)
16973 .map(|hunk| hunk.row_range.start)
16974 });
16975 }
16976
16977 if let Some(row) = row {
16978 let destination = Point::new(row.0, 0);
16979 let autoscroll = Autoscroll::center();
16980
16981 self.unfold_ranges(&[destination..destination], false, false, cx);
16982 self.change_selections(Some(autoscroll), window, cx, |s| {
16983 s.select_ranges([destination..destination]);
16984 });
16985 }
16986 }
16987
16988 fn do_stage_or_unstage(
16989 &self,
16990 stage: bool,
16991 buffer_id: BufferId,
16992 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16993 cx: &mut App,
16994 ) -> Option<()> {
16995 let project = self.project.as_ref()?;
16996 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16997 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16998 let buffer_snapshot = buffer.read(cx).snapshot();
16999 let file_exists = buffer_snapshot
17000 .file()
17001 .is_some_and(|file| file.disk_state().exists());
17002 diff.update(cx, |diff, cx| {
17003 diff.stage_or_unstage_hunks(
17004 stage,
17005 &hunks
17006 .map(|hunk| buffer_diff::DiffHunk {
17007 buffer_range: hunk.buffer_range,
17008 diff_base_byte_range: hunk.diff_base_byte_range,
17009 secondary_status: hunk.secondary_status,
17010 range: Point::zero()..Point::zero(), // unused
17011 })
17012 .collect::<Vec<_>>(),
17013 &buffer_snapshot,
17014 file_exists,
17015 cx,
17016 )
17017 });
17018 None
17019 }
17020
17021 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17022 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17023 self.buffer
17024 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17025 }
17026
17027 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17028 self.buffer.update(cx, |buffer, cx| {
17029 let ranges = vec![Anchor::min()..Anchor::max()];
17030 if !buffer.all_diff_hunks_expanded()
17031 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17032 {
17033 buffer.collapse_diff_hunks(ranges, cx);
17034 true
17035 } else {
17036 false
17037 }
17038 })
17039 }
17040
17041 fn toggle_diff_hunks_in_ranges(
17042 &mut self,
17043 ranges: Vec<Range<Anchor>>,
17044 cx: &mut Context<Editor>,
17045 ) {
17046 self.buffer.update(cx, |buffer, cx| {
17047 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17048 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17049 })
17050 }
17051
17052 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17053 self.buffer.update(cx, |buffer, cx| {
17054 let snapshot = buffer.snapshot(cx);
17055 let excerpt_id = range.end.excerpt_id;
17056 let point_range = range.to_point(&snapshot);
17057 let expand = !buffer.single_hunk_is_expanded(range, cx);
17058 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17059 })
17060 }
17061
17062 pub(crate) fn apply_all_diff_hunks(
17063 &mut self,
17064 _: &ApplyAllDiffHunks,
17065 window: &mut Window,
17066 cx: &mut Context<Self>,
17067 ) {
17068 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17069
17070 let buffers = self.buffer.read(cx).all_buffers();
17071 for branch_buffer in buffers {
17072 branch_buffer.update(cx, |branch_buffer, cx| {
17073 branch_buffer.merge_into_base(Vec::new(), cx);
17074 });
17075 }
17076
17077 if let Some(project) = self.project.clone() {
17078 self.save(true, project, window, cx).detach_and_log_err(cx);
17079 }
17080 }
17081
17082 pub(crate) fn apply_selected_diff_hunks(
17083 &mut self,
17084 _: &ApplyDiffHunk,
17085 window: &mut Window,
17086 cx: &mut Context<Self>,
17087 ) {
17088 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17089 let snapshot = self.snapshot(window, cx);
17090 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17091 let mut ranges_by_buffer = HashMap::default();
17092 self.transact(window, cx, |editor, _window, cx| {
17093 for hunk in hunks {
17094 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17095 ranges_by_buffer
17096 .entry(buffer.clone())
17097 .or_insert_with(Vec::new)
17098 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17099 }
17100 }
17101
17102 for (buffer, ranges) in ranges_by_buffer {
17103 buffer.update(cx, |buffer, cx| {
17104 buffer.merge_into_base(ranges, cx);
17105 });
17106 }
17107 });
17108
17109 if let Some(project) = self.project.clone() {
17110 self.save(true, project, window, cx).detach_and_log_err(cx);
17111 }
17112 }
17113
17114 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17115 if hovered != self.gutter_hovered {
17116 self.gutter_hovered = hovered;
17117 cx.notify();
17118 }
17119 }
17120
17121 pub fn insert_blocks(
17122 &mut self,
17123 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17124 autoscroll: Option<Autoscroll>,
17125 cx: &mut Context<Self>,
17126 ) -> Vec<CustomBlockId> {
17127 let blocks = self
17128 .display_map
17129 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17130 if let Some(autoscroll) = autoscroll {
17131 self.request_autoscroll(autoscroll, cx);
17132 }
17133 cx.notify();
17134 blocks
17135 }
17136
17137 pub fn resize_blocks(
17138 &mut self,
17139 heights: HashMap<CustomBlockId, u32>,
17140 autoscroll: Option<Autoscroll>,
17141 cx: &mut Context<Self>,
17142 ) {
17143 self.display_map
17144 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17145 if let Some(autoscroll) = autoscroll {
17146 self.request_autoscroll(autoscroll, cx);
17147 }
17148 cx.notify();
17149 }
17150
17151 pub fn replace_blocks(
17152 &mut self,
17153 renderers: HashMap<CustomBlockId, RenderBlock>,
17154 autoscroll: Option<Autoscroll>,
17155 cx: &mut Context<Self>,
17156 ) {
17157 self.display_map
17158 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17159 if let Some(autoscroll) = autoscroll {
17160 self.request_autoscroll(autoscroll, cx);
17161 }
17162 cx.notify();
17163 }
17164
17165 pub fn remove_blocks(
17166 &mut self,
17167 block_ids: HashSet<CustomBlockId>,
17168 autoscroll: Option<Autoscroll>,
17169 cx: &mut Context<Self>,
17170 ) {
17171 self.display_map.update(cx, |display_map, cx| {
17172 display_map.remove_blocks(block_ids, cx)
17173 });
17174 if let Some(autoscroll) = autoscroll {
17175 self.request_autoscroll(autoscroll, cx);
17176 }
17177 cx.notify();
17178 }
17179
17180 pub fn row_for_block(
17181 &self,
17182 block_id: CustomBlockId,
17183 cx: &mut Context<Self>,
17184 ) -> Option<DisplayRow> {
17185 self.display_map
17186 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17187 }
17188
17189 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17190 self.focused_block = Some(focused_block);
17191 }
17192
17193 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17194 self.focused_block.take()
17195 }
17196
17197 pub fn insert_creases(
17198 &mut self,
17199 creases: impl IntoIterator<Item = Crease<Anchor>>,
17200 cx: &mut Context<Self>,
17201 ) -> Vec<CreaseId> {
17202 self.display_map
17203 .update(cx, |map, cx| map.insert_creases(creases, cx))
17204 }
17205
17206 pub fn remove_creases(
17207 &mut self,
17208 ids: impl IntoIterator<Item = CreaseId>,
17209 cx: &mut Context<Self>,
17210 ) -> Vec<(CreaseId, Range<Anchor>)> {
17211 self.display_map
17212 .update(cx, |map, cx| map.remove_creases(ids, cx))
17213 }
17214
17215 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17216 self.display_map
17217 .update(cx, |map, cx| map.snapshot(cx))
17218 .longest_row()
17219 }
17220
17221 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17222 self.display_map
17223 .update(cx, |map, cx| map.snapshot(cx))
17224 .max_point()
17225 }
17226
17227 pub fn text(&self, cx: &App) -> String {
17228 self.buffer.read(cx).read(cx).text()
17229 }
17230
17231 pub fn is_empty(&self, cx: &App) -> bool {
17232 self.buffer.read(cx).read(cx).is_empty()
17233 }
17234
17235 pub fn text_option(&self, cx: &App) -> Option<String> {
17236 let text = self.text(cx);
17237 let text = text.trim();
17238
17239 if text.is_empty() {
17240 return None;
17241 }
17242
17243 Some(text.to_string())
17244 }
17245
17246 pub fn set_text(
17247 &mut self,
17248 text: impl Into<Arc<str>>,
17249 window: &mut Window,
17250 cx: &mut Context<Self>,
17251 ) {
17252 self.transact(window, cx, |this, _, cx| {
17253 this.buffer
17254 .read(cx)
17255 .as_singleton()
17256 .expect("you can only call set_text on editors for singleton buffers")
17257 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17258 });
17259 }
17260
17261 pub fn display_text(&self, cx: &mut App) -> String {
17262 self.display_map
17263 .update(cx, |map, cx| map.snapshot(cx))
17264 .text()
17265 }
17266
17267 fn create_minimap(
17268 &self,
17269 minimap_settings: MinimapSettings,
17270 window: &mut Window,
17271 cx: &mut Context<Self>,
17272 ) -> Option<Entity<Self>> {
17273 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17274 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17275 }
17276
17277 fn initialize_new_minimap(
17278 &self,
17279 minimap_settings: MinimapSettings,
17280 window: &mut Window,
17281 cx: &mut Context<Self>,
17282 ) -> Entity<Self> {
17283 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17284
17285 let mut minimap = Editor::new_internal(
17286 EditorMode::Minimap {
17287 parent: cx.weak_entity(),
17288 },
17289 self.buffer.clone(),
17290 self.project.clone(),
17291 Some(self.display_map.clone()),
17292 window,
17293 cx,
17294 );
17295 minimap.scroll_manager.clone_state(&self.scroll_manager);
17296 minimap.set_text_style_refinement(TextStyleRefinement {
17297 font_size: Some(MINIMAP_FONT_SIZE),
17298 font_weight: Some(MINIMAP_FONT_WEIGHT),
17299 ..Default::default()
17300 });
17301 minimap.update_minimap_configuration(minimap_settings, cx);
17302 cx.new(|_| minimap)
17303 }
17304
17305 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17306 let current_line_highlight = minimap_settings
17307 .current_line_highlight
17308 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17309 self.set_current_line_highlight(Some(current_line_highlight));
17310 }
17311
17312 pub fn minimap(&self) -> Option<&Entity<Self>> {
17313 self.minimap
17314 .as_ref()
17315 .filter(|_| self.minimap_visibility.visible())
17316 }
17317
17318 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17319 let mut wrap_guides = smallvec![];
17320
17321 if self.show_wrap_guides == Some(false) {
17322 return wrap_guides;
17323 }
17324
17325 let settings = self.buffer.read(cx).language_settings(cx);
17326 if settings.show_wrap_guides {
17327 match self.soft_wrap_mode(cx) {
17328 SoftWrap::Column(soft_wrap) => {
17329 wrap_guides.push((soft_wrap as usize, true));
17330 }
17331 SoftWrap::Bounded(soft_wrap) => {
17332 wrap_guides.push((soft_wrap as usize, true));
17333 }
17334 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17335 }
17336 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17337 }
17338
17339 wrap_guides
17340 }
17341
17342 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17343 let settings = self.buffer.read(cx).language_settings(cx);
17344 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17345 match mode {
17346 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17347 SoftWrap::None
17348 }
17349 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17350 language_settings::SoftWrap::PreferredLineLength => {
17351 SoftWrap::Column(settings.preferred_line_length)
17352 }
17353 language_settings::SoftWrap::Bounded => {
17354 SoftWrap::Bounded(settings.preferred_line_length)
17355 }
17356 }
17357 }
17358
17359 pub fn set_soft_wrap_mode(
17360 &mut self,
17361 mode: language_settings::SoftWrap,
17362
17363 cx: &mut Context<Self>,
17364 ) {
17365 self.soft_wrap_mode_override = Some(mode);
17366 cx.notify();
17367 }
17368
17369 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17370 self.hard_wrap = hard_wrap;
17371 cx.notify();
17372 }
17373
17374 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17375 self.text_style_refinement = Some(style);
17376 }
17377
17378 /// called by the Element so we know what style we were most recently rendered with.
17379 pub(crate) fn set_style(
17380 &mut self,
17381 style: EditorStyle,
17382 window: &mut Window,
17383 cx: &mut Context<Self>,
17384 ) {
17385 // We intentionally do not inform the display map about the minimap style
17386 // so that wrapping is not recalculated and stays consistent for the editor
17387 // and its linked minimap.
17388 if !self.mode.is_minimap() {
17389 let rem_size = window.rem_size();
17390 self.display_map.update(cx, |map, cx| {
17391 map.set_font(
17392 style.text.font(),
17393 style.text.font_size.to_pixels(rem_size),
17394 cx,
17395 )
17396 });
17397 }
17398 self.style = Some(style);
17399 }
17400
17401 pub fn style(&self) -> Option<&EditorStyle> {
17402 self.style.as_ref()
17403 }
17404
17405 // Called by the element. This method is not designed to be called outside of the editor
17406 // element's layout code because it does not notify when rewrapping is computed synchronously.
17407 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17408 self.display_map
17409 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17410 }
17411
17412 pub fn set_soft_wrap(&mut self) {
17413 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17414 }
17415
17416 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17417 if self.soft_wrap_mode_override.is_some() {
17418 self.soft_wrap_mode_override.take();
17419 } else {
17420 let soft_wrap = match self.soft_wrap_mode(cx) {
17421 SoftWrap::GitDiff => return,
17422 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17423 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17424 language_settings::SoftWrap::None
17425 }
17426 };
17427 self.soft_wrap_mode_override = Some(soft_wrap);
17428 }
17429 cx.notify();
17430 }
17431
17432 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17433 let Some(workspace) = self.workspace() else {
17434 return;
17435 };
17436 let fs = workspace.read(cx).app_state().fs.clone();
17437 let current_show = TabBarSettings::get_global(cx).show;
17438 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17439 setting.show = Some(!current_show);
17440 });
17441 }
17442
17443 pub fn toggle_indent_guides(
17444 &mut self,
17445 _: &ToggleIndentGuides,
17446 _: &mut Window,
17447 cx: &mut Context<Self>,
17448 ) {
17449 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17450 self.buffer
17451 .read(cx)
17452 .language_settings(cx)
17453 .indent_guides
17454 .enabled
17455 });
17456 self.show_indent_guides = Some(!currently_enabled);
17457 cx.notify();
17458 }
17459
17460 fn should_show_indent_guides(&self) -> Option<bool> {
17461 self.show_indent_guides
17462 }
17463
17464 pub fn toggle_line_numbers(
17465 &mut self,
17466 _: &ToggleLineNumbers,
17467 _: &mut Window,
17468 cx: &mut Context<Self>,
17469 ) {
17470 let mut editor_settings = EditorSettings::get_global(cx).clone();
17471 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17472 EditorSettings::override_global(editor_settings, cx);
17473 }
17474
17475 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17476 if let Some(show_line_numbers) = self.show_line_numbers {
17477 return show_line_numbers;
17478 }
17479 EditorSettings::get_global(cx).gutter.line_numbers
17480 }
17481
17482 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17483 self.use_relative_line_numbers
17484 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17485 }
17486
17487 pub fn toggle_relative_line_numbers(
17488 &mut self,
17489 _: &ToggleRelativeLineNumbers,
17490 _: &mut Window,
17491 cx: &mut Context<Self>,
17492 ) {
17493 let is_relative = self.should_use_relative_line_numbers(cx);
17494 self.set_relative_line_number(Some(!is_relative), cx)
17495 }
17496
17497 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17498 self.use_relative_line_numbers = is_relative;
17499 cx.notify();
17500 }
17501
17502 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17503 self.show_gutter = show_gutter;
17504 cx.notify();
17505 }
17506
17507 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17508 self.show_scrollbars = ScrollbarAxes {
17509 horizontal: show,
17510 vertical: show,
17511 };
17512 cx.notify();
17513 }
17514
17515 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17516 self.show_scrollbars.vertical = show;
17517 cx.notify();
17518 }
17519
17520 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17521 self.show_scrollbars.horizontal = show;
17522 cx.notify();
17523 }
17524
17525 pub fn set_minimap_visibility(
17526 &mut self,
17527 minimap_visibility: MinimapVisibility,
17528 window: &mut Window,
17529 cx: &mut Context<Self>,
17530 ) {
17531 if self.minimap_visibility != minimap_visibility {
17532 if minimap_visibility.visible() && self.minimap.is_none() {
17533 let minimap_settings = EditorSettings::get_global(cx).minimap;
17534 self.minimap =
17535 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17536 }
17537 self.minimap_visibility = minimap_visibility;
17538 cx.notify();
17539 }
17540 }
17541
17542 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17543 self.set_show_scrollbars(false, cx);
17544 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17545 }
17546
17547 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17548 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17549 }
17550
17551 /// Normally the text in full mode and auto height editors is padded on the
17552 /// left side by roughly half a character width for improved hit testing.
17553 ///
17554 /// Use this method to disable this for cases where this is not wanted (e.g.
17555 /// if you want to align the editor text with some other text above or below)
17556 /// or if you want to add this padding to single-line editors.
17557 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17558 self.offset_content = offset_content;
17559 cx.notify();
17560 }
17561
17562 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17563 self.show_line_numbers = Some(show_line_numbers);
17564 cx.notify();
17565 }
17566
17567 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17568 self.disable_expand_excerpt_buttons = true;
17569 cx.notify();
17570 }
17571
17572 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17573 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17574 cx.notify();
17575 }
17576
17577 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17578 self.show_code_actions = Some(show_code_actions);
17579 cx.notify();
17580 }
17581
17582 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17583 self.show_runnables = Some(show_runnables);
17584 cx.notify();
17585 }
17586
17587 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17588 self.show_breakpoints = Some(show_breakpoints);
17589 cx.notify();
17590 }
17591
17592 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17593 if self.display_map.read(cx).masked != masked {
17594 self.display_map.update(cx, |map, _| map.masked = masked);
17595 }
17596 cx.notify()
17597 }
17598
17599 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17600 self.show_wrap_guides = Some(show_wrap_guides);
17601 cx.notify();
17602 }
17603
17604 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17605 self.show_indent_guides = Some(show_indent_guides);
17606 cx.notify();
17607 }
17608
17609 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17610 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17611 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17612 if let Some(dir) = file.abs_path(cx).parent() {
17613 return Some(dir.to_owned());
17614 }
17615 }
17616
17617 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17618 return Some(project_path.path.to_path_buf());
17619 }
17620 }
17621
17622 None
17623 }
17624
17625 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17626 self.active_excerpt(cx)?
17627 .1
17628 .read(cx)
17629 .file()
17630 .and_then(|f| f.as_local())
17631 }
17632
17633 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17634 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17635 let buffer = buffer.read(cx);
17636 if let Some(project_path) = buffer.project_path(cx) {
17637 let project = self.project.as_ref()?.read(cx);
17638 project.absolute_path(&project_path, cx)
17639 } else {
17640 buffer
17641 .file()
17642 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17643 }
17644 })
17645 }
17646
17647 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17648 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17649 let project_path = buffer.read(cx).project_path(cx)?;
17650 let project = self.project.as_ref()?.read(cx);
17651 let entry = project.entry_for_path(&project_path, cx)?;
17652 let path = entry.path.to_path_buf();
17653 Some(path)
17654 })
17655 }
17656
17657 pub fn reveal_in_finder(
17658 &mut self,
17659 _: &RevealInFileManager,
17660 _window: &mut Window,
17661 cx: &mut Context<Self>,
17662 ) {
17663 if let Some(target) = self.target_file(cx) {
17664 cx.reveal_path(&target.abs_path(cx));
17665 }
17666 }
17667
17668 pub fn copy_path(
17669 &mut self,
17670 _: &zed_actions::workspace::CopyPath,
17671 _window: &mut Window,
17672 cx: &mut Context<Self>,
17673 ) {
17674 if let Some(path) = self.target_file_abs_path(cx) {
17675 if let Some(path) = path.to_str() {
17676 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17677 }
17678 }
17679 }
17680
17681 pub fn copy_relative_path(
17682 &mut self,
17683 _: &zed_actions::workspace::CopyRelativePath,
17684 _window: &mut Window,
17685 cx: &mut Context<Self>,
17686 ) {
17687 if let Some(path) = self.target_file_path(cx) {
17688 if let Some(path) = path.to_str() {
17689 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17690 }
17691 }
17692 }
17693
17694 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17695 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17696 buffer.read(cx).project_path(cx)
17697 } else {
17698 None
17699 }
17700 }
17701
17702 // Returns true if the editor handled a go-to-line request
17703 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17704 maybe!({
17705 let breakpoint_store = self.breakpoint_store.as_ref()?;
17706
17707 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17708 else {
17709 self.clear_row_highlights::<ActiveDebugLine>();
17710 return None;
17711 };
17712
17713 let position = active_stack_frame.position;
17714 let buffer_id = position.buffer_id?;
17715 let snapshot = self
17716 .project
17717 .as_ref()?
17718 .read(cx)
17719 .buffer_for_id(buffer_id, cx)?
17720 .read(cx)
17721 .snapshot();
17722
17723 let mut handled = false;
17724 for (id, ExcerptRange { context, .. }) in
17725 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17726 {
17727 if context.start.cmp(&position, &snapshot).is_ge()
17728 || context.end.cmp(&position, &snapshot).is_lt()
17729 {
17730 continue;
17731 }
17732 let snapshot = self.buffer.read(cx).snapshot(cx);
17733 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17734
17735 handled = true;
17736 self.clear_row_highlights::<ActiveDebugLine>();
17737
17738 self.go_to_line::<ActiveDebugLine>(
17739 multibuffer_anchor,
17740 Some(cx.theme().colors().editor_debugger_active_line_background),
17741 window,
17742 cx,
17743 );
17744
17745 cx.notify();
17746 }
17747
17748 handled.then_some(())
17749 })
17750 .is_some()
17751 }
17752
17753 pub fn copy_file_name_without_extension(
17754 &mut self,
17755 _: &CopyFileNameWithoutExtension,
17756 _: &mut Window,
17757 cx: &mut Context<Self>,
17758 ) {
17759 if let Some(file) = self.target_file(cx) {
17760 if let Some(file_stem) = file.path().file_stem() {
17761 if let Some(name) = file_stem.to_str() {
17762 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17763 }
17764 }
17765 }
17766 }
17767
17768 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17769 if let Some(file) = self.target_file(cx) {
17770 if let Some(file_name) = file.path().file_name() {
17771 if let Some(name) = file_name.to_str() {
17772 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17773 }
17774 }
17775 }
17776 }
17777
17778 pub fn toggle_git_blame(
17779 &mut self,
17780 _: &::git::Blame,
17781 window: &mut Window,
17782 cx: &mut Context<Self>,
17783 ) {
17784 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17785
17786 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17787 self.start_git_blame(true, window, cx);
17788 }
17789
17790 cx.notify();
17791 }
17792
17793 pub fn toggle_git_blame_inline(
17794 &mut self,
17795 _: &ToggleGitBlameInline,
17796 window: &mut Window,
17797 cx: &mut Context<Self>,
17798 ) {
17799 self.toggle_git_blame_inline_internal(true, window, cx);
17800 cx.notify();
17801 }
17802
17803 pub fn open_git_blame_commit(
17804 &mut self,
17805 _: &OpenGitBlameCommit,
17806 window: &mut Window,
17807 cx: &mut Context<Self>,
17808 ) {
17809 self.open_git_blame_commit_internal(window, cx);
17810 }
17811
17812 fn open_git_blame_commit_internal(
17813 &mut self,
17814 window: &mut Window,
17815 cx: &mut Context<Self>,
17816 ) -> Option<()> {
17817 let blame = self.blame.as_ref()?;
17818 let snapshot = self.snapshot(window, cx);
17819 let cursor = self.selections.newest::<Point>(cx).head();
17820 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17821 let blame_entry = blame
17822 .update(cx, |blame, cx| {
17823 blame
17824 .blame_for_rows(
17825 &[RowInfo {
17826 buffer_id: Some(buffer.remote_id()),
17827 buffer_row: Some(point.row),
17828 ..Default::default()
17829 }],
17830 cx,
17831 )
17832 .next()
17833 })
17834 .flatten()?;
17835 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17836 let repo = blame.read(cx).repository(cx)?;
17837 let workspace = self.workspace()?.downgrade();
17838 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17839 None
17840 }
17841
17842 pub fn git_blame_inline_enabled(&self) -> bool {
17843 self.git_blame_inline_enabled
17844 }
17845
17846 pub fn toggle_selection_menu(
17847 &mut self,
17848 _: &ToggleSelectionMenu,
17849 _: &mut Window,
17850 cx: &mut Context<Self>,
17851 ) {
17852 self.show_selection_menu = self
17853 .show_selection_menu
17854 .map(|show_selections_menu| !show_selections_menu)
17855 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17856
17857 cx.notify();
17858 }
17859
17860 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17861 self.show_selection_menu
17862 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17863 }
17864
17865 fn start_git_blame(
17866 &mut self,
17867 user_triggered: bool,
17868 window: &mut Window,
17869 cx: &mut Context<Self>,
17870 ) {
17871 if let Some(project) = self.project.as_ref() {
17872 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17873 return;
17874 };
17875
17876 if buffer.read(cx).file().is_none() {
17877 return;
17878 }
17879
17880 let focused = self.focus_handle(cx).contains_focused(window, cx);
17881
17882 let project = project.clone();
17883 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17884 self.blame_subscription =
17885 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17886 self.blame = Some(blame);
17887 }
17888 }
17889
17890 fn toggle_git_blame_inline_internal(
17891 &mut self,
17892 user_triggered: bool,
17893 window: &mut Window,
17894 cx: &mut Context<Self>,
17895 ) {
17896 if self.git_blame_inline_enabled {
17897 self.git_blame_inline_enabled = false;
17898 self.show_git_blame_inline = false;
17899 self.show_git_blame_inline_delay_task.take();
17900 } else {
17901 self.git_blame_inline_enabled = true;
17902 self.start_git_blame_inline(user_triggered, window, cx);
17903 }
17904
17905 cx.notify();
17906 }
17907
17908 fn start_git_blame_inline(
17909 &mut self,
17910 user_triggered: bool,
17911 window: &mut Window,
17912 cx: &mut Context<Self>,
17913 ) {
17914 self.start_git_blame(user_triggered, window, cx);
17915
17916 if ProjectSettings::get_global(cx)
17917 .git
17918 .inline_blame_delay()
17919 .is_some()
17920 {
17921 self.start_inline_blame_timer(window, cx);
17922 } else {
17923 self.show_git_blame_inline = true
17924 }
17925 }
17926
17927 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17928 self.blame.as_ref()
17929 }
17930
17931 pub fn show_git_blame_gutter(&self) -> bool {
17932 self.show_git_blame_gutter
17933 }
17934
17935 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17936 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17937 }
17938
17939 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17940 self.show_git_blame_inline
17941 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17942 && !self.newest_selection_head_on_empty_line(cx)
17943 && self.has_blame_entries(cx)
17944 }
17945
17946 fn has_blame_entries(&self, cx: &App) -> bool {
17947 self.blame()
17948 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17949 }
17950
17951 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17952 let cursor_anchor = self.selections.newest_anchor().head();
17953
17954 let snapshot = self.buffer.read(cx).snapshot(cx);
17955 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17956
17957 snapshot.line_len(buffer_row) == 0
17958 }
17959
17960 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17961 let buffer_and_selection = maybe!({
17962 let selection = self.selections.newest::<Point>(cx);
17963 let selection_range = selection.range();
17964
17965 let multi_buffer = self.buffer().read(cx);
17966 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17967 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17968
17969 let (buffer, range, _) = if selection.reversed {
17970 buffer_ranges.first()
17971 } else {
17972 buffer_ranges.last()
17973 }?;
17974
17975 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17976 ..text::ToPoint::to_point(&range.end, &buffer).row;
17977 Some((
17978 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
17979 selection,
17980 ))
17981 });
17982
17983 let Some((buffer, selection)) = buffer_and_selection else {
17984 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
17985 };
17986
17987 let Some(project) = self.project.as_ref() else {
17988 return Task::ready(Err(anyhow!("editor does not have project")));
17989 };
17990
17991 project.update(cx, |project, cx| {
17992 project.get_permalink_to_line(&buffer, selection, cx)
17993 })
17994 }
17995
17996 pub fn copy_permalink_to_line(
17997 &mut self,
17998 _: &CopyPermalinkToLine,
17999 window: &mut Window,
18000 cx: &mut Context<Self>,
18001 ) {
18002 let permalink_task = self.get_permalink_to_line(cx);
18003 let workspace = self.workspace();
18004
18005 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18006 Ok(permalink) => {
18007 cx.update(|_, cx| {
18008 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18009 })
18010 .ok();
18011 }
18012 Err(err) => {
18013 let message = format!("Failed to copy permalink: {err}");
18014
18015 anyhow::Result::<()>::Err(err).log_err();
18016
18017 if let Some(workspace) = workspace {
18018 workspace
18019 .update_in(cx, |workspace, _, cx| {
18020 struct CopyPermalinkToLine;
18021
18022 workspace.show_toast(
18023 Toast::new(
18024 NotificationId::unique::<CopyPermalinkToLine>(),
18025 message,
18026 ),
18027 cx,
18028 )
18029 })
18030 .ok();
18031 }
18032 }
18033 })
18034 .detach();
18035 }
18036
18037 pub fn copy_file_location(
18038 &mut self,
18039 _: &CopyFileLocation,
18040 _: &mut Window,
18041 cx: &mut Context<Self>,
18042 ) {
18043 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18044 if let Some(file) = self.target_file(cx) {
18045 if let Some(path) = file.path().to_str() {
18046 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18047 }
18048 }
18049 }
18050
18051 pub fn open_permalink_to_line(
18052 &mut self,
18053 _: &OpenPermalinkToLine,
18054 window: &mut Window,
18055 cx: &mut Context<Self>,
18056 ) {
18057 let permalink_task = self.get_permalink_to_line(cx);
18058 let workspace = self.workspace();
18059
18060 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18061 Ok(permalink) => {
18062 cx.update(|_, cx| {
18063 cx.open_url(permalink.as_ref());
18064 })
18065 .ok();
18066 }
18067 Err(err) => {
18068 let message = format!("Failed to open permalink: {err}");
18069
18070 anyhow::Result::<()>::Err(err).log_err();
18071
18072 if let Some(workspace) = workspace {
18073 workspace
18074 .update(cx, |workspace, cx| {
18075 struct OpenPermalinkToLine;
18076
18077 workspace.show_toast(
18078 Toast::new(
18079 NotificationId::unique::<OpenPermalinkToLine>(),
18080 message,
18081 ),
18082 cx,
18083 )
18084 })
18085 .ok();
18086 }
18087 }
18088 })
18089 .detach();
18090 }
18091
18092 pub fn insert_uuid_v4(
18093 &mut self,
18094 _: &InsertUuidV4,
18095 window: &mut Window,
18096 cx: &mut Context<Self>,
18097 ) {
18098 self.insert_uuid(UuidVersion::V4, window, cx);
18099 }
18100
18101 pub fn insert_uuid_v7(
18102 &mut self,
18103 _: &InsertUuidV7,
18104 window: &mut Window,
18105 cx: &mut Context<Self>,
18106 ) {
18107 self.insert_uuid(UuidVersion::V7, window, cx);
18108 }
18109
18110 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18111 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
18112 self.transact(window, cx, |this, window, cx| {
18113 let edits = this
18114 .selections
18115 .all::<Point>(cx)
18116 .into_iter()
18117 .map(|selection| {
18118 let uuid = match version {
18119 UuidVersion::V4 => uuid::Uuid::new_v4(),
18120 UuidVersion::V7 => uuid::Uuid::now_v7(),
18121 };
18122
18123 (selection.range(), uuid.to_string())
18124 });
18125 this.edit(edits, cx);
18126 this.refresh_inline_completion(true, false, window, cx);
18127 });
18128 }
18129
18130 pub fn open_selections_in_multibuffer(
18131 &mut self,
18132 _: &OpenSelectionsInMultibuffer,
18133 window: &mut Window,
18134 cx: &mut Context<Self>,
18135 ) {
18136 let multibuffer = self.buffer.read(cx);
18137
18138 let Some(buffer) = multibuffer.as_singleton() else {
18139 return;
18140 };
18141
18142 let Some(workspace) = self.workspace() else {
18143 return;
18144 };
18145
18146 let locations = self
18147 .selections
18148 .disjoint_anchors()
18149 .iter()
18150 .map(|range| Location {
18151 buffer: buffer.clone(),
18152 range: range.start.text_anchor..range.end.text_anchor,
18153 })
18154 .collect::<Vec<_>>();
18155
18156 let title = multibuffer.title(cx).to_string();
18157
18158 cx.spawn_in(window, async move |_, cx| {
18159 workspace.update_in(cx, |workspace, window, cx| {
18160 Self::open_locations_in_multibuffer(
18161 workspace,
18162 locations,
18163 format!("Selections for '{title}'"),
18164 false,
18165 MultibufferSelectionMode::All,
18166 window,
18167 cx,
18168 );
18169 })
18170 })
18171 .detach();
18172 }
18173
18174 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18175 /// last highlight added will be used.
18176 ///
18177 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18178 pub fn highlight_rows<T: 'static>(
18179 &mut self,
18180 range: Range<Anchor>,
18181 color: Hsla,
18182 options: RowHighlightOptions,
18183 cx: &mut Context<Self>,
18184 ) {
18185 let snapshot = self.buffer().read(cx).snapshot(cx);
18186 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18187 let ix = row_highlights.binary_search_by(|highlight| {
18188 Ordering::Equal
18189 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18190 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18191 });
18192
18193 if let Err(mut ix) = ix {
18194 let index = post_inc(&mut self.highlight_order);
18195
18196 // If this range intersects with the preceding highlight, then merge it with
18197 // the preceding highlight. Otherwise insert a new highlight.
18198 let mut merged = false;
18199 if ix > 0 {
18200 let prev_highlight = &mut row_highlights[ix - 1];
18201 if prev_highlight
18202 .range
18203 .end
18204 .cmp(&range.start, &snapshot)
18205 .is_ge()
18206 {
18207 ix -= 1;
18208 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18209 prev_highlight.range.end = range.end;
18210 }
18211 merged = true;
18212 prev_highlight.index = index;
18213 prev_highlight.color = color;
18214 prev_highlight.options = options;
18215 }
18216 }
18217
18218 if !merged {
18219 row_highlights.insert(
18220 ix,
18221 RowHighlight {
18222 range: range.clone(),
18223 index,
18224 color,
18225 options,
18226 type_id: TypeId::of::<T>(),
18227 },
18228 );
18229 }
18230
18231 // If any of the following highlights intersect with this one, merge them.
18232 while let Some(next_highlight) = row_highlights.get(ix + 1) {
18233 let highlight = &row_highlights[ix];
18234 if next_highlight
18235 .range
18236 .start
18237 .cmp(&highlight.range.end, &snapshot)
18238 .is_le()
18239 {
18240 if next_highlight
18241 .range
18242 .end
18243 .cmp(&highlight.range.end, &snapshot)
18244 .is_gt()
18245 {
18246 row_highlights[ix].range.end = next_highlight.range.end;
18247 }
18248 row_highlights.remove(ix + 1);
18249 } else {
18250 break;
18251 }
18252 }
18253 }
18254 }
18255
18256 /// Remove any highlighted row ranges of the given type that intersect the
18257 /// given ranges.
18258 pub fn remove_highlighted_rows<T: 'static>(
18259 &mut self,
18260 ranges_to_remove: Vec<Range<Anchor>>,
18261 cx: &mut Context<Self>,
18262 ) {
18263 let snapshot = self.buffer().read(cx).snapshot(cx);
18264 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18265 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18266 row_highlights.retain(|highlight| {
18267 while let Some(range_to_remove) = ranges_to_remove.peek() {
18268 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18269 Ordering::Less | Ordering::Equal => {
18270 ranges_to_remove.next();
18271 }
18272 Ordering::Greater => {
18273 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18274 Ordering::Less | Ordering::Equal => {
18275 return false;
18276 }
18277 Ordering::Greater => break,
18278 }
18279 }
18280 }
18281 }
18282
18283 true
18284 })
18285 }
18286
18287 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18288 pub fn clear_row_highlights<T: 'static>(&mut self) {
18289 self.highlighted_rows.remove(&TypeId::of::<T>());
18290 }
18291
18292 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18293 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18294 self.highlighted_rows
18295 .get(&TypeId::of::<T>())
18296 .map_or(&[] as &[_], |vec| vec.as_slice())
18297 .iter()
18298 .map(|highlight| (highlight.range.clone(), highlight.color))
18299 }
18300
18301 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18302 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18303 /// Allows to ignore certain kinds of highlights.
18304 pub fn highlighted_display_rows(
18305 &self,
18306 window: &mut Window,
18307 cx: &mut App,
18308 ) -> BTreeMap<DisplayRow, LineHighlight> {
18309 let snapshot = self.snapshot(window, cx);
18310 let mut used_highlight_orders = HashMap::default();
18311 self.highlighted_rows
18312 .iter()
18313 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18314 .fold(
18315 BTreeMap::<DisplayRow, LineHighlight>::new(),
18316 |mut unique_rows, highlight| {
18317 let start = highlight.range.start.to_display_point(&snapshot);
18318 let end = highlight.range.end.to_display_point(&snapshot);
18319 let start_row = start.row().0;
18320 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18321 && end.column() == 0
18322 {
18323 end.row().0.saturating_sub(1)
18324 } else {
18325 end.row().0
18326 };
18327 for row in start_row..=end_row {
18328 let used_index =
18329 used_highlight_orders.entry(row).or_insert(highlight.index);
18330 if highlight.index >= *used_index {
18331 *used_index = highlight.index;
18332 unique_rows.insert(
18333 DisplayRow(row),
18334 LineHighlight {
18335 include_gutter: highlight.options.include_gutter,
18336 border: None,
18337 background: highlight.color.into(),
18338 type_id: Some(highlight.type_id),
18339 },
18340 );
18341 }
18342 }
18343 unique_rows
18344 },
18345 )
18346 }
18347
18348 pub fn highlighted_display_row_for_autoscroll(
18349 &self,
18350 snapshot: &DisplaySnapshot,
18351 ) -> Option<DisplayRow> {
18352 self.highlighted_rows
18353 .values()
18354 .flat_map(|highlighted_rows| highlighted_rows.iter())
18355 .filter_map(|highlight| {
18356 if highlight.options.autoscroll {
18357 Some(highlight.range.start.to_display_point(snapshot).row())
18358 } else {
18359 None
18360 }
18361 })
18362 .min()
18363 }
18364
18365 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18366 self.highlight_background::<SearchWithinRange>(
18367 ranges,
18368 |colors| colors.editor_document_highlight_read_background,
18369 cx,
18370 )
18371 }
18372
18373 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18374 self.breadcrumb_header = Some(new_header);
18375 }
18376
18377 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18378 self.clear_background_highlights::<SearchWithinRange>(cx);
18379 }
18380
18381 pub fn highlight_background<T: 'static>(
18382 &mut self,
18383 ranges: &[Range<Anchor>],
18384 color_fetcher: fn(&ThemeColors) -> Hsla,
18385 cx: &mut Context<Self>,
18386 ) {
18387 self.background_highlights
18388 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
18389 self.scrollbar_marker_state.dirty = true;
18390 cx.notify();
18391 }
18392
18393 pub fn clear_background_highlights<T: 'static>(
18394 &mut self,
18395 cx: &mut Context<Self>,
18396 ) -> Option<BackgroundHighlight> {
18397 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
18398 if !text_highlights.1.is_empty() {
18399 self.scrollbar_marker_state.dirty = true;
18400 cx.notify();
18401 }
18402 Some(text_highlights)
18403 }
18404
18405 pub fn highlight_gutter<T: 'static>(
18406 &mut self,
18407 ranges: impl Into<Vec<Range<Anchor>>>,
18408 color_fetcher: fn(&App) -> Hsla,
18409 cx: &mut Context<Self>,
18410 ) {
18411 self.gutter_highlights
18412 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
18413 cx.notify();
18414 }
18415
18416 pub fn clear_gutter_highlights<T: 'static>(
18417 &mut self,
18418 cx: &mut Context<Self>,
18419 ) -> Option<GutterHighlight> {
18420 cx.notify();
18421 self.gutter_highlights.remove(&TypeId::of::<T>())
18422 }
18423
18424 pub fn insert_gutter_highlight<T: 'static>(
18425 &mut self,
18426 range: Range<Anchor>,
18427 color_fetcher: fn(&App) -> Hsla,
18428 cx: &mut Context<Self>,
18429 ) {
18430 let snapshot = self.buffer().read(cx).snapshot(cx);
18431 let mut highlights = self
18432 .gutter_highlights
18433 .remove(&TypeId::of::<T>())
18434 .map(|(_, highlights)| highlights)
18435 .unwrap_or_default();
18436 let ix = highlights.binary_search_by(|highlight| {
18437 Ordering::Equal
18438 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
18439 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
18440 });
18441 if let Err(ix) = ix {
18442 highlights.insert(ix, range);
18443 }
18444 self.gutter_highlights
18445 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
18446 }
18447
18448 pub fn remove_gutter_highlights<T: 'static>(
18449 &mut self,
18450 ranges_to_remove: Vec<Range<Anchor>>,
18451 cx: &mut Context<Self>,
18452 ) {
18453 let snapshot = self.buffer().read(cx).snapshot(cx);
18454 let Some((color_fetcher, mut gutter_highlights)) =
18455 self.gutter_highlights.remove(&TypeId::of::<T>())
18456 else {
18457 return;
18458 };
18459 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18460 gutter_highlights.retain(|highlight| {
18461 while let Some(range_to_remove) = ranges_to_remove.peek() {
18462 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
18463 Ordering::Less | Ordering::Equal => {
18464 ranges_to_remove.next();
18465 }
18466 Ordering::Greater => {
18467 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
18468 Ordering::Less | Ordering::Equal => {
18469 return false;
18470 }
18471 Ordering::Greater => break,
18472 }
18473 }
18474 }
18475 }
18476
18477 true
18478 });
18479 self.gutter_highlights
18480 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
18481 }
18482
18483 #[cfg(feature = "test-support")]
18484 pub fn all_text_background_highlights(
18485 &self,
18486 window: &mut Window,
18487 cx: &mut Context<Self>,
18488 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18489 let snapshot = self.snapshot(window, cx);
18490 let buffer = &snapshot.buffer_snapshot;
18491 let start = buffer.anchor_before(0);
18492 let end = buffer.anchor_after(buffer.len());
18493 let theme = cx.theme().colors();
18494 self.background_highlights_in_range(start..end, &snapshot, theme)
18495 }
18496
18497 #[cfg(feature = "test-support")]
18498 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18499 let snapshot = self.buffer().read(cx).snapshot(cx);
18500
18501 let highlights = self
18502 .background_highlights
18503 .get(&TypeId::of::<items::BufferSearchHighlights>());
18504
18505 if let Some((_color, ranges)) = highlights {
18506 ranges
18507 .iter()
18508 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
18509 .collect_vec()
18510 } else {
18511 vec![]
18512 }
18513 }
18514
18515 fn document_highlights_for_position<'a>(
18516 &'a self,
18517 position: Anchor,
18518 buffer: &'a MultiBufferSnapshot,
18519 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
18520 let read_highlights = self
18521 .background_highlights
18522 .get(&TypeId::of::<DocumentHighlightRead>())
18523 .map(|h| &h.1);
18524 let write_highlights = self
18525 .background_highlights
18526 .get(&TypeId::of::<DocumentHighlightWrite>())
18527 .map(|h| &h.1);
18528 let left_position = position.bias_left(buffer);
18529 let right_position = position.bias_right(buffer);
18530 read_highlights
18531 .into_iter()
18532 .chain(write_highlights)
18533 .flat_map(move |ranges| {
18534 let start_ix = match ranges.binary_search_by(|probe| {
18535 let cmp = probe.end.cmp(&left_position, buffer);
18536 if cmp.is_ge() {
18537 Ordering::Greater
18538 } else {
18539 Ordering::Less
18540 }
18541 }) {
18542 Ok(i) | Err(i) => i,
18543 };
18544
18545 ranges[start_ix..]
18546 .iter()
18547 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
18548 })
18549 }
18550
18551 pub fn has_background_highlights<T: 'static>(&self) -> bool {
18552 self.background_highlights
18553 .get(&TypeId::of::<T>())
18554 .map_or(false, |(_, highlights)| !highlights.is_empty())
18555 }
18556
18557 pub fn background_highlights_in_range(
18558 &self,
18559 search_range: Range<Anchor>,
18560 display_snapshot: &DisplaySnapshot,
18561 theme: &ThemeColors,
18562 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18563 let mut results = Vec::new();
18564 for (color_fetcher, ranges) in self.background_highlights.values() {
18565 let color = color_fetcher(theme);
18566 let start_ix = match ranges.binary_search_by(|probe| {
18567 let cmp = probe
18568 .end
18569 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18570 if cmp.is_gt() {
18571 Ordering::Greater
18572 } else {
18573 Ordering::Less
18574 }
18575 }) {
18576 Ok(i) | Err(i) => i,
18577 };
18578 for range in &ranges[start_ix..] {
18579 if range
18580 .start
18581 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18582 .is_ge()
18583 {
18584 break;
18585 }
18586
18587 let start = range.start.to_display_point(display_snapshot);
18588 let end = range.end.to_display_point(display_snapshot);
18589 results.push((start..end, color))
18590 }
18591 }
18592 results
18593 }
18594
18595 pub fn background_highlight_row_ranges<T: 'static>(
18596 &self,
18597 search_range: Range<Anchor>,
18598 display_snapshot: &DisplaySnapshot,
18599 count: usize,
18600 ) -> Vec<RangeInclusive<DisplayPoint>> {
18601 let mut results = Vec::new();
18602 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
18603 return vec![];
18604 };
18605
18606 let start_ix = match ranges.binary_search_by(|probe| {
18607 let cmp = probe
18608 .end
18609 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18610 if cmp.is_gt() {
18611 Ordering::Greater
18612 } else {
18613 Ordering::Less
18614 }
18615 }) {
18616 Ok(i) | Err(i) => i,
18617 };
18618 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18619 if let (Some(start_display), Some(end_display)) = (start, end) {
18620 results.push(
18621 start_display.to_display_point(display_snapshot)
18622 ..=end_display.to_display_point(display_snapshot),
18623 );
18624 }
18625 };
18626 let mut start_row: Option<Point> = None;
18627 let mut end_row: Option<Point> = None;
18628 if ranges.len() > count {
18629 return Vec::new();
18630 }
18631 for range in &ranges[start_ix..] {
18632 if range
18633 .start
18634 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18635 .is_ge()
18636 {
18637 break;
18638 }
18639 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
18640 if let Some(current_row) = &end_row {
18641 if end.row == current_row.row {
18642 continue;
18643 }
18644 }
18645 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
18646 if start_row.is_none() {
18647 assert_eq!(end_row, None);
18648 start_row = Some(start);
18649 end_row = Some(end);
18650 continue;
18651 }
18652 if let Some(current_end) = end_row.as_mut() {
18653 if start.row > current_end.row + 1 {
18654 push_region(start_row, end_row);
18655 start_row = Some(start);
18656 end_row = Some(end);
18657 } else {
18658 // Merge two hunks.
18659 *current_end = end;
18660 }
18661 } else {
18662 unreachable!();
18663 }
18664 }
18665 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18666 push_region(start_row, end_row);
18667 results
18668 }
18669
18670 pub fn gutter_highlights_in_range(
18671 &self,
18672 search_range: Range<Anchor>,
18673 display_snapshot: &DisplaySnapshot,
18674 cx: &App,
18675 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18676 let mut results = Vec::new();
18677 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18678 let color = color_fetcher(cx);
18679 let start_ix = match ranges.binary_search_by(|probe| {
18680 let cmp = probe
18681 .end
18682 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18683 if cmp.is_gt() {
18684 Ordering::Greater
18685 } else {
18686 Ordering::Less
18687 }
18688 }) {
18689 Ok(i) | Err(i) => i,
18690 };
18691 for range in &ranges[start_ix..] {
18692 if range
18693 .start
18694 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18695 .is_ge()
18696 {
18697 break;
18698 }
18699
18700 let start = range.start.to_display_point(display_snapshot);
18701 let end = range.end.to_display_point(display_snapshot);
18702 results.push((start..end, color))
18703 }
18704 }
18705 results
18706 }
18707
18708 /// Get the text ranges corresponding to the redaction query
18709 pub fn redacted_ranges(
18710 &self,
18711 search_range: Range<Anchor>,
18712 display_snapshot: &DisplaySnapshot,
18713 cx: &App,
18714 ) -> Vec<Range<DisplayPoint>> {
18715 display_snapshot
18716 .buffer_snapshot
18717 .redacted_ranges(search_range, |file| {
18718 if let Some(file) = file {
18719 file.is_private()
18720 && EditorSettings::get(
18721 Some(SettingsLocation {
18722 worktree_id: file.worktree_id(cx),
18723 path: file.path().as_ref(),
18724 }),
18725 cx,
18726 )
18727 .redact_private_values
18728 } else {
18729 false
18730 }
18731 })
18732 .map(|range| {
18733 range.start.to_display_point(display_snapshot)
18734 ..range.end.to_display_point(display_snapshot)
18735 })
18736 .collect()
18737 }
18738
18739 pub fn highlight_text<T: 'static>(
18740 &mut self,
18741 ranges: Vec<Range<Anchor>>,
18742 style: HighlightStyle,
18743 cx: &mut Context<Self>,
18744 ) {
18745 self.display_map.update(cx, |map, _| {
18746 map.highlight_text(TypeId::of::<T>(), ranges, style)
18747 });
18748 cx.notify();
18749 }
18750
18751 pub(crate) fn highlight_inlays<T: 'static>(
18752 &mut self,
18753 highlights: Vec<InlayHighlight>,
18754 style: HighlightStyle,
18755 cx: &mut Context<Self>,
18756 ) {
18757 self.display_map.update(cx, |map, _| {
18758 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18759 });
18760 cx.notify();
18761 }
18762
18763 pub fn text_highlights<'a, T: 'static>(
18764 &'a self,
18765 cx: &'a App,
18766 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18767 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18768 }
18769
18770 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18771 let cleared = self
18772 .display_map
18773 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18774 if cleared {
18775 cx.notify();
18776 }
18777 }
18778
18779 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18780 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18781 && self.focus_handle.is_focused(window)
18782 }
18783
18784 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18785 self.show_cursor_when_unfocused = is_enabled;
18786 cx.notify();
18787 }
18788
18789 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18790 cx.notify();
18791 }
18792
18793 fn on_debug_session_event(
18794 &mut self,
18795 _session: Entity<Session>,
18796 event: &SessionEvent,
18797 cx: &mut Context<Self>,
18798 ) {
18799 match event {
18800 SessionEvent::InvalidateInlineValue => {
18801 self.refresh_inline_values(cx);
18802 }
18803 _ => {}
18804 }
18805 }
18806
18807 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18808 let Some(project) = self.project.clone() else {
18809 return;
18810 };
18811
18812 if !self.inline_value_cache.enabled {
18813 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18814 self.splice_inlays(&inlays, Vec::new(), cx);
18815 return;
18816 }
18817
18818 let current_execution_position = self
18819 .highlighted_rows
18820 .get(&TypeId::of::<ActiveDebugLine>())
18821 .and_then(|lines| lines.last().map(|line| line.range.start));
18822
18823 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18824 let inline_values = editor
18825 .update(cx, |editor, cx| {
18826 let Some(current_execution_position) = current_execution_position else {
18827 return Some(Task::ready(Ok(Vec::new())));
18828 };
18829
18830 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18831 let snapshot = buffer.snapshot(cx);
18832
18833 let excerpt = snapshot.excerpt_containing(
18834 current_execution_position..current_execution_position,
18835 )?;
18836
18837 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18838 })?;
18839
18840 let range =
18841 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18842
18843 project.inline_values(buffer, range, cx)
18844 })
18845 .ok()
18846 .flatten()?
18847 .await
18848 .context("refreshing debugger inlays")
18849 .log_err()?;
18850
18851 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18852
18853 for (buffer_id, inline_value) in inline_values
18854 .into_iter()
18855 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18856 {
18857 buffer_inline_values
18858 .entry(buffer_id)
18859 .or_default()
18860 .push(inline_value);
18861 }
18862
18863 editor
18864 .update(cx, |editor, cx| {
18865 let snapshot = editor.buffer.read(cx).snapshot(cx);
18866 let mut new_inlays = Vec::default();
18867
18868 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18869 let buffer_id = buffer_snapshot.remote_id();
18870 buffer_inline_values
18871 .get(&buffer_id)
18872 .into_iter()
18873 .flatten()
18874 .for_each(|hint| {
18875 let inlay = Inlay::debugger_hint(
18876 post_inc(&mut editor.next_inlay_id),
18877 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18878 hint.text(),
18879 );
18880
18881 new_inlays.push(inlay);
18882 });
18883 }
18884
18885 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18886 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18887
18888 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18889 })
18890 .ok()?;
18891 Some(())
18892 });
18893 }
18894
18895 fn on_buffer_event(
18896 &mut self,
18897 multibuffer: &Entity<MultiBuffer>,
18898 event: &multi_buffer::Event,
18899 window: &mut Window,
18900 cx: &mut Context<Self>,
18901 ) {
18902 match event {
18903 multi_buffer::Event::Edited {
18904 singleton_buffer_edited,
18905 edited_buffer,
18906 } => {
18907 self.scrollbar_marker_state.dirty = true;
18908 self.active_indent_guides_state.dirty = true;
18909 self.refresh_active_diagnostics(cx);
18910 self.refresh_code_actions(window, cx);
18911 self.refresh_selected_text_highlights(true, window, cx);
18912 refresh_matching_bracket_highlights(self, window, cx);
18913 if self.has_active_inline_completion() {
18914 self.update_visible_inline_completion(window, cx);
18915 }
18916 if let Some(project) = self.project.as_ref() {
18917 if let Some(edited_buffer) = edited_buffer {
18918 project.update(cx, |project, cx| {
18919 self.registered_buffers
18920 .entry(edited_buffer.read(cx).remote_id())
18921 .or_insert_with(|| {
18922 project
18923 .register_buffer_with_language_servers(&edited_buffer, cx)
18924 });
18925 });
18926 if edited_buffer.read(cx).file().is_some() {
18927 self.pull_diagnostics(
18928 Some(edited_buffer.read(cx).remote_id()),
18929 window,
18930 cx,
18931 );
18932 }
18933 }
18934 }
18935 cx.emit(EditorEvent::BufferEdited);
18936 cx.emit(SearchEvent::MatchesInvalidated);
18937 if *singleton_buffer_edited {
18938 if let Some(buffer) = edited_buffer {
18939 if buffer.read(cx).file().is_none() {
18940 cx.emit(EditorEvent::TitleChanged);
18941 }
18942 }
18943 if let Some(project) = &self.project {
18944 #[allow(clippy::mutable_key_type)]
18945 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18946 multibuffer
18947 .all_buffers()
18948 .into_iter()
18949 .filter_map(|buffer| {
18950 buffer.update(cx, |buffer, cx| {
18951 let language = buffer.language()?;
18952 let should_discard = project.update(cx, |project, cx| {
18953 project.is_local()
18954 && !project.has_language_servers_for(buffer, cx)
18955 });
18956 should_discard.not().then_some(language.clone())
18957 })
18958 })
18959 .collect::<HashSet<_>>()
18960 });
18961 if !languages_affected.is_empty() {
18962 self.refresh_inlay_hints(
18963 InlayHintRefreshReason::BufferEdited(languages_affected),
18964 cx,
18965 );
18966 }
18967 }
18968 }
18969
18970 let Some(project) = &self.project else { return };
18971 let (telemetry, is_via_ssh) = {
18972 let project = project.read(cx);
18973 let telemetry = project.client().telemetry().clone();
18974 let is_via_ssh = project.is_via_ssh();
18975 (telemetry, is_via_ssh)
18976 };
18977 refresh_linked_ranges(self, window, cx);
18978 telemetry.log_edit_event("editor", is_via_ssh);
18979 }
18980 multi_buffer::Event::ExcerptsAdded {
18981 buffer,
18982 predecessor,
18983 excerpts,
18984 } => {
18985 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18986 let buffer_id = buffer.read(cx).remote_id();
18987 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
18988 if let Some(project) = &self.project {
18989 update_uncommitted_diff_for_buffer(
18990 cx.entity(),
18991 project,
18992 [buffer.clone()],
18993 self.buffer.clone(),
18994 cx,
18995 )
18996 .detach();
18997 }
18998 }
18999 cx.emit(EditorEvent::ExcerptsAdded {
19000 buffer: buffer.clone(),
19001 predecessor: *predecessor,
19002 excerpts: excerpts.clone(),
19003 });
19004 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19005 }
19006 multi_buffer::Event::ExcerptsRemoved {
19007 ids,
19008 removed_buffer_ids,
19009 } => {
19010 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19011 let buffer = self.buffer.read(cx);
19012 self.registered_buffers
19013 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19014 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19015 cx.emit(EditorEvent::ExcerptsRemoved {
19016 ids: ids.clone(),
19017 removed_buffer_ids: removed_buffer_ids.clone(),
19018 })
19019 }
19020 multi_buffer::Event::ExcerptsEdited {
19021 excerpt_ids,
19022 buffer_ids,
19023 } => {
19024 self.display_map.update(cx, |map, cx| {
19025 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19026 });
19027 cx.emit(EditorEvent::ExcerptsEdited {
19028 ids: excerpt_ids.clone(),
19029 })
19030 }
19031 multi_buffer::Event::ExcerptsExpanded { ids } => {
19032 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19033 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19034 }
19035 multi_buffer::Event::Reparsed(buffer_id) => {
19036 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19037 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19038
19039 cx.emit(EditorEvent::Reparsed(*buffer_id));
19040 }
19041 multi_buffer::Event::DiffHunksToggled => {
19042 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19043 }
19044 multi_buffer::Event::LanguageChanged(buffer_id) => {
19045 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19046 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19047 cx.emit(EditorEvent::Reparsed(*buffer_id));
19048 cx.notify();
19049 }
19050 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19051 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19052 multi_buffer::Event::FileHandleChanged
19053 | multi_buffer::Event::Reloaded
19054 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19055 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19056 multi_buffer::Event::DiagnosticsUpdated => {
19057 self.update_diagnostics_state(window, cx);
19058 }
19059 _ => {}
19060 };
19061 }
19062
19063 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19064 self.refresh_active_diagnostics(cx);
19065 self.refresh_inline_diagnostics(true, window, cx);
19066 self.scrollbar_marker_state.dirty = true;
19067 cx.notify();
19068 }
19069
19070 pub fn start_temporary_diff_override(&mut self) {
19071 self.load_diff_task.take();
19072 self.temporary_diff_override = true;
19073 }
19074
19075 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19076 self.temporary_diff_override = false;
19077 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19078 self.buffer.update(cx, |buffer, cx| {
19079 buffer.set_all_diff_hunks_collapsed(cx);
19080 });
19081
19082 if let Some(project) = self.project.clone() {
19083 self.load_diff_task = Some(
19084 update_uncommitted_diff_for_buffer(
19085 cx.entity(),
19086 &project,
19087 self.buffer.read(cx).all_buffers(),
19088 self.buffer.clone(),
19089 cx,
19090 )
19091 .shared(),
19092 );
19093 }
19094 }
19095
19096 fn on_display_map_changed(
19097 &mut self,
19098 _: Entity<DisplayMap>,
19099 _: &mut Window,
19100 cx: &mut Context<Self>,
19101 ) {
19102 cx.notify();
19103 }
19104
19105 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19106 let new_severity = if self.diagnostics_enabled() {
19107 EditorSettings::get_global(cx)
19108 .diagnostics_max_severity
19109 .unwrap_or(DiagnosticSeverity::Hint)
19110 } else {
19111 DiagnosticSeverity::Off
19112 };
19113 self.set_max_diagnostics_severity(new_severity, cx);
19114 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19115 self.update_edit_prediction_settings(cx);
19116 self.refresh_inline_completion(true, false, window, cx);
19117 self.refresh_inlay_hints(
19118 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19119 self.selections.newest_anchor().head(),
19120 &self.buffer.read(cx).snapshot(cx),
19121 cx,
19122 )),
19123 cx,
19124 );
19125
19126 let old_cursor_shape = self.cursor_shape;
19127
19128 {
19129 let editor_settings = EditorSettings::get_global(cx);
19130 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19131 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19132 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19133 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19134 self.drag_and_drop_selection_enabled = editor_settings.drag_and_drop_selection;
19135 }
19136
19137 if old_cursor_shape != self.cursor_shape {
19138 cx.emit(EditorEvent::CursorShapeChanged);
19139 }
19140
19141 let project_settings = ProjectSettings::get_global(cx);
19142 self.serialize_dirty_buffers =
19143 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19144
19145 if self.mode.is_full() {
19146 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19147 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19148 if self.show_inline_diagnostics != show_inline_diagnostics {
19149 self.show_inline_diagnostics = show_inline_diagnostics;
19150 self.refresh_inline_diagnostics(false, window, cx);
19151 }
19152
19153 if self.git_blame_inline_enabled != inline_blame_enabled {
19154 self.toggle_git_blame_inline_internal(false, window, cx);
19155 }
19156
19157 let minimap_settings = EditorSettings::get_global(cx).minimap;
19158 if self.minimap_visibility != MinimapVisibility::Disabled {
19159 if self.minimap_visibility.settings_visibility()
19160 != minimap_settings.minimap_enabled()
19161 {
19162 self.set_minimap_visibility(
19163 MinimapVisibility::for_mode(self.mode(), cx),
19164 window,
19165 cx,
19166 );
19167 } else if let Some(minimap_entity) = self.minimap.as_ref() {
19168 minimap_entity.update(cx, |minimap_editor, cx| {
19169 minimap_editor.update_minimap_configuration(minimap_settings, cx)
19170 })
19171 }
19172 }
19173 }
19174
19175 cx.notify();
19176 }
19177
19178 pub fn set_searchable(&mut self, searchable: bool) {
19179 self.searchable = searchable;
19180 }
19181
19182 pub fn searchable(&self) -> bool {
19183 self.searchable
19184 }
19185
19186 fn open_proposed_changes_editor(
19187 &mut self,
19188 _: &OpenProposedChangesEditor,
19189 window: &mut Window,
19190 cx: &mut Context<Self>,
19191 ) {
19192 let Some(workspace) = self.workspace() else {
19193 cx.propagate();
19194 return;
19195 };
19196
19197 let selections = self.selections.all::<usize>(cx);
19198 let multi_buffer = self.buffer.read(cx);
19199 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19200 let mut new_selections_by_buffer = HashMap::default();
19201 for selection in selections {
19202 for (buffer, range, _) in
19203 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
19204 {
19205 let mut range = range.to_point(buffer);
19206 range.start.column = 0;
19207 range.end.column = buffer.line_len(range.end.row);
19208 new_selections_by_buffer
19209 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
19210 .or_insert(Vec::new())
19211 .push(range)
19212 }
19213 }
19214
19215 let proposed_changes_buffers = new_selections_by_buffer
19216 .into_iter()
19217 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
19218 .collect::<Vec<_>>();
19219 let proposed_changes_editor = cx.new(|cx| {
19220 ProposedChangesEditor::new(
19221 "Proposed changes",
19222 proposed_changes_buffers,
19223 self.project.clone(),
19224 window,
19225 cx,
19226 )
19227 });
19228
19229 window.defer(cx, move |window, cx| {
19230 workspace.update(cx, |workspace, cx| {
19231 workspace.active_pane().update(cx, |pane, cx| {
19232 pane.add_item(
19233 Box::new(proposed_changes_editor),
19234 true,
19235 true,
19236 None,
19237 window,
19238 cx,
19239 );
19240 });
19241 });
19242 });
19243 }
19244
19245 pub fn open_excerpts_in_split(
19246 &mut self,
19247 _: &OpenExcerptsSplit,
19248 window: &mut Window,
19249 cx: &mut Context<Self>,
19250 ) {
19251 self.open_excerpts_common(None, true, window, cx)
19252 }
19253
19254 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
19255 self.open_excerpts_common(None, false, window, cx)
19256 }
19257
19258 fn open_excerpts_common(
19259 &mut self,
19260 jump_data: Option<JumpData>,
19261 split: bool,
19262 window: &mut Window,
19263 cx: &mut Context<Self>,
19264 ) {
19265 let Some(workspace) = self.workspace() else {
19266 cx.propagate();
19267 return;
19268 };
19269
19270 if self.buffer.read(cx).is_singleton() {
19271 cx.propagate();
19272 return;
19273 }
19274
19275 let mut new_selections_by_buffer = HashMap::default();
19276 match &jump_data {
19277 Some(JumpData::MultiBufferPoint {
19278 excerpt_id,
19279 position,
19280 anchor,
19281 line_offset_from_top,
19282 }) => {
19283 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19284 if let Some(buffer) = multi_buffer_snapshot
19285 .buffer_id_for_excerpt(*excerpt_id)
19286 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
19287 {
19288 let buffer_snapshot = buffer.read(cx).snapshot();
19289 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
19290 language::ToPoint::to_point(anchor, &buffer_snapshot)
19291 } else {
19292 buffer_snapshot.clip_point(*position, Bias::Left)
19293 };
19294 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
19295 new_selections_by_buffer.insert(
19296 buffer,
19297 (
19298 vec![jump_to_offset..jump_to_offset],
19299 Some(*line_offset_from_top),
19300 ),
19301 );
19302 }
19303 }
19304 Some(JumpData::MultiBufferRow {
19305 row,
19306 line_offset_from_top,
19307 }) => {
19308 let point = MultiBufferPoint::new(row.0, 0);
19309 if let Some((buffer, buffer_point, _)) =
19310 self.buffer.read(cx).point_to_buffer_point(point, cx)
19311 {
19312 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
19313 new_selections_by_buffer
19314 .entry(buffer)
19315 .or_insert((Vec::new(), Some(*line_offset_from_top)))
19316 .0
19317 .push(buffer_offset..buffer_offset)
19318 }
19319 }
19320 None => {
19321 let selections = self.selections.all::<usize>(cx);
19322 let multi_buffer = self.buffer.read(cx);
19323 for selection in selections {
19324 for (snapshot, range, _, anchor) in multi_buffer
19325 .snapshot(cx)
19326 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
19327 {
19328 if let Some(anchor) = anchor {
19329 // selection is in a deleted hunk
19330 let Some(buffer_id) = anchor.buffer_id else {
19331 continue;
19332 };
19333 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
19334 continue;
19335 };
19336 let offset = text::ToOffset::to_offset(
19337 &anchor.text_anchor,
19338 &buffer_handle.read(cx).snapshot(),
19339 );
19340 let range = offset..offset;
19341 new_selections_by_buffer
19342 .entry(buffer_handle)
19343 .or_insert((Vec::new(), None))
19344 .0
19345 .push(range)
19346 } else {
19347 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
19348 else {
19349 continue;
19350 };
19351 new_selections_by_buffer
19352 .entry(buffer_handle)
19353 .or_insert((Vec::new(), None))
19354 .0
19355 .push(range)
19356 }
19357 }
19358 }
19359 }
19360 }
19361
19362 new_selections_by_buffer
19363 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
19364
19365 if new_selections_by_buffer.is_empty() {
19366 return;
19367 }
19368
19369 // We defer the pane interaction because we ourselves are a workspace item
19370 // and activating a new item causes the pane to call a method on us reentrantly,
19371 // which panics if we're on the stack.
19372 window.defer(cx, move |window, cx| {
19373 workspace.update(cx, |workspace, cx| {
19374 let pane = if split {
19375 workspace.adjacent_pane(window, cx)
19376 } else {
19377 workspace.active_pane().clone()
19378 };
19379
19380 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
19381 let editor = buffer
19382 .read(cx)
19383 .file()
19384 .is_none()
19385 .then(|| {
19386 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
19387 // so `workspace.open_project_item` will never find them, always opening a new editor.
19388 // Instead, we try to activate the existing editor in the pane first.
19389 let (editor, pane_item_index) =
19390 pane.read(cx).items().enumerate().find_map(|(i, item)| {
19391 let editor = item.downcast::<Editor>()?;
19392 let singleton_buffer =
19393 editor.read(cx).buffer().read(cx).as_singleton()?;
19394 if singleton_buffer == buffer {
19395 Some((editor, i))
19396 } else {
19397 None
19398 }
19399 })?;
19400 pane.update(cx, |pane, cx| {
19401 pane.activate_item(pane_item_index, true, true, window, cx)
19402 });
19403 Some(editor)
19404 })
19405 .flatten()
19406 .unwrap_or_else(|| {
19407 workspace.open_project_item::<Self>(
19408 pane.clone(),
19409 buffer,
19410 true,
19411 true,
19412 window,
19413 cx,
19414 )
19415 });
19416
19417 editor.update(cx, |editor, cx| {
19418 let autoscroll = match scroll_offset {
19419 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
19420 None => Autoscroll::newest(),
19421 };
19422 let nav_history = editor.nav_history.take();
19423 editor.change_selections(Some(autoscroll), window, cx, |s| {
19424 s.select_ranges(ranges);
19425 });
19426 editor.nav_history = nav_history;
19427 });
19428 }
19429 })
19430 });
19431 }
19432
19433 // For now, don't allow opening excerpts in buffers that aren't backed by
19434 // regular project files.
19435 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
19436 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
19437 }
19438
19439 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
19440 let snapshot = self.buffer.read(cx).read(cx);
19441 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
19442 Some(
19443 ranges
19444 .iter()
19445 .map(move |range| {
19446 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19447 })
19448 .collect(),
19449 )
19450 }
19451
19452 fn selection_replacement_ranges(
19453 &self,
19454 range: Range<OffsetUtf16>,
19455 cx: &mut App,
19456 ) -> Vec<Range<OffsetUtf16>> {
19457 let selections = self.selections.all::<OffsetUtf16>(cx);
19458 let newest_selection = selections
19459 .iter()
19460 .max_by_key(|selection| selection.id)
19461 .unwrap();
19462 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19463 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19464 let snapshot = self.buffer.read(cx).read(cx);
19465 selections
19466 .into_iter()
19467 .map(|mut selection| {
19468 selection.start.0 =
19469 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19470 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19471 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19472 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19473 })
19474 .collect()
19475 }
19476
19477 fn report_editor_event(
19478 &self,
19479 event_type: &'static str,
19480 file_extension: Option<String>,
19481 cx: &App,
19482 ) {
19483 if cfg!(any(test, feature = "test-support")) {
19484 return;
19485 }
19486
19487 let Some(project) = &self.project else { return };
19488
19489 // If None, we are in a file without an extension
19490 let file = self
19491 .buffer
19492 .read(cx)
19493 .as_singleton()
19494 .and_then(|b| b.read(cx).file());
19495 let file_extension = file_extension.or(file
19496 .as_ref()
19497 .and_then(|file| Path::new(file.file_name(cx)).extension())
19498 .and_then(|e| e.to_str())
19499 .map(|a| a.to_string()));
19500
19501 let vim_mode = vim_enabled(cx);
19502
19503 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
19504 let copilot_enabled = edit_predictions_provider
19505 == language::language_settings::EditPredictionProvider::Copilot;
19506 let copilot_enabled_for_language = self
19507 .buffer
19508 .read(cx)
19509 .language_settings(cx)
19510 .show_edit_predictions;
19511
19512 let project = project.read(cx);
19513 telemetry::event!(
19514 event_type,
19515 file_extension,
19516 vim_mode,
19517 copilot_enabled,
19518 copilot_enabled_for_language,
19519 edit_predictions_provider,
19520 is_via_ssh = project.is_via_ssh(),
19521 );
19522 }
19523
19524 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
19525 /// with each line being an array of {text, highlight} objects.
19526 fn copy_highlight_json(
19527 &mut self,
19528 _: &CopyHighlightJson,
19529 window: &mut Window,
19530 cx: &mut Context<Self>,
19531 ) {
19532 #[derive(Serialize)]
19533 struct Chunk<'a> {
19534 text: String,
19535 highlight: Option<&'a str>,
19536 }
19537
19538 let snapshot = self.buffer.read(cx).snapshot(cx);
19539 let range = self
19540 .selected_text_range(false, window, cx)
19541 .and_then(|selection| {
19542 if selection.range.is_empty() {
19543 None
19544 } else {
19545 Some(selection.range)
19546 }
19547 })
19548 .unwrap_or_else(|| 0..snapshot.len());
19549
19550 let chunks = snapshot.chunks(range, true);
19551 let mut lines = Vec::new();
19552 let mut line: VecDeque<Chunk> = VecDeque::new();
19553
19554 let Some(style) = self.style.as_ref() else {
19555 return;
19556 };
19557
19558 for chunk in chunks {
19559 let highlight = chunk
19560 .syntax_highlight_id
19561 .and_then(|id| id.name(&style.syntax));
19562 let mut chunk_lines = chunk.text.split('\n').peekable();
19563 while let Some(text) = chunk_lines.next() {
19564 let mut merged_with_last_token = false;
19565 if let Some(last_token) = line.back_mut() {
19566 if last_token.highlight == highlight {
19567 last_token.text.push_str(text);
19568 merged_with_last_token = true;
19569 }
19570 }
19571
19572 if !merged_with_last_token {
19573 line.push_back(Chunk {
19574 text: text.into(),
19575 highlight,
19576 });
19577 }
19578
19579 if chunk_lines.peek().is_some() {
19580 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19581 line.pop_front();
19582 }
19583 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19584 line.pop_back();
19585 }
19586
19587 lines.push(mem::take(&mut line));
19588 }
19589 }
19590 }
19591
19592 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19593 return;
19594 };
19595 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19596 }
19597
19598 pub fn open_context_menu(
19599 &mut self,
19600 _: &OpenContextMenu,
19601 window: &mut Window,
19602 cx: &mut Context<Self>,
19603 ) {
19604 self.request_autoscroll(Autoscroll::newest(), cx);
19605 let position = self.selections.newest_display(cx).start;
19606 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19607 }
19608
19609 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19610 &self.inlay_hint_cache
19611 }
19612
19613 pub fn replay_insert_event(
19614 &mut self,
19615 text: &str,
19616 relative_utf16_range: Option<Range<isize>>,
19617 window: &mut Window,
19618 cx: &mut Context<Self>,
19619 ) {
19620 if !self.input_enabled {
19621 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19622 return;
19623 }
19624 if let Some(relative_utf16_range) = relative_utf16_range {
19625 let selections = self.selections.all::<OffsetUtf16>(cx);
19626 self.change_selections(None, window, cx, |s| {
19627 let new_ranges = selections.into_iter().map(|range| {
19628 let start = OffsetUtf16(
19629 range
19630 .head()
19631 .0
19632 .saturating_add_signed(relative_utf16_range.start),
19633 );
19634 let end = OffsetUtf16(
19635 range
19636 .head()
19637 .0
19638 .saturating_add_signed(relative_utf16_range.end),
19639 );
19640 start..end
19641 });
19642 s.select_ranges(new_ranges);
19643 });
19644 }
19645
19646 self.handle_input(text, window, cx);
19647 }
19648
19649 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19650 let Some(provider) = self.semantics_provider.as_ref() else {
19651 return false;
19652 };
19653
19654 let mut supports = false;
19655 self.buffer().update(cx, |this, cx| {
19656 this.for_each_buffer(|buffer| {
19657 supports |= provider.supports_inlay_hints(buffer, cx);
19658 });
19659 });
19660
19661 supports
19662 }
19663
19664 pub fn is_focused(&self, window: &Window) -> bool {
19665 self.focus_handle.is_focused(window)
19666 }
19667
19668 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19669 cx.emit(EditorEvent::Focused);
19670
19671 if let Some(descendant) = self
19672 .last_focused_descendant
19673 .take()
19674 .and_then(|descendant| descendant.upgrade())
19675 {
19676 window.focus(&descendant);
19677 } else {
19678 if let Some(blame) = self.blame.as_ref() {
19679 blame.update(cx, GitBlame::focus)
19680 }
19681
19682 self.blink_manager.update(cx, BlinkManager::enable);
19683 self.show_cursor_names(window, cx);
19684 self.buffer.update(cx, |buffer, cx| {
19685 buffer.finalize_last_transaction(cx);
19686 if self.leader_id.is_none() {
19687 buffer.set_active_selections(
19688 &self.selections.disjoint_anchors(),
19689 self.selections.line_mode,
19690 self.cursor_shape,
19691 cx,
19692 );
19693 }
19694 });
19695 }
19696 }
19697
19698 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19699 cx.emit(EditorEvent::FocusedIn)
19700 }
19701
19702 fn handle_focus_out(
19703 &mut self,
19704 event: FocusOutEvent,
19705 _window: &mut Window,
19706 cx: &mut Context<Self>,
19707 ) {
19708 if event.blurred != self.focus_handle {
19709 self.last_focused_descendant = Some(event.blurred);
19710 }
19711 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19712 }
19713
19714 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19715 self.blink_manager.update(cx, BlinkManager::disable);
19716 self.buffer
19717 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19718
19719 if let Some(blame) = self.blame.as_ref() {
19720 blame.update(cx, GitBlame::blur)
19721 }
19722 if !self.hover_state.focused(window, cx) {
19723 hide_hover(self, cx);
19724 }
19725 if !self
19726 .context_menu
19727 .borrow()
19728 .as_ref()
19729 .is_some_and(|context_menu| context_menu.focused(window, cx))
19730 {
19731 self.hide_context_menu(window, cx);
19732 }
19733 self.discard_inline_completion(false, cx);
19734 cx.emit(EditorEvent::Blurred);
19735 cx.notify();
19736 }
19737
19738 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19739 let mut pending: String = window
19740 .pending_input_keystrokes()
19741 .into_iter()
19742 .flatten()
19743 .filter_map(|keystroke| {
19744 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
19745 Some(keystroke.key_char.clone().unwrap_or(keystroke.key.clone()))
19746 } else {
19747 None
19748 }
19749 })
19750 .collect();
19751
19752 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
19753 pending = "".to_string();
19754 }
19755
19756 let existing_pending = self
19757 .text_highlights::<PendingInput>(cx)
19758 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
19759 if existing_pending.is_none() && pending.is_empty() {
19760 return;
19761 }
19762 let transaction =
19763 self.transact(window, cx, |this, window, cx| {
19764 let selections = this.selections.all::<usize>(cx);
19765 let edits = selections
19766 .iter()
19767 .map(|selection| (selection.end..selection.end, pending.clone()));
19768 this.edit(edits, cx);
19769 this.change_selections(None, window, cx, |s| {
19770 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
19771 sel.start + ix * pending.len()..sel.end + ix * pending.len()
19772 }));
19773 });
19774 if let Some(existing_ranges) = existing_pending {
19775 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
19776 this.edit(edits, cx);
19777 }
19778 });
19779
19780 let snapshot = self.snapshot(window, cx);
19781 let ranges = self
19782 .selections
19783 .all::<usize>(cx)
19784 .into_iter()
19785 .map(|selection| {
19786 snapshot.buffer_snapshot.anchor_after(selection.end)
19787 ..snapshot
19788 .buffer_snapshot
19789 .anchor_before(selection.end + pending.len())
19790 })
19791 .collect();
19792
19793 if pending.is_empty() {
19794 self.clear_highlights::<PendingInput>(cx);
19795 } else {
19796 self.highlight_text::<PendingInput>(
19797 ranges,
19798 HighlightStyle {
19799 underline: Some(UnderlineStyle {
19800 thickness: px(1.),
19801 color: None,
19802 wavy: false,
19803 }),
19804 ..Default::default()
19805 },
19806 cx,
19807 );
19808 }
19809
19810 self.ime_transaction = self.ime_transaction.or(transaction);
19811 if let Some(transaction) = self.ime_transaction {
19812 self.buffer.update(cx, |buffer, cx| {
19813 buffer.group_until_transaction(transaction, cx);
19814 });
19815 }
19816
19817 if self.text_highlights::<PendingInput>(cx).is_none() {
19818 self.ime_transaction.take();
19819 }
19820 }
19821
19822 pub fn register_action<A: Action>(
19823 &mut self,
19824 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19825 ) -> Subscription {
19826 let id = self.next_editor_action_id.post_inc();
19827 let listener = Arc::new(listener);
19828 self.editor_actions.borrow_mut().insert(
19829 id,
19830 Box::new(move |window, _| {
19831 let listener = listener.clone();
19832 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19833 let action = action.downcast_ref().unwrap();
19834 if phase == DispatchPhase::Bubble {
19835 listener(action, window, cx)
19836 }
19837 })
19838 }),
19839 );
19840
19841 let editor_actions = self.editor_actions.clone();
19842 Subscription::new(move || {
19843 editor_actions.borrow_mut().remove(&id);
19844 })
19845 }
19846
19847 pub fn file_header_size(&self) -> u32 {
19848 FILE_HEADER_HEIGHT
19849 }
19850
19851 pub fn restore(
19852 &mut self,
19853 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19854 window: &mut Window,
19855 cx: &mut Context<Self>,
19856 ) {
19857 let workspace = self.workspace();
19858 let project = self.project.as_ref();
19859 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19860 let mut tasks = Vec::new();
19861 for (buffer_id, changes) in revert_changes {
19862 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19863 buffer.update(cx, |buffer, cx| {
19864 buffer.edit(
19865 changes
19866 .into_iter()
19867 .map(|(range, text)| (range, text.to_string())),
19868 None,
19869 cx,
19870 );
19871 });
19872
19873 if let Some(project) =
19874 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19875 {
19876 project.update(cx, |project, cx| {
19877 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19878 })
19879 }
19880 }
19881 }
19882 tasks
19883 });
19884 cx.spawn_in(window, async move |_, cx| {
19885 for (buffer, task) in save_tasks {
19886 let result = task.await;
19887 if result.is_err() {
19888 let Some(path) = buffer
19889 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19890 .ok()
19891 else {
19892 continue;
19893 };
19894 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19895 let Some(task) = cx
19896 .update_window_entity(&workspace, |workspace, window, cx| {
19897 workspace
19898 .open_path_preview(path, None, false, false, false, window, cx)
19899 })
19900 .ok()
19901 else {
19902 continue;
19903 };
19904 task.await.log_err();
19905 }
19906 }
19907 }
19908 })
19909 .detach();
19910 self.change_selections(None, window, cx, |selections| selections.refresh());
19911 }
19912
19913 pub fn to_pixel_point(
19914 &self,
19915 source: multi_buffer::Anchor,
19916 editor_snapshot: &EditorSnapshot,
19917 window: &mut Window,
19918 ) -> Option<gpui::Point<Pixels>> {
19919 let source_point = source.to_display_point(editor_snapshot);
19920 self.display_to_pixel_point(source_point, editor_snapshot, window)
19921 }
19922
19923 pub fn display_to_pixel_point(
19924 &self,
19925 source: DisplayPoint,
19926 editor_snapshot: &EditorSnapshot,
19927 window: &mut Window,
19928 ) -> Option<gpui::Point<Pixels>> {
19929 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19930 let text_layout_details = self.text_layout_details(window);
19931 let scroll_top = text_layout_details
19932 .scroll_anchor
19933 .scroll_position(editor_snapshot)
19934 .y;
19935
19936 if source.row().as_f32() < scroll_top.floor() {
19937 return None;
19938 }
19939 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19940 let source_y = line_height * (source.row().as_f32() - scroll_top);
19941 Some(gpui::Point::new(source_x, source_y))
19942 }
19943
19944 pub fn has_visible_completions_menu(&self) -> bool {
19945 !self.edit_prediction_preview_is_active()
19946 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19947 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19948 })
19949 }
19950
19951 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19952 if self.mode.is_minimap() {
19953 return;
19954 }
19955 self.addons
19956 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
19957 }
19958
19959 pub fn unregister_addon<T: Addon>(&mut self) {
19960 self.addons.remove(&std::any::TypeId::of::<T>());
19961 }
19962
19963 pub fn addon<T: Addon>(&self) -> Option<&T> {
19964 let type_id = std::any::TypeId::of::<T>();
19965 self.addons
19966 .get(&type_id)
19967 .and_then(|item| item.to_any().downcast_ref::<T>())
19968 }
19969
19970 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
19971 let type_id = std::any::TypeId::of::<T>();
19972 self.addons
19973 .get_mut(&type_id)
19974 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
19975 }
19976
19977 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
19978 let text_layout_details = self.text_layout_details(window);
19979 let style = &text_layout_details.editor_style;
19980 let font_id = window.text_system().resolve_font(&style.text.font());
19981 let font_size = style.text.font_size.to_pixels(window.rem_size());
19982 let line_height = style.text.line_height_in_pixels(window.rem_size());
19983 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
19984
19985 gpui::Size::new(em_width, line_height)
19986 }
19987
19988 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
19989 self.load_diff_task.clone()
19990 }
19991
19992 fn read_metadata_from_db(
19993 &mut self,
19994 item_id: u64,
19995 workspace_id: WorkspaceId,
19996 window: &mut Window,
19997 cx: &mut Context<Editor>,
19998 ) {
19999 if self.is_singleton(cx)
20000 && !self.mode.is_minimap()
20001 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20002 {
20003 let buffer_snapshot = OnceCell::new();
20004
20005 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20006 if !folds.is_empty() {
20007 let snapshot =
20008 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20009 self.fold_ranges(
20010 folds
20011 .into_iter()
20012 .map(|(start, end)| {
20013 snapshot.clip_offset(start, Bias::Left)
20014 ..snapshot.clip_offset(end, Bias::Right)
20015 })
20016 .collect(),
20017 false,
20018 window,
20019 cx,
20020 );
20021 }
20022 }
20023
20024 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20025 if !selections.is_empty() {
20026 let snapshot =
20027 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20028 // skip adding the initial selection to selection history
20029 self.selection_history.mode = SelectionHistoryMode::Skipping;
20030 self.change_selections(None, window, cx, |s| {
20031 s.select_ranges(selections.into_iter().map(|(start, end)| {
20032 snapshot.clip_offset(start, Bias::Left)
20033 ..snapshot.clip_offset(end, Bias::Right)
20034 }));
20035 });
20036 self.selection_history.mode = SelectionHistoryMode::Normal;
20037 }
20038 };
20039 }
20040
20041 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20042 }
20043}
20044
20045fn vim_enabled(cx: &App) -> bool {
20046 cx.global::<SettingsStore>()
20047 .raw_user_settings()
20048 .get("vim_mode")
20049 == Some(&serde_json::Value::Bool(true))
20050}
20051
20052fn process_completion_for_edit(
20053 completion: &Completion,
20054 intent: CompletionIntent,
20055 buffer: &Entity<Buffer>,
20056 cursor_position: &text::Anchor,
20057 cx: &mut Context<Editor>,
20058) -> CompletionEdit {
20059 let buffer = buffer.read(cx);
20060 let buffer_snapshot = buffer.snapshot();
20061 let (snippet, new_text) = if completion.is_snippet() {
20062 let mut snippet_source = completion.new_text.clone();
20063 if let Some(scope) = buffer_snapshot.language_scope_at(cursor_position) {
20064 if scope.prefers_label_for_snippet_in_completion() {
20065 if let Some(label) = completion.label() {
20066 if matches!(
20067 completion.kind(),
20068 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20069 ) {
20070 snippet_source = label;
20071 }
20072 }
20073 }
20074 }
20075 match Snippet::parse(&snippet_source).log_err() {
20076 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20077 None => (None, completion.new_text.clone()),
20078 }
20079 } else {
20080 (None, completion.new_text.clone())
20081 };
20082
20083 let mut range_to_replace = {
20084 let replace_range = &completion.replace_range;
20085 if let CompletionSource::Lsp {
20086 insert_range: Some(insert_range),
20087 ..
20088 } = &completion.source
20089 {
20090 debug_assert_eq!(
20091 insert_range.start, replace_range.start,
20092 "insert_range and replace_range should start at the same position"
20093 );
20094 debug_assert!(
20095 insert_range
20096 .start
20097 .cmp(&cursor_position, &buffer_snapshot)
20098 .is_le(),
20099 "insert_range should start before or at cursor position"
20100 );
20101 debug_assert!(
20102 replace_range
20103 .start
20104 .cmp(&cursor_position, &buffer_snapshot)
20105 .is_le(),
20106 "replace_range should start before or at cursor position"
20107 );
20108 debug_assert!(
20109 insert_range
20110 .end
20111 .cmp(&cursor_position, &buffer_snapshot)
20112 .is_le(),
20113 "insert_range should end before or at cursor position"
20114 );
20115
20116 let should_replace = match intent {
20117 CompletionIntent::CompleteWithInsert => false,
20118 CompletionIntent::CompleteWithReplace => true,
20119 CompletionIntent::Complete | CompletionIntent::Compose => {
20120 let insert_mode =
20121 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
20122 .completions
20123 .lsp_insert_mode;
20124 match insert_mode {
20125 LspInsertMode::Insert => false,
20126 LspInsertMode::Replace => true,
20127 LspInsertMode::ReplaceSubsequence => {
20128 let mut text_to_replace = buffer.chars_for_range(
20129 buffer.anchor_before(replace_range.start)
20130 ..buffer.anchor_after(replace_range.end),
20131 );
20132 let mut current_needle = text_to_replace.next();
20133 for haystack_ch in completion.label.text.chars() {
20134 if let Some(needle_ch) = current_needle {
20135 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
20136 current_needle = text_to_replace.next();
20137 }
20138 }
20139 }
20140 current_needle.is_none()
20141 }
20142 LspInsertMode::ReplaceSuffix => {
20143 if replace_range
20144 .end
20145 .cmp(&cursor_position, &buffer_snapshot)
20146 .is_gt()
20147 {
20148 let range_after_cursor = *cursor_position..replace_range.end;
20149 let text_after_cursor = buffer
20150 .text_for_range(
20151 buffer.anchor_before(range_after_cursor.start)
20152 ..buffer.anchor_after(range_after_cursor.end),
20153 )
20154 .collect::<String>()
20155 .to_ascii_lowercase();
20156 completion
20157 .label
20158 .text
20159 .to_ascii_lowercase()
20160 .ends_with(&text_after_cursor)
20161 } else {
20162 true
20163 }
20164 }
20165 }
20166 }
20167 };
20168
20169 if should_replace {
20170 replace_range.clone()
20171 } else {
20172 insert_range.clone()
20173 }
20174 } else {
20175 replace_range.clone()
20176 }
20177 };
20178
20179 if range_to_replace
20180 .end
20181 .cmp(&cursor_position, &buffer_snapshot)
20182 .is_lt()
20183 {
20184 range_to_replace.end = *cursor_position;
20185 }
20186
20187 CompletionEdit {
20188 new_text,
20189 replace_range: range_to_replace.to_offset(&buffer),
20190 snippet,
20191 }
20192}
20193
20194struct CompletionEdit {
20195 new_text: String,
20196 replace_range: Range<usize>,
20197 snippet: Option<Snippet>,
20198}
20199
20200fn insert_extra_newline_brackets(
20201 buffer: &MultiBufferSnapshot,
20202 range: Range<usize>,
20203 language: &language::LanguageScope,
20204) -> bool {
20205 let leading_whitespace_len = buffer
20206 .reversed_chars_at(range.start)
20207 .take_while(|c| c.is_whitespace() && *c != '\n')
20208 .map(|c| c.len_utf8())
20209 .sum::<usize>();
20210 let trailing_whitespace_len = buffer
20211 .chars_at(range.end)
20212 .take_while(|c| c.is_whitespace() && *c != '\n')
20213 .map(|c| c.len_utf8())
20214 .sum::<usize>();
20215 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
20216
20217 language.brackets().any(|(pair, enabled)| {
20218 let pair_start = pair.start.trim_end();
20219 let pair_end = pair.end.trim_start();
20220
20221 enabled
20222 && pair.newline
20223 && buffer.contains_str_at(range.end, pair_end)
20224 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
20225 })
20226}
20227
20228fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
20229 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
20230 [(buffer, range, _)] => (*buffer, range.clone()),
20231 _ => return false,
20232 };
20233 let pair = {
20234 let mut result: Option<BracketMatch> = None;
20235
20236 for pair in buffer
20237 .all_bracket_ranges(range.clone())
20238 .filter(move |pair| {
20239 pair.open_range.start <= range.start && pair.close_range.end >= range.end
20240 })
20241 {
20242 let len = pair.close_range.end - pair.open_range.start;
20243
20244 if let Some(existing) = &result {
20245 let existing_len = existing.close_range.end - existing.open_range.start;
20246 if len > existing_len {
20247 continue;
20248 }
20249 }
20250
20251 result = Some(pair);
20252 }
20253
20254 result
20255 };
20256 let Some(pair) = pair else {
20257 return false;
20258 };
20259 pair.newline_only
20260 && buffer
20261 .chars_for_range(pair.open_range.end..range.start)
20262 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
20263 .all(|c| c.is_whitespace() && c != '\n')
20264}
20265
20266fn update_uncommitted_diff_for_buffer(
20267 editor: Entity<Editor>,
20268 project: &Entity<Project>,
20269 buffers: impl IntoIterator<Item = Entity<Buffer>>,
20270 buffer: Entity<MultiBuffer>,
20271 cx: &mut App,
20272) -> Task<()> {
20273 let mut tasks = Vec::new();
20274 project.update(cx, |project, cx| {
20275 for buffer in buffers {
20276 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
20277 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
20278 }
20279 }
20280 });
20281 cx.spawn(async move |cx| {
20282 let diffs = future::join_all(tasks).await;
20283 if editor
20284 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
20285 .unwrap_or(false)
20286 {
20287 return;
20288 }
20289
20290 buffer
20291 .update(cx, |buffer, cx| {
20292 for diff in diffs.into_iter().flatten() {
20293 buffer.add_diff(diff, cx);
20294 }
20295 })
20296 .ok();
20297 })
20298}
20299
20300fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
20301 let tab_size = tab_size.get() as usize;
20302 let mut width = offset;
20303
20304 for ch in text.chars() {
20305 width += if ch == '\t' {
20306 tab_size - (width % tab_size)
20307 } else {
20308 1
20309 };
20310 }
20311
20312 width - offset
20313}
20314
20315#[cfg(test)]
20316mod tests {
20317 use super::*;
20318
20319 #[test]
20320 fn test_string_size_with_expanded_tabs() {
20321 let nz = |val| NonZeroU32::new(val).unwrap();
20322 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
20323 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
20324 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
20325 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
20326 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
20327 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
20328 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
20329 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
20330 }
20331}
20332
20333/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
20334struct WordBreakingTokenizer<'a> {
20335 input: &'a str,
20336}
20337
20338impl<'a> WordBreakingTokenizer<'a> {
20339 fn new(input: &'a str) -> Self {
20340 Self { input }
20341 }
20342}
20343
20344fn is_char_ideographic(ch: char) -> bool {
20345 use unicode_script::Script::*;
20346 use unicode_script::UnicodeScript;
20347 matches!(ch.script(), Han | Tangut | Yi)
20348}
20349
20350fn is_grapheme_ideographic(text: &str) -> bool {
20351 text.chars().any(is_char_ideographic)
20352}
20353
20354fn is_grapheme_whitespace(text: &str) -> bool {
20355 text.chars().any(|x| x.is_whitespace())
20356}
20357
20358fn should_stay_with_preceding_ideograph(text: &str) -> bool {
20359 text.chars().next().map_or(false, |ch| {
20360 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
20361 })
20362}
20363
20364#[derive(PartialEq, Eq, Debug, Clone, Copy)]
20365enum WordBreakToken<'a> {
20366 Word { token: &'a str, grapheme_len: usize },
20367 InlineWhitespace { token: &'a str, grapheme_len: usize },
20368 Newline,
20369}
20370
20371impl<'a> Iterator for WordBreakingTokenizer<'a> {
20372 /// Yields a span, the count of graphemes in the token, and whether it was
20373 /// whitespace. Note that it also breaks at word boundaries.
20374 type Item = WordBreakToken<'a>;
20375
20376 fn next(&mut self) -> Option<Self::Item> {
20377 use unicode_segmentation::UnicodeSegmentation;
20378 if self.input.is_empty() {
20379 return None;
20380 }
20381
20382 let mut iter = self.input.graphemes(true).peekable();
20383 let mut offset = 0;
20384 let mut grapheme_len = 0;
20385 if let Some(first_grapheme) = iter.next() {
20386 let is_newline = first_grapheme == "\n";
20387 let is_whitespace = is_grapheme_whitespace(first_grapheme);
20388 offset += first_grapheme.len();
20389 grapheme_len += 1;
20390 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
20391 if let Some(grapheme) = iter.peek().copied() {
20392 if should_stay_with_preceding_ideograph(grapheme) {
20393 offset += grapheme.len();
20394 grapheme_len += 1;
20395 }
20396 }
20397 } else {
20398 let mut words = self.input[offset..].split_word_bound_indices().peekable();
20399 let mut next_word_bound = words.peek().copied();
20400 if next_word_bound.map_or(false, |(i, _)| i == 0) {
20401 next_word_bound = words.next();
20402 }
20403 while let Some(grapheme) = iter.peek().copied() {
20404 if next_word_bound.map_or(false, |(i, _)| i == offset) {
20405 break;
20406 };
20407 if is_grapheme_whitespace(grapheme) != is_whitespace
20408 || (grapheme == "\n") != is_newline
20409 {
20410 break;
20411 };
20412 offset += grapheme.len();
20413 grapheme_len += 1;
20414 iter.next();
20415 }
20416 }
20417 let token = &self.input[..offset];
20418 self.input = &self.input[offset..];
20419 if token == "\n" {
20420 Some(WordBreakToken::Newline)
20421 } else if is_whitespace {
20422 Some(WordBreakToken::InlineWhitespace {
20423 token,
20424 grapheme_len,
20425 })
20426 } else {
20427 Some(WordBreakToken::Word {
20428 token,
20429 grapheme_len,
20430 })
20431 }
20432 } else {
20433 None
20434 }
20435 }
20436}
20437
20438#[test]
20439fn test_word_breaking_tokenizer() {
20440 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
20441 ("", &[]),
20442 (" ", &[whitespace(" ", 2)]),
20443 ("Ʒ", &[word("Ʒ", 1)]),
20444 ("Ǽ", &[word("Ǽ", 1)]),
20445 ("⋑", &[word("⋑", 1)]),
20446 ("⋑⋑", &[word("⋑⋑", 2)]),
20447 (
20448 "原理,进而",
20449 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
20450 ),
20451 (
20452 "hello world",
20453 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
20454 ),
20455 (
20456 "hello, world",
20457 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
20458 ),
20459 (
20460 " hello world",
20461 &[
20462 whitespace(" ", 2),
20463 word("hello", 5),
20464 whitespace(" ", 1),
20465 word("world", 5),
20466 ],
20467 ),
20468 (
20469 "这是什么 \n 钢笔",
20470 &[
20471 word("这", 1),
20472 word("是", 1),
20473 word("什", 1),
20474 word("么", 1),
20475 whitespace(" ", 1),
20476 newline(),
20477 whitespace(" ", 1),
20478 word("钢", 1),
20479 word("笔", 1),
20480 ],
20481 ),
20482 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
20483 ];
20484
20485 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20486 WordBreakToken::Word {
20487 token,
20488 grapheme_len,
20489 }
20490 }
20491
20492 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20493 WordBreakToken::InlineWhitespace {
20494 token,
20495 grapheme_len,
20496 }
20497 }
20498
20499 fn newline() -> WordBreakToken<'static> {
20500 WordBreakToken::Newline
20501 }
20502
20503 for (input, result) in tests {
20504 assert_eq!(
20505 WordBreakingTokenizer::new(input)
20506 .collect::<Vec<_>>()
20507 .as_slice(),
20508 *result,
20509 );
20510 }
20511}
20512
20513fn wrap_with_prefix(
20514 line_prefix: String,
20515 unwrapped_text: String,
20516 wrap_column: usize,
20517 tab_size: NonZeroU32,
20518 preserve_existing_whitespace: bool,
20519) -> String {
20520 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
20521 let mut wrapped_text = String::new();
20522 let mut current_line = line_prefix.clone();
20523
20524 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
20525 let mut current_line_len = line_prefix_len;
20526 let mut in_whitespace = false;
20527 for token in tokenizer {
20528 let have_preceding_whitespace = in_whitespace;
20529 match token {
20530 WordBreakToken::Word {
20531 token,
20532 grapheme_len,
20533 } => {
20534 in_whitespace = false;
20535 if current_line_len + grapheme_len > wrap_column
20536 && current_line_len != line_prefix_len
20537 {
20538 wrapped_text.push_str(current_line.trim_end());
20539 wrapped_text.push('\n');
20540 current_line.truncate(line_prefix.len());
20541 current_line_len = line_prefix_len;
20542 }
20543 current_line.push_str(token);
20544 current_line_len += grapheme_len;
20545 }
20546 WordBreakToken::InlineWhitespace {
20547 mut token,
20548 mut grapheme_len,
20549 } => {
20550 in_whitespace = true;
20551 if have_preceding_whitespace && !preserve_existing_whitespace {
20552 continue;
20553 }
20554 if !preserve_existing_whitespace {
20555 token = " ";
20556 grapheme_len = 1;
20557 }
20558 if current_line_len + grapheme_len > wrap_column {
20559 wrapped_text.push_str(current_line.trim_end());
20560 wrapped_text.push('\n');
20561 current_line.truncate(line_prefix.len());
20562 current_line_len = line_prefix_len;
20563 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
20564 current_line.push_str(token);
20565 current_line_len += grapheme_len;
20566 }
20567 }
20568 WordBreakToken::Newline => {
20569 in_whitespace = true;
20570 if preserve_existing_whitespace {
20571 wrapped_text.push_str(current_line.trim_end());
20572 wrapped_text.push('\n');
20573 current_line.truncate(line_prefix.len());
20574 current_line_len = line_prefix_len;
20575 } else if have_preceding_whitespace {
20576 continue;
20577 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
20578 {
20579 wrapped_text.push_str(current_line.trim_end());
20580 wrapped_text.push('\n');
20581 current_line.truncate(line_prefix.len());
20582 current_line_len = line_prefix_len;
20583 } else if current_line_len != line_prefix_len {
20584 current_line.push(' ');
20585 current_line_len += 1;
20586 }
20587 }
20588 }
20589 }
20590
20591 if !current_line.is_empty() {
20592 wrapped_text.push_str(¤t_line);
20593 }
20594 wrapped_text
20595}
20596
20597#[test]
20598fn test_wrap_with_prefix() {
20599 assert_eq!(
20600 wrap_with_prefix(
20601 "# ".to_string(),
20602 "abcdefg".to_string(),
20603 4,
20604 NonZeroU32::new(4).unwrap(),
20605 false,
20606 ),
20607 "# abcdefg"
20608 );
20609 assert_eq!(
20610 wrap_with_prefix(
20611 "".to_string(),
20612 "\thello world".to_string(),
20613 8,
20614 NonZeroU32::new(4).unwrap(),
20615 false,
20616 ),
20617 "hello\nworld"
20618 );
20619 assert_eq!(
20620 wrap_with_prefix(
20621 "// ".to_string(),
20622 "xx \nyy zz aa bb cc".to_string(),
20623 12,
20624 NonZeroU32::new(4).unwrap(),
20625 false,
20626 ),
20627 "// xx yy zz\n// aa bb cc"
20628 );
20629 assert_eq!(
20630 wrap_with_prefix(
20631 String::new(),
20632 "这是什么 \n 钢笔".to_string(),
20633 3,
20634 NonZeroU32::new(4).unwrap(),
20635 false,
20636 ),
20637 "这是什\n么 钢\n笔"
20638 );
20639}
20640
20641pub trait CollaborationHub {
20642 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
20643 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
20644 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
20645}
20646
20647impl CollaborationHub for Entity<Project> {
20648 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
20649 self.read(cx).collaborators()
20650 }
20651
20652 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
20653 self.read(cx).user_store().read(cx).participant_indices()
20654 }
20655
20656 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
20657 let this = self.read(cx);
20658 let user_ids = this.collaborators().values().map(|c| c.user_id);
20659 this.user_store().read(cx).participant_names(user_ids, cx)
20660 }
20661}
20662
20663pub trait SemanticsProvider {
20664 fn hover(
20665 &self,
20666 buffer: &Entity<Buffer>,
20667 position: text::Anchor,
20668 cx: &mut App,
20669 ) -> Option<Task<Vec<project::Hover>>>;
20670
20671 fn inline_values(
20672 &self,
20673 buffer_handle: Entity<Buffer>,
20674 range: Range<text::Anchor>,
20675 cx: &mut App,
20676 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20677
20678 fn inlay_hints(
20679 &self,
20680 buffer_handle: Entity<Buffer>,
20681 range: Range<text::Anchor>,
20682 cx: &mut App,
20683 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20684
20685 fn resolve_inlay_hint(
20686 &self,
20687 hint: InlayHint,
20688 buffer_handle: Entity<Buffer>,
20689 server_id: LanguageServerId,
20690 cx: &mut App,
20691 ) -> Option<Task<anyhow::Result<InlayHint>>>;
20692
20693 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
20694
20695 fn document_highlights(
20696 &self,
20697 buffer: &Entity<Buffer>,
20698 position: text::Anchor,
20699 cx: &mut App,
20700 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
20701
20702 fn definitions(
20703 &self,
20704 buffer: &Entity<Buffer>,
20705 position: text::Anchor,
20706 kind: GotoDefinitionKind,
20707 cx: &mut App,
20708 ) -> Option<Task<Result<Vec<LocationLink>>>>;
20709
20710 fn range_for_rename(
20711 &self,
20712 buffer: &Entity<Buffer>,
20713 position: text::Anchor,
20714 cx: &mut App,
20715 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
20716
20717 fn perform_rename(
20718 &self,
20719 buffer: &Entity<Buffer>,
20720 position: text::Anchor,
20721 new_name: String,
20722 cx: &mut App,
20723 ) -> Option<Task<Result<ProjectTransaction>>>;
20724
20725 fn pull_diagnostics_for_buffer(
20726 &self,
20727 buffer: Entity<Buffer>,
20728 cx: &mut App,
20729 ) -> Task<anyhow::Result<()>>;
20730}
20731
20732pub trait CompletionProvider {
20733 fn completions(
20734 &self,
20735 excerpt_id: ExcerptId,
20736 buffer: &Entity<Buffer>,
20737 buffer_position: text::Anchor,
20738 trigger: CompletionContext,
20739 window: &mut Window,
20740 cx: &mut Context<Editor>,
20741 ) -> Task<Result<Vec<CompletionResponse>>>;
20742
20743 fn resolve_completions(
20744 &self,
20745 _buffer: Entity<Buffer>,
20746 _completion_indices: Vec<usize>,
20747 _completions: Rc<RefCell<Box<[Completion]>>>,
20748 _cx: &mut Context<Editor>,
20749 ) -> Task<Result<bool>> {
20750 Task::ready(Ok(false))
20751 }
20752
20753 fn apply_additional_edits_for_completion(
20754 &self,
20755 _buffer: Entity<Buffer>,
20756 _completions: Rc<RefCell<Box<[Completion]>>>,
20757 _completion_index: usize,
20758 _push_to_history: bool,
20759 _cx: &mut Context<Editor>,
20760 ) -> Task<Result<Option<language::Transaction>>> {
20761 Task::ready(Ok(None))
20762 }
20763
20764 fn is_completion_trigger(
20765 &self,
20766 buffer: &Entity<Buffer>,
20767 position: language::Anchor,
20768 text: &str,
20769 trigger_in_words: bool,
20770 menu_is_open: bool,
20771 cx: &mut Context<Editor>,
20772 ) -> bool;
20773
20774 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
20775
20776 fn sort_completions(&self) -> bool {
20777 true
20778 }
20779
20780 fn filter_completions(&self) -> bool {
20781 true
20782 }
20783}
20784
20785pub trait CodeActionProvider {
20786 fn id(&self) -> Arc<str>;
20787
20788 fn code_actions(
20789 &self,
20790 buffer: &Entity<Buffer>,
20791 range: Range<text::Anchor>,
20792 window: &mut Window,
20793 cx: &mut App,
20794 ) -> Task<Result<Vec<CodeAction>>>;
20795
20796 fn apply_code_action(
20797 &self,
20798 buffer_handle: Entity<Buffer>,
20799 action: CodeAction,
20800 excerpt_id: ExcerptId,
20801 push_to_history: bool,
20802 window: &mut Window,
20803 cx: &mut App,
20804 ) -> Task<Result<ProjectTransaction>>;
20805}
20806
20807impl CodeActionProvider for Entity<Project> {
20808 fn id(&self) -> Arc<str> {
20809 "project".into()
20810 }
20811
20812 fn code_actions(
20813 &self,
20814 buffer: &Entity<Buffer>,
20815 range: Range<text::Anchor>,
20816 _window: &mut Window,
20817 cx: &mut App,
20818 ) -> Task<Result<Vec<CodeAction>>> {
20819 self.update(cx, |project, cx| {
20820 let code_lens = project.code_lens(buffer, range.clone(), cx);
20821 let code_actions = project.code_actions(buffer, range, None, cx);
20822 cx.background_spawn(async move {
20823 let (code_lens, code_actions) = join(code_lens, code_actions).await;
20824 Ok(code_lens
20825 .context("code lens fetch")?
20826 .into_iter()
20827 .chain(code_actions.context("code action fetch")?)
20828 .collect())
20829 })
20830 })
20831 }
20832
20833 fn apply_code_action(
20834 &self,
20835 buffer_handle: Entity<Buffer>,
20836 action: CodeAction,
20837 _excerpt_id: ExcerptId,
20838 push_to_history: bool,
20839 _window: &mut Window,
20840 cx: &mut App,
20841 ) -> Task<Result<ProjectTransaction>> {
20842 self.update(cx, |project, cx| {
20843 project.apply_code_action(buffer_handle, action, push_to_history, cx)
20844 })
20845 }
20846}
20847
20848fn snippet_completions(
20849 project: &Project,
20850 buffer: &Entity<Buffer>,
20851 buffer_position: text::Anchor,
20852 cx: &mut App,
20853) -> Task<Result<CompletionResponse>> {
20854 let languages = buffer.read(cx).languages_at(buffer_position);
20855 let snippet_store = project.snippets().read(cx);
20856
20857 let scopes: Vec<_> = languages
20858 .iter()
20859 .filter_map(|language| {
20860 let language_name = language.lsp_id();
20861 let snippets = snippet_store.snippets_for(Some(language_name), cx);
20862
20863 if snippets.is_empty() {
20864 None
20865 } else {
20866 Some((language.default_scope(), snippets))
20867 }
20868 })
20869 .collect();
20870
20871 if scopes.is_empty() {
20872 return Task::ready(Ok(CompletionResponse {
20873 completions: vec![],
20874 is_incomplete: false,
20875 }));
20876 }
20877
20878 let snapshot = buffer.read(cx).text_snapshot();
20879 let chars: String = snapshot
20880 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
20881 .collect();
20882 let executor = cx.background_executor().clone();
20883
20884 cx.background_spawn(async move {
20885 let mut is_incomplete = false;
20886 let mut completions: Vec<Completion> = Vec::new();
20887 for (scope, snippets) in scopes.into_iter() {
20888 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
20889 let mut last_word = chars
20890 .chars()
20891 .take_while(|c| classifier.is_word(*c))
20892 .collect::<String>();
20893 last_word = last_word.chars().rev().collect();
20894
20895 if last_word.is_empty() {
20896 return Ok(CompletionResponse {
20897 completions: vec![],
20898 is_incomplete: true,
20899 });
20900 }
20901
20902 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
20903 let to_lsp = |point: &text::Anchor| {
20904 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
20905 point_to_lsp(end)
20906 };
20907 let lsp_end = to_lsp(&buffer_position);
20908
20909 let candidates = snippets
20910 .iter()
20911 .enumerate()
20912 .flat_map(|(ix, snippet)| {
20913 snippet
20914 .prefix
20915 .iter()
20916 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
20917 })
20918 .collect::<Vec<StringMatchCandidate>>();
20919
20920 const MAX_RESULTS: usize = 100;
20921 let mut matches = fuzzy::match_strings(
20922 &candidates,
20923 &last_word,
20924 last_word.chars().any(|c| c.is_uppercase()),
20925 MAX_RESULTS,
20926 &Default::default(),
20927 executor.clone(),
20928 )
20929 .await;
20930
20931 if matches.len() >= MAX_RESULTS {
20932 is_incomplete = true;
20933 }
20934
20935 // Remove all candidates where the query's start does not match the start of any word in the candidate
20936 if let Some(query_start) = last_word.chars().next() {
20937 matches.retain(|string_match| {
20938 split_words(&string_match.string).any(|word| {
20939 // Check that the first codepoint of the word as lowercase matches the first
20940 // codepoint of the query as lowercase
20941 word.chars()
20942 .flat_map(|codepoint| codepoint.to_lowercase())
20943 .zip(query_start.to_lowercase())
20944 .all(|(word_cp, query_cp)| word_cp == query_cp)
20945 })
20946 });
20947 }
20948
20949 let matched_strings = matches
20950 .into_iter()
20951 .map(|m| m.string)
20952 .collect::<HashSet<_>>();
20953
20954 completions.extend(snippets.iter().filter_map(|snippet| {
20955 let matching_prefix = snippet
20956 .prefix
20957 .iter()
20958 .find(|prefix| matched_strings.contains(*prefix))?;
20959 let start = as_offset - last_word.len();
20960 let start = snapshot.anchor_before(start);
20961 let range = start..buffer_position;
20962 let lsp_start = to_lsp(&start);
20963 let lsp_range = lsp::Range {
20964 start: lsp_start,
20965 end: lsp_end,
20966 };
20967 Some(Completion {
20968 replace_range: range,
20969 new_text: snippet.body.clone(),
20970 source: CompletionSource::Lsp {
20971 insert_range: None,
20972 server_id: LanguageServerId(usize::MAX),
20973 resolved: true,
20974 lsp_completion: Box::new(lsp::CompletionItem {
20975 label: snippet.prefix.first().unwrap().clone(),
20976 kind: Some(CompletionItemKind::SNIPPET),
20977 label_details: snippet.description.as_ref().map(|description| {
20978 lsp::CompletionItemLabelDetails {
20979 detail: Some(description.clone()),
20980 description: None,
20981 }
20982 }),
20983 insert_text_format: Some(InsertTextFormat::SNIPPET),
20984 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20985 lsp::InsertReplaceEdit {
20986 new_text: snippet.body.clone(),
20987 insert: lsp_range,
20988 replace: lsp_range,
20989 },
20990 )),
20991 filter_text: Some(snippet.body.clone()),
20992 sort_text: Some(char::MAX.to_string()),
20993 ..lsp::CompletionItem::default()
20994 }),
20995 lsp_defaults: None,
20996 },
20997 label: CodeLabel {
20998 text: matching_prefix.clone(),
20999 runs: Vec::new(),
21000 filter_range: 0..matching_prefix.len(),
21001 },
21002 icon_path: None,
21003 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
21004 single_line: snippet.name.clone().into(),
21005 plain_text: snippet
21006 .description
21007 .clone()
21008 .map(|description| description.into()),
21009 }),
21010 insert_text_mode: None,
21011 confirm: None,
21012 })
21013 }))
21014 }
21015
21016 Ok(CompletionResponse {
21017 completions,
21018 is_incomplete,
21019 })
21020 })
21021}
21022
21023impl CompletionProvider for Entity<Project> {
21024 fn completions(
21025 &self,
21026 _excerpt_id: ExcerptId,
21027 buffer: &Entity<Buffer>,
21028 buffer_position: text::Anchor,
21029 options: CompletionContext,
21030 _window: &mut Window,
21031 cx: &mut Context<Editor>,
21032 ) -> Task<Result<Vec<CompletionResponse>>> {
21033 self.update(cx, |project, cx| {
21034 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21035 let project_completions = project.completions(buffer, buffer_position, options, cx);
21036 cx.background_spawn(async move {
21037 let mut responses = project_completions.await?;
21038 let snippets = snippets.await?;
21039 if !snippets.completions.is_empty() {
21040 responses.push(snippets);
21041 }
21042 Ok(responses)
21043 })
21044 })
21045 }
21046
21047 fn resolve_completions(
21048 &self,
21049 buffer: Entity<Buffer>,
21050 completion_indices: Vec<usize>,
21051 completions: Rc<RefCell<Box<[Completion]>>>,
21052 cx: &mut Context<Editor>,
21053 ) -> Task<Result<bool>> {
21054 self.update(cx, |project, cx| {
21055 project.lsp_store().update(cx, |lsp_store, cx| {
21056 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21057 })
21058 })
21059 }
21060
21061 fn apply_additional_edits_for_completion(
21062 &self,
21063 buffer: Entity<Buffer>,
21064 completions: Rc<RefCell<Box<[Completion]>>>,
21065 completion_index: usize,
21066 push_to_history: bool,
21067 cx: &mut Context<Editor>,
21068 ) -> Task<Result<Option<language::Transaction>>> {
21069 self.update(cx, |project, cx| {
21070 project.lsp_store().update(cx, |lsp_store, cx| {
21071 lsp_store.apply_additional_edits_for_completion(
21072 buffer,
21073 completions,
21074 completion_index,
21075 push_to_history,
21076 cx,
21077 )
21078 })
21079 })
21080 }
21081
21082 fn is_completion_trigger(
21083 &self,
21084 buffer: &Entity<Buffer>,
21085 position: language::Anchor,
21086 text: &str,
21087 trigger_in_words: bool,
21088 menu_is_open: bool,
21089 cx: &mut Context<Editor>,
21090 ) -> bool {
21091 let mut chars = text.chars();
21092 let char = if let Some(char) = chars.next() {
21093 char
21094 } else {
21095 return false;
21096 };
21097 if chars.next().is_some() {
21098 return false;
21099 }
21100
21101 let buffer = buffer.read(cx);
21102 let snapshot = buffer.snapshot();
21103 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
21104 return false;
21105 }
21106 let classifier = snapshot.char_classifier_at(position).for_completion(true);
21107 if trigger_in_words && classifier.is_word(char) {
21108 return true;
21109 }
21110
21111 buffer.completion_triggers().contains(text)
21112 }
21113}
21114
21115impl SemanticsProvider for Entity<Project> {
21116 fn hover(
21117 &self,
21118 buffer: &Entity<Buffer>,
21119 position: text::Anchor,
21120 cx: &mut App,
21121 ) -> Option<Task<Vec<project::Hover>>> {
21122 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
21123 }
21124
21125 fn document_highlights(
21126 &self,
21127 buffer: &Entity<Buffer>,
21128 position: text::Anchor,
21129 cx: &mut App,
21130 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
21131 Some(self.update(cx, |project, cx| {
21132 project.document_highlights(buffer, position, cx)
21133 }))
21134 }
21135
21136 fn definitions(
21137 &self,
21138 buffer: &Entity<Buffer>,
21139 position: text::Anchor,
21140 kind: GotoDefinitionKind,
21141 cx: &mut App,
21142 ) -> Option<Task<Result<Vec<LocationLink>>>> {
21143 Some(self.update(cx, |project, cx| match kind {
21144 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
21145 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
21146 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
21147 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
21148 }))
21149 }
21150
21151 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
21152 // TODO: make this work for remote projects
21153 self.update(cx, |project, cx| {
21154 if project
21155 .active_debug_session(cx)
21156 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
21157 {
21158 return true;
21159 }
21160
21161 buffer.update(cx, |buffer, cx| {
21162 project.any_language_server_supports_inlay_hints(buffer, cx)
21163 })
21164 })
21165 }
21166
21167 fn inline_values(
21168 &self,
21169 buffer_handle: Entity<Buffer>,
21170
21171 range: Range<text::Anchor>,
21172 cx: &mut App,
21173 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21174 self.update(cx, |project, cx| {
21175 let (session, active_stack_frame) = project.active_debug_session(cx)?;
21176
21177 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
21178 })
21179 }
21180
21181 fn inlay_hints(
21182 &self,
21183 buffer_handle: Entity<Buffer>,
21184 range: Range<text::Anchor>,
21185 cx: &mut App,
21186 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21187 Some(self.update(cx, |project, cx| {
21188 project.inlay_hints(buffer_handle, range, cx)
21189 }))
21190 }
21191
21192 fn resolve_inlay_hint(
21193 &self,
21194 hint: InlayHint,
21195 buffer_handle: Entity<Buffer>,
21196 server_id: LanguageServerId,
21197 cx: &mut App,
21198 ) -> Option<Task<anyhow::Result<InlayHint>>> {
21199 Some(self.update(cx, |project, cx| {
21200 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
21201 }))
21202 }
21203
21204 fn range_for_rename(
21205 &self,
21206 buffer: &Entity<Buffer>,
21207 position: text::Anchor,
21208 cx: &mut App,
21209 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
21210 Some(self.update(cx, |project, cx| {
21211 let buffer = buffer.clone();
21212 let task = project.prepare_rename(buffer.clone(), position, cx);
21213 cx.spawn(async move |_, cx| {
21214 Ok(match task.await? {
21215 PrepareRenameResponse::Success(range) => Some(range),
21216 PrepareRenameResponse::InvalidPosition => None,
21217 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
21218 // Fallback on using TreeSitter info to determine identifier range
21219 buffer.read_with(cx, |buffer, _| {
21220 let snapshot = buffer.snapshot();
21221 let (range, kind) = snapshot.surrounding_word(position);
21222 if kind != Some(CharKind::Word) {
21223 return None;
21224 }
21225 Some(
21226 snapshot.anchor_before(range.start)
21227 ..snapshot.anchor_after(range.end),
21228 )
21229 })?
21230 }
21231 })
21232 })
21233 }))
21234 }
21235
21236 fn perform_rename(
21237 &self,
21238 buffer: &Entity<Buffer>,
21239 position: text::Anchor,
21240 new_name: String,
21241 cx: &mut App,
21242 ) -> Option<Task<Result<ProjectTransaction>>> {
21243 Some(self.update(cx, |project, cx| {
21244 project.perform_rename(buffer.clone(), position, new_name, cx)
21245 }))
21246 }
21247
21248 fn pull_diagnostics_for_buffer(
21249 &self,
21250 buffer: Entity<Buffer>,
21251 cx: &mut App,
21252 ) -> Task<anyhow::Result<()>> {
21253 let diagnostics = self.update(cx, |project, cx| {
21254 project
21255 .lsp_store()
21256 .update(cx, |lsp_store, cx| lsp_store.pull_diagnostics(buffer, cx))
21257 });
21258 let project = self.clone();
21259 cx.spawn(async move |cx| {
21260 let diagnostics = diagnostics.await.context("pulling diagnostics")?;
21261 project.update(cx, |project, cx| {
21262 project.lsp_store().update(cx, |lsp_store, cx| {
21263 for diagnostics_set in diagnostics {
21264 let LspPullDiagnostics::Response {
21265 server_id,
21266 uri,
21267 diagnostics,
21268 } = diagnostics_set
21269 else {
21270 continue;
21271 };
21272
21273 let adapter = lsp_store.language_server_adapter_for_id(server_id);
21274 let disk_based_sources = adapter
21275 .as_ref()
21276 .map(|adapter| adapter.disk_based_diagnostic_sources.as_slice())
21277 .unwrap_or(&[]);
21278 match diagnostics {
21279 PulledDiagnostics::Unchanged { result_id } => {
21280 lsp_store
21281 .merge_diagnostics(
21282 server_id,
21283 lsp::PublishDiagnosticsParams {
21284 uri: uri.clone(),
21285 diagnostics: Vec::new(),
21286 version: None,
21287 },
21288 Some(result_id),
21289 DiagnosticSourceKind::Pulled,
21290 disk_based_sources,
21291 |_, _| true,
21292 cx,
21293 )
21294 .log_err();
21295 }
21296 PulledDiagnostics::Changed {
21297 diagnostics,
21298 result_id,
21299 } => {
21300 lsp_store
21301 .merge_diagnostics(
21302 server_id,
21303 lsp::PublishDiagnosticsParams {
21304 uri: uri.clone(),
21305 diagnostics,
21306 version: None,
21307 },
21308 result_id,
21309 DiagnosticSourceKind::Pulled,
21310 disk_based_sources,
21311 |old_diagnostic, _| match old_diagnostic.source_kind {
21312 DiagnosticSourceKind::Pulled => false,
21313 DiagnosticSourceKind::Other
21314 | DiagnosticSourceKind::Pushed => true,
21315 },
21316 cx,
21317 )
21318 .log_err();
21319 }
21320 }
21321 }
21322 })
21323 })
21324 })
21325 }
21326}
21327
21328fn inlay_hint_settings(
21329 location: Anchor,
21330 snapshot: &MultiBufferSnapshot,
21331 cx: &mut Context<Editor>,
21332) -> InlayHintSettings {
21333 let file = snapshot.file_at(location);
21334 let language = snapshot.language_at(location).map(|l| l.name());
21335 language_settings(language, file, cx).inlay_hints
21336}
21337
21338fn consume_contiguous_rows(
21339 contiguous_row_selections: &mut Vec<Selection<Point>>,
21340 selection: &Selection<Point>,
21341 display_map: &DisplaySnapshot,
21342 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
21343) -> (MultiBufferRow, MultiBufferRow) {
21344 contiguous_row_selections.push(selection.clone());
21345 let start_row = MultiBufferRow(selection.start.row);
21346 let mut end_row = ending_row(selection, display_map);
21347
21348 while let Some(next_selection) = selections.peek() {
21349 if next_selection.start.row <= end_row.0 {
21350 end_row = ending_row(next_selection, display_map);
21351 contiguous_row_selections.push(selections.next().unwrap().clone());
21352 } else {
21353 break;
21354 }
21355 }
21356 (start_row, end_row)
21357}
21358
21359fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
21360 if next_selection.end.column > 0 || next_selection.is_empty() {
21361 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
21362 } else {
21363 MultiBufferRow(next_selection.end.row)
21364 }
21365}
21366
21367impl EditorSnapshot {
21368 pub fn remote_selections_in_range<'a>(
21369 &'a self,
21370 range: &'a Range<Anchor>,
21371 collaboration_hub: &dyn CollaborationHub,
21372 cx: &'a App,
21373 ) -> impl 'a + Iterator<Item = RemoteSelection> {
21374 let participant_names = collaboration_hub.user_names(cx);
21375 let participant_indices = collaboration_hub.user_participant_indices(cx);
21376 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
21377 let collaborators_by_replica_id = collaborators_by_peer_id
21378 .values()
21379 .map(|collaborator| (collaborator.replica_id, collaborator))
21380 .collect::<HashMap<_, _>>();
21381 self.buffer_snapshot
21382 .selections_in_range(range, false)
21383 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
21384 if replica_id == AGENT_REPLICA_ID {
21385 Some(RemoteSelection {
21386 replica_id,
21387 selection,
21388 cursor_shape,
21389 line_mode,
21390 collaborator_id: CollaboratorId::Agent,
21391 user_name: Some("Agent".into()),
21392 color: cx.theme().players().agent(),
21393 })
21394 } else {
21395 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
21396 let participant_index = participant_indices.get(&collaborator.user_id).copied();
21397 let user_name = participant_names.get(&collaborator.user_id).cloned();
21398 Some(RemoteSelection {
21399 replica_id,
21400 selection,
21401 cursor_shape,
21402 line_mode,
21403 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
21404 user_name,
21405 color: if let Some(index) = participant_index {
21406 cx.theme().players().color_for_participant(index.0)
21407 } else {
21408 cx.theme().players().absent()
21409 },
21410 })
21411 }
21412 })
21413 }
21414
21415 pub fn hunks_for_ranges(
21416 &self,
21417 ranges: impl IntoIterator<Item = Range<Point>>,
21418 ) -> Vec<MultiBufferDiffHunk> {
21419 let mut hunks = Vec::new();
21420 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
21421 HashMap::default();
21422 for query_range in ranges {
21423 let query_rows =
21424 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
21425 for hunk in self.buffer_snapshot.diff_hunks_in_range(
21426 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
21427 ) {
21428 // Include deleted hunks that are adjacent to the query range, because
21429 // otherwise they would be missed.
21430 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
21431 if hunk.status().is_deleted() {
21432 intersects_range |= hunk.row_range.start == query_rows.end;
21433 intersects_range |= hunk.row_range.end == query_rows.start;
21434 }
21435 if intersects_range {
21436 if !processed_buffer_rows
21437 .entry(hunk.buffer_id)
21438 .or_default()
21439 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
21440 {
21441 continue;
21442 }
21443 hunks.push(hunk);
21444 }
21445 }
21446 }
21447
21448 hunks
21449 }
21450
21451 fn display_diff_hunks_for_rows<'a>(
21452 &'a self,
21453 display_rows: Range<DisplayRow>,
21454 folded_buffers: &'a HashSet<BufferId>,
21455 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
21456 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
21457 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
21458
21459 self.buffer_snapshot
21460 .diff_hunks_in_range(buffer_start..buffer_end)
21461 .filter_map(|hunk| {
21462 if folded_buffers.contains(&hunk.buffer_id) {
21463 return None;
21464 }
21465
21466 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
21467 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
21468
21469 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
21470 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
21471
21472 let display_hunk = if hunk_display_start.column() != 0 {
21473 DisplayDiffHunk::Folded {
21474 display_row: hunk_display_start.row(),
21475 }
21476 } else {
21477 let mut end_row = hunk_display_end.row();
21478 if hunk_display_end.column() > 0 {
21479 end_row.0 += 1;
21480 }
21481 let is_created_file = hunk.is_created_file();
21482 DisplayDiffHunk::Unfolded {
21483 status: hunk.status(),
21484 diff_base_byte_range: hunk.diff_base_byte_range,
21485 display_row_range: hunk_display_start.row()..end_row,
21486 multi_buffer_range: Anchor::range_in_buffer(
21487 hunk.excerpt_id,
21488 hunk.buffer_id,
21489 hunk.buffer_range,
21490 ),
21491 is_created_file,
21492 }
21493 };
21494
21495 Some(display_hunk)
21496 })
21497 }
21498
21499 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
21500 self.display_snapshot.buffer_snapshot.language_at(position)
21501 }
21502
21503 pub fn is_focused(&self) -> bool {
21504 self.is_focused
21505 }
21506
21507 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
21508 self.placeholder_text.as_ref()
21509 }
21510
21511 pub fn scroll_position(&self) -> gpui::Point<f32> {
21512 self.scroll_anchor.scroll_position(&self.display_snapshot)
21513 }
21514
21515 fn gutter_dimensions(
21516 &self,
21517 font_id: FontId,
21518 font_size: Pixels,
21519 max_line_number_width: Pixels,
21520 cx: &App,
21521 ) -> Option<GutterDimensions> {
21522 if !self.show_gutter {
21523 return None;
21524 }
21525
21526 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
21527 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
21528
21529 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
21530 matches!(
21531 ProjectSettings::get_global(cx).git.git_gutter,
21532 Some(GitGutterSetting::TrackedFiles)
21533 )
21534 });
21535 let gutter_settings = EditorSettings::get_global(cx).gutter;
21536 let show_line_numbers = self
21537 .show_line_numbers
21538 .unwrap_or(gutter_settings.line_numbers);
21539 let line_gutter_width = if show_line_numbers {
21540 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
21541 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
21542 max_line_number_width.max(min_width_for_number_on_gutter)
21543 } else {
21544 0.0.into()
21545 };
21546
21547 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
21548 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
21549
21550 let git_blame_entries_width =
21551 self.git_blame_gutter_max_author_length
21552 .map(|max_author_length| {
21553 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
21554 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
21555
21556 /// The number of characters to dedicate to gaps and margins.
21557 const SPACING_WIDTH: usize = 4;
21558
21559 let max_char_count = max_author_length.min(renderer.max_author_length())
21560 + ::git::SHORT_SHA_LENGTH
21561 + MAX_RELATIVE_TIMESTAMP.len()
21562 + SPACING_WIDTH;
21563
21564 em_advance * max_char_count
21565 });
21566
21567 let is_singleton = self.buffer_snapshot.is_singleton();
21568
21569 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
21570 left_padding += if !is_singleton {
21571 em_width * 4.0
21572 } else if show_runnables || show_breakpoints {
21573 em_width * 3.0
21574 } else if show_git_gutter && show_line_numbers {
21575 em_width * 2.0
21576 } else if show_git_gutter || show_line_numbers {
21577 em_width
21578 } else {
21579 px(0.)
21580 };
21581
21582 let shows_folds = is_singleton && gutter_settings.folds;
21583
21584 let right_padding = if shows_folds && show_line_numbers {
21585 em_width * 4.0
21586 } else if shows_folds || (!is_singleton && show_line_numbers) {
21587 em_width * 3.0
21588 } else if show_line_numbers {
21589 em_width
21590 } else {
21591 px(0.)
21592 };
21593
21594 Some(GutterDimensions {
21595 left_padding,
21596 right_padding,
21597 width: line_gutter_width + left_padding + right_padding,
21598 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
21599 git_blame_entries_width,
21600 })
21601 }
21602
21603 pub fn render_crease_toggle(
21604 &self,
21605 buffer_row: MultiBufferRow,
21606 row_contains_cursor: bool,
21607 editor: Entity<Editor>,
21608 window: &mut Window,
21609 cx: &mut App,
21610 ) -> Option<AnyElement> {
21611 let folded = self.is_line_folded(buffer_row);
21612 let mut is_foldable = false;
21613
21614 if let Some(crease) = self
21615 .crease_snapshot
21616 .query_row(buffer_row, &self.buffer_snapshot)
21617 {
21618 is_foldable = true;
21619 match crease {
21620 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
21621 if let Some(render_toggle) = render_toggle {
21622 let toggle_callback =
21623 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
21624 if folded {
21625 editor.update(cx, |editor, cx| {
21626 editor.fold_at(buffer_row, window, cx)
21627 });
21628 } else {
21629 editor.update(cx, |editor, cx| {
21630 editor.unfold_at(buffer_row, window, cx)
21631 });
21632 }
21633 });
21634 return Some((render_toggle)(
21635 buffer_row,
21636 folded,
21637 toggle_callback,
21638 window,
21639 cx,
21640 ));
21641 }
21642 }
21643 }
21644 }
21645
21646 is_foldable |= self.starts_indent(buffer_row);
21647
21648 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
21649 Some(
21650 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
21651 .toggle_state(folded)
21652 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
21653 if folded {
21654 this.unfold_at(buffer_row, window, cx);
21655 } else {
21656 this.fold_at(buffer_row, window, cx);
21657 }
21658 }))
21659 .into_any_element(),
21660 )
21661 } else {
21662 None
21663 }
21664 }
21665
21666 pub fn render_crease_trailer(
21667 &self,
21668 buffer_row: MultiBufferRow,
21669 window: &mut Window,
21670 cx: &mut App,
21671 ) -> Option<AnyElement> {
21672 let folded = self.is_line_folded(buffer_row);
21673 if let Crease::Inline { render_trailer, .. } = self
21674 .crease_snapshot
21675 .query_row(buffer_row, &self.buffer_snapshot)?
21676 {
21677 let render_trailer = render_trailer.as_ref()?;
21678 Some(render_trailer(buffer_row, folded, window, cx))
21679 } else {
21680 None
21681 }
21682 }
21683}
21684
21685impl Deref for EditorSnapshot {
21686 type Target = DisplaySnapshot;
21687
21688 fn deref(&self) -> &Self::Target {
21689 &self.display_snapshot
21690 }
21691}
21692
21693#[derive(Clone, Debug, PartialEq, Eq)]
21694pub enum EditorEvent {
21695 InputIgnored {
21696 text: Arc<str>,
21697 },
21698 InputHandled {
21699 utf16_range_to_replace: Option<Range<isize>>,
21700 text: Arc<str>,
21701 },
21702 ExcerptsAdded {
21703 buffer: Entity<Buffer>,
21704 predecessor: ExcerptId,
21705 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
21706 },
21707 ExcerptsRemoved {
21708 ids: Vec<ExcerptId>,
21709 removed_buffer_ids: Vec<BufferId>,
21710 },
21711 BufferFoldToggled {
21712 ids: Vec<ExcerptId>,
21713 folded: bool,
21714 },
21715 ExcerptsEdited {
21716 ids: Vec<ExcerptId>,
21717 },
21718 ExcerptsExpanded {
21719 ids: Vec<ExcerptId>,
21720 },
21721 BufferEdited,
21722 Edited {
21723 transaction_id: clock::Lamport,
21724 },
21725 Reparsed(BufferId),
21726 Focused,
21727 FocusedIn,
21728 Blurred,
21729 DirtyChanged,
21730 Saved,
21731 TitleChanged,
21732 DiffBaseChanged,
21733 SelectionsChanged {
21734 local: bool,
21735 },
21736 ScrollPositionChanged {
21737 local: bool,
21738 autoscroll: bool,
21739 },
21740 Closed,
21741 TransactionUndone {
21742 transaction_id: clock::Lamport,
21743 },
21744 TransactionBegun {
21745 transaction_id: clock::Lamport,
21746 },
21747 Reloaded,
21748 CursorShapeChanged,
21749 PushedToNavHistory {
21750 anchor: Anchor,
21751 is_deactivate: bool,
21752 },
21753}
21754
21755impl EventEmitter<EditorEvent> for Editor {}
21756
21757impl Focusable for Editor {
21758 fn focus_handle(&self, _cx: &App) -> FocusHandle {
21759 self.focus_handle.clone()
21760 }
21761}
21762
21763impl Render for Editor {
21764 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21765 let settings = ThemeSettings::get_global(cx);
21766
21767 let mut text_style = match self.mode {
21768 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
21769 color: cx.theme().colors().editor_foreground,
21770 font_family: settings.ui_font.family.clone(),
21771 font_features: settings.ui_font.features.clone(),
21772 font_fallbacks: settings.ui_font.fallbacks.clone(),
21773 font_size: rems(0.875).into(),
21774 font_weight: settings.ui_font.weight,
21775 line_height: relative(settings.buffer_line_height.value()),
21776 ..Default::default()
21777 },
21778 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
21779 color: cx.theme().colors().editor_foreground,
21780 font_family: settings.buffer_font.family.clone(),
21781 font_features: settings.buffer_font.features.clone(),
21782 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21783 font_size: settings.buffer_font_size(cx).into(),
21784 font_weight: settings.buffer_font.weight,
21785 line_height: relative(settings.buffer_line_height.value()),
21786 ..Default::default()
21787 },
21788 };
21789 if let Some(text_style_refinement) = &self.text_style_refinement {
21790 text_style.refine(text_style_refinement)
21791 }
21792
21793 let background = match self.mode {
21794 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
21795 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
21796 EditorMode::Full { .. } => cx.theme().colors().editor_background,
21797 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
21798 };
21799
21800 EditorElement::new(
21801 &cx.entity(),
21802 EditorStyle {
21803 background,
21804 local_player: cx.theme().players().local(),
21805 text: text_style,
21806 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
21807 syntax: cx.theme().syntax().clone(),
21808 status: cx.theme().status().clone(),
21809 inlay_hints_style: make_inlay_hints_style(cx),
21810 inline_completion_styles: make_suggestion_styles(cx),
21811 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
21812 show_underlines: !self.mode.is_minimap(),
21813 },
21814 )
21815 }
21816}
21817
21818impl EntityInputHandler for Editor {
21819 fn text_for_range(
21820 &mut self,
21821 range_utf16: Range<usize>,
21822 adjusted_range: &mut Option<Range<usize>>,
21823 _: &mut Window,
21824 cx: &mut Context<Self>,
21825 ) -> Option<String> {
21826 let snapshot = self.buffer.read(cx).read(cx);
21827 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
21828 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
21829 if (start.0..end.0) != range_utf16 {
21830 adjusted_range.replace(start.0..end.0);
21831 }
21832 Some(snapshot.text_for_range(start..end).collect())
21833 }
21834
21835 fn selected_text_range(
21836 &mut self,
21837 ignore_disabled_input: bool,
21838 _: &mut Window,
21839 cx: &mut Context<Self>,
21840 ) -> Option<UTF16Selection> {
21841 // Prevent the IME menu from appearing when holding down an alphabetic key
21842 // while input is disabled.
21843 if !ignore_disabled_input && !self.input_enabled {
21844 return None;
21845 }
21846
21847 let selection = self.selections.newest::<OffsetUtf16>(cx);
21848 let range = selection.range();
21849
21850 Some(UTF16Selection {
21851 range: range.start.0..range.end.0,
21852 reversed: selection.reversed,
21853 })
21854 }
21855
21856 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
21857 let snapshot = self.buffer.read(cx).read(cx);
21858 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
21859 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
21860 }
21861
21862 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21863 self.clear_highlights::<InputComposition>(cx);
21864 self.ime_transaction.take();
21865 }
21866
21867 fn replace_text_in_range(
21868 &mut self,
21869 range_utf16: Option<Range<usize>>,
21870 text: &str,
21871 window: &mut Window,
21872 cx: &mut Context<Self>,
21873 ) {
21874 if !self.input_enabled {
21875 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21876 return;
21877 }
21878
21879 self.transact(window, cx, |this, window, cx| {
21880 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
21881 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21882 Some(this.selection_replacement_ranges(range_utf16, cx))
21883 } else {
21884 this.marked_text_ranges(cx)
21885 };
21886
21887 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
21888 let newest_selection_id = this.selections.newest_anchor().id;
21889 this.selections
21890 .all::<OffsetUtf16>(cx)
21891 .iter()
21892 .zip(ranges_to_replace.iter())
21893 .find_map(|(selection, range)| {
21894 if selection.id == newest_selection_id {
21895 Some(
21896 (range.start.0 as isize - selection.head().0 as isize)
21897 ..(range.end.0 as isize - selection.head().0 as isize),
21898 )
21899 } else {
21900 None
21901 }
21902 })
21903 });
21904
21905 cx.emit(EditorEvent::InputHandled {
21906 utf16_range_to_replace: range_to_replace,
21907 text: text.into(),
21908 });
21909
21910 if let Some(new_selected_ranges) = new_selected_ranges {
21911 this.change_selections(None, window, cx, |selections| {
21912 selections.select_ranges(new_selected_ranges)
21913 });
21914 this.backspace(&Default::default(), window, cx);
21915 }
21916
21917 this.handle_input(text, window, cx);
21918 });
21919
21920 if let Some(transaction) = self.ime_transaction {
21921 self.buffer.update(cx, |buffer, cx| {
21922 buffer.group_until_transaction(transaction, cx);
21923 });
21924 }
21925
21926 self.unmark_text(window, cx);
21927 }
21928
21929 fn replace_and_mark_text_in_range(
21930 &mut self,
21931 range_utf16: Option<Range<usize>>,
21932 text: &str,
21933 new_selected_range_utf16: Option<Range<usize>>,
21934 window: &mut Window,
21935 cx: &mut Context<Self>,
21936 ) {
21937 if !self.input_enabled {
21938 return;
21939 }
21940
21941 let transaction = self.transact(window, cx, |this, window, cx| {
21942 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
21943 let snapshot = this.buffer.read(cx).read(cx);
21944 if let Some(relative_range_utf16) = range_utf16.as_ref() {
21945 for marked_range in &mut marked_ranges {
21946 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
21947 marked_range.start.0 += relative_range_utf16.start;
21948 marked_range.start =
21949 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
21950 marked_range.end =
21951 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
21952 }
21953 }
21954 Some(marked_ranges)
21955 } else if let Some(range_utf16) = range_utf16 {
21956 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21957 Some(this.selection_replacement_ranges(range_utf16, cx))
21958 } else {
21959 None
21960 };
21961
21962 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
21963 let newest_selection_id = this.selections.newest_anchor().id;
21964 this.selections
21965 .all::<OffsetUtf16>(cx)
21966 .iter()
21967 .zip(ranges_to_replace.iter())
21968 .find_map(|(selection, range)| {
21969 if selection.id == newest_selection_id {
21970 Some(
21971 (range.start.0 as isize - selection.head().0 as isize)
21972 ..(range.end.0 as isize - selection.head().0 as isize),
21973 )
21974 } else {
21975 None
21976 }
21977 })
21978 });
21979
21980 cx.emit(EditorEvent::InputHandled {
21981 utf16_range_to_replace: range_to_replace,
21982 text: text.into(),
21983 });
21984
21985 if let Some(ranges) = ranges_to_replace {
21986 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
21987 }
21988
21989 let marked_ranges = {
21990 let snapshot = this.buffer.read(cx).read(cx);
21991 this.selections
21992 .disjoint_anchors()
21993 .iter()
21994 .map(|selection| {
21995 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
21996 })
21997 .collect::<Vec<_>>()
21998 };
21999
22000 if text.is_empty() {
22001 this.unmark_text(window, cx);
22002 } else {
22003 this.highlight_text::<InputComposition>(
22004 marked_ranges.clone(),
22005 HighlightStyle {
22006 underline: Some(UnderlineStyle {
22007 thickness: px(1.),
22008 color: None,
22009 wavy: false,
22010 }),
22011 ..Default::default()
22012 },
22013 cx,
22014 );
22015 }
22016
22017 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22018 let use_autoclose = this.use_autoclose;
22019 let use_auto_surround = this.use_auto_surround;
22020 this.set_use_autoclose(false);
22021 this.set_use_auto_surround(false);
22022 this.handle_input(text, window, cx);
22023 this.set_use_autoclose(use_autoclose);
22024 this.set_use_auto_surround(use_auto_surround);
22025
22026 if let Some(new_selected_range) = new_selected_range_utf16 {
22027 let snapshot = this.buffer.read(cx).read(cx);
22028 let new_selected_ranges = marked_ranges
22029 .into_iter()
22030 .map(|marked_range| {
22031 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22032 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22033 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22034 snapshot.clip_offset_utf16(new_start, Bias::Left)
22035 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22036 })
22037 .collect::<Vec<_>>();
22038
22039 drop(snapshot);
22040 this.change_selections(None, window, cx, |selections| {
22041 selections.select_ranges(new_selected_ranges)
22042 });
22043 }
22044 });
22045
22046 self.ime_transaction = self.ime_transaction.or(transaction);
22047 if let Some(transaction) = self.ime_transaction {
22048 self.buffer.update(cx, |buffer, cx| {
22049 buffer.group_until_transaction(transaction, cx);
22050 });
22051 }
22052
22053 if self.text_highlights::<InputComposition>(cx).is_none() {
22054 self.ime_transaction.take();
22055 }
22056 }
22057
22058 fn bounds_for_range(
22059 &mut self,
22060 range_utf16: Range<usize>,
22061 element_bounds: gpui::Bounds<Pixels>,
22062 window: &mut Window,
22063 cx: &mut Context<Self>,
22064 ) -> Option<gpui::Bounds<Pixels>> {
22065 let text_layout_details = self.text_layout_details(window);
22066 let gpui::Size {
22067 width: em_width,
22068 height: line_height,
22069 } = self.character_size(window);
22070
22071 let snapshot = self.snapshot(window, cx);
22072 let scroll_position = snapshot.scroll_position();
22073 let scroll_left = scroll_position.x * em_width;
22074
22075 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22076 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22077 + self.gutter_dimensions.width
22078 + self.gutter_dimensions.margin;
22079 let y = line_height * (start.row().as_f32() - scroll_position.y);
22080
22081 Some(Bounds {
22082 origin: element_bounds.origin + point(x, y),
22083 size: size(em_width, line_height),
22084 })
22085 }
22086
22087 fn character_index_for_point(
22088 &mut self,
22089 point: gpui::Point<Pixels>,
22090 _window: &mut Window,
22091 _cx: &mut Context<Self>,
22092 ) -> Option<usize> {
22093 let position_map = self.last_position_map.as_ref()?;
22094 if !position_map.text_hitbox.contains(&point) {
22095 return None;
22096 }
22097 let display_point = position_map.point_for_position(point).previous_valid;
22098 let anchor = position_map
22099 .snapshot
22100 .display_point_to_anchor(display_point, Bias::Left);
22101 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22102 Some(utf16_offset.0)
22103 }
22104}
22105
22106trait SelectionExt {
22107 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22108 fn spanned_rows(
22109 &self,
22110 include_end_if_at_line_start: bool,
22111 map: &DisplaySnapshot,
22112 ) -> Range<MultiBufferRow>;
22113}
22114
22115impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22116 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22117 let start = self
22118 .start
22119 .to_point(&map.buffer_snapshot)
22120 .to_display_point(map);
22121 let end = self
22122 .end
22123 .to_point(&map.buffer_snapshot)
22124 .to_display_point(map);
22125 if self.reversed {
22126 end..start
22127 } else {
22128 start..end
22129 }
22130 }
22131
22132 fn spanned_rows(
22133 &self,
22134 include_end_if_at_line_start: bool,
22135 map: &DisplaySnapshot,
22136 ) -> Range<MultiBufferRow> {
22137 let start = self.start.to_point(&map.buffer_snapshot);
22138 let mut end = self.end.to_point(&map.buffer_snapshot);
22139 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22140 end.row -= 1;
22141 }
22142
22143 let buffer_start = map.prev_line_boundary(start).0;
22144 let buffer_end = map.next_line_boundary(end).0;
22145 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22146 }
22147}
22148
22149impl<T: InvalidationRegion> InvalidationStack<T> {
22150 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22151 where
22152 S: Clone + ToOffset,
22153 {
22154 while let Some(region) = self.last() {
22155 let all_selections_inside_invalidation_ranges =
22156 if selections.len() == region.ranges().len() {
22157 selections
22158 .iter()
22159 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
22160 .all(|(selection, invalidation_range)| {
22161 let head = selection.head().to_offset(buffer);
22162 invalidation_range.start <= head && invalidation_range.end >= head
22163 })
22164 } else {
22165 false
22166 };
22167
22168 if all_selections_inside_invalidation_ranges {
22169 break;
22170 } else {
22171 self.pop();
22172 }
22173 }
22174 }
22175}
22176
22177impl<T> Default for InvalidationStack<T> {
22178 fn default() -> Self {
22179 Self(Default::default())
22180 }
22181}
22182
22183impl<T> Deref for InvalidationStack<T> {
22184 type Target = Vec<T>;
22185
22186 fn deref(&self) -> &Self::Target {
22187 &self.0
22188 }
22189}
22190
22191impl<T> DerefMut for InvalidationStack<T> {
22192 fn deref_mut(&mut self) -> &mut Self::Target {
22193 &mut self.0
22194 }
22195}
22196
22197impl InvalidationRegion for SnippetState {
22198 fn ranges(&self) -> &[Range<Anchor>] {
22199 &self.ranges[self.active_index]
22200 }
22201}
22202
22203fn inline_completion_edit_text(
22204 current_snapshot: &BufferSnapshot,
22205 edits: &[(Range<Anchor>, String)],
22206 edit_preview: &EditPreview,
22207 include_deletions: bool,
22208 cx: &App,
22209) -> HighlightedText {
22210 let edits = edits
22211 .iter()
22212 .map(|(anchor, text)| {
22213 (
22214 anchor.start.text_anchor..anchor.end.text_anchor,
22215 text.clone(),
22216 )
22217 })
22218 .collect::<Vec<_>>();
22219
22220 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
22221}
22222
22223pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
22224 match severity {
22225 lsp::DiagnosticSeverity::ERROR => colors.error,
22226 lsp::DiagnosticSeverity::WARNING => colors.warning,
22227 lsp::DiagnosticSeverity::INFORMATION => colors.info,
22228 lsp::DiagnosticSeverity::HINT => colors.info,
22229 _ => colors.ignored,
22230 }
22231}
22232
22233pub fn styled_runs_for_code_label<'a>(
22234 label: &'a CodeLabel,
22235 syntax_theme: &'a theme::SyntaxTheme,
22236) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
22237 let fade_out = HighlightStyle {
22238 fade_out: Some(0.35),
22239 ..Default::default()
22240 };
22241
22242 let mut prev_end = label.filter_range.end;
22243 label
22244 .runs
22245 .iter()
22246 .enumerate()
22247 .flat_map(move |(ix, (range, highlight_id))| {
22248 let style = if let Some(style) = highlight_id.style(syntax_theme) {
22249 style
22250 } else {
22251 return Default::default();
22252 };
22253 let mut muted_style = style;
22254 muted_style.highlight(fade_out);
22255
22256 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
22257 if range.start >= label.filter_range.end {
22258 if range.start > prev_end {
22259 runs.push((prev_end..range.start, fade_out));
22260 }
22261 runs.push((range.clone(), muted_style));
22262 } else if range.end <= label.filter_range.end {
22263 runs.push((range.clone(), style));
22264 } else {
22265 runs.push((range.start..label.filter_range.end, style));
22266 runs.push((label.filter_range.end..range.end, muted_style));
22267 }
22268 prev_end = cmp::max(prev_end, range.end);
22269
22270 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
22271 runs.push((prev_end..label.text.len(), fade_out));
22272 }
22273
22274 runs
22275 })
22276}
22277
22278pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
22279 let mut prev_index = 0;
22280 let mut prev_codepoint: Option<char> = None;
22281 text.char_indices()
22282 .chain([(text.len(), '\0')])
22283 .filter_map(move |(index, codepoint)| {
22284 let prev_codepoint = prev_codepoint.replace(codepoint)?;
22285 let is_boundary = index == text.len()
22286 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
22287 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
22288 if is_boundary {
22289 let chunk = &text[prev_index..index];
22290 prev_index = index;
22291 Some(chunk)
22292 } else {
22293 None
22294 }
22295 })
22296}
22297
22298pub trait RangeToAnchorExt: Sized {
22299 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
22300
22301 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
22302 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
22303 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
22304 }
22305}
22306
22307impl<T: ToOffset> RangeToAnchorExt for Range<T> {
22308 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
22309 let start_offset = self.start.to_offset(snapshot);
22310 let end_offset = self.end.to_offset(snapshot);
22311 if start_offset == end_offset {
22312 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
22313 } else {
22314 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
22315 }
22316 }
22317}
22318
22319pub trait RowExt {
22320 fn as_f32(&self) -> f32;
22321
22322 fn next_row(&self) -> Self;
22323
22324 fn previous_row(&self) -> Self;
22325
22326 fn minus(&self, other: Self) -> u32;
22327}
22328
22329impl RowExt for DisplayRow {
22330 fn as_f32(&self) -> f32 {
22331 self.0 as f32
22332 }
22333
22334 fn next_row(&self) -> Self {
22335 Self(self.0 + 1)
22336 }
22337
22338 fn previous_row(&self) -> Self {
22339 Self(self.0.saturating_sub(1))
22340 }
22341
22342 fn minus(&self, other: Self) -> u32 {
22343 self.0 - other.0
22344 }
22345}
22346
22347impl RowExt for MultiBufferRow {
22348 fn as_f32(&self) -> f32 {
22349 self.0 as f32
22350 }
22351
22352 fn next_row(&self) -> Self {
22353 Self(self.0 + 1)
22354 }
22355
22356 fn previous_row(&self) -> Self {
22357 Self(self.0.saturating_sub(1))
22358 }
22359
22360 fn minus(&self, other: Self) -> u32 {
22361 self.0 - other.0
22362 }
22363}
22364
22365trait RowRangeExt {
22366 type Row;
22367
22368 fn len(&self) -> usize;
22369
22370 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
22371}
22372
22373impl RowRangeExt for Range<MultiBufferRow> {
22374 type Row = MultiBufferRow;
22375
22376 fn len(&self) -> usize {
22377 (self.end.0 - self.start.0) as usize
22378 }
22379
22380 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
22381 (self.start.0..self.end.0).map(MultiBufferRow)
22382 }
22383}
22384
22385impl RowRangeExt for Range<DisplayRow> {
22386 type Row = DisplayRow;
22387
22388 fn len(&self) -> usize {
22389 (self.end.0 - self.start.0) as usize
22390 }
22391
22392 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
22393 (self.start.0..self.end.0).map(DisplayRow)
22394 }
22395}
22396
22397/// If select range has more than one line, we
22398/// just point the cursor to range.start.
22399fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
22400 if range.start.row == range.end.row {
22401 range
22402 } else {
22403 range.start..range.start
22404 }
22405}
22406pub struct KillRing(ClipboardItem);
22407impl Global for KillRing {}
22408
22409const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
22410
22411enum BreakpointPromptEditAction {
22412 Log,
22413 Condition,
22414 HitCondition,
22415}
22416
22417struct BreakpointPromptEditor {
22418 pub(crate) prompt: Entity<Editor>,
22419 editor: WeakEntity<Editor>,
22420 breakpoint_anchor: Anchor,
22421 breakpoint: Breakpoint,
22422 edit_action: BreakpointPromptEditAction,
22423 block_ids: HashSet<CustomBlockId>,
22424 editor_margins: Arc<Mutex<EditorMargins>>,
22425 _subscriptions: Vec<Subscription>,
22426}
22427
22428impl BreakpointPromptEditor {
22429 const MAX_LINES: u8 = 4;
22430
22431 fn new(
22432 editor: WeakEntity<Editor>,
22433 breakpoint_anchor: Anchor,
22434 breakpoint: Breakpoint,
22435 edit_action: BreakpointPromptEditAction,
22436 window: &mut Window,
22437 cx: &mut Context<Self>,
22438 ) -> Self {
22439 let base_text = match edit_action {
22440 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
22441 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
22442 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
22443 }
22444 .map(|msg| msg.to_string())
22445 .unwrap_or_default();
22446
22447 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
22448 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
22449
22450 let prompt = cx.new(|cx| {
22451 let mut prompt = Editor::new(
22452 EditorMode::AutoHeight {
22453 max_lines: Self::MAX_LINES as usize,
22454 },
22455 buffer,
22456 None,
22457 window,
22458 cx,
22459 );
22460 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
22461 prompt.set_show_cursor_when_unfocused(false, cx);
22462 prompt.set_placeholder_text(
22463 match edit_action {
22464 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
22465 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
22466 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
22467 },
22468 cx,
22469 );
22470
22471 prompt
22472 });
22473
22474 Self {
22475 prompt,
22476 editor,
22477 breakpoint_anchor,
22478 breakpoint,
22479 edit_action,
22480 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
22481 block_ids: Default::default(),
22482 _subscriptions: vec![],
22483 }
22484 }
22485
22486 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
22487 self.block_ids.extend(block_ids)
22488 }
22489
22490 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
22491 if let Some(editor) = self.editor.upgrade() {
22492 let message = self
22493 .prompt
22494 .read(cx)
22495 .buffer
22496 .read(cx)
22497 .as_singleton()
22498 .expect("A multi buffer in breakpoint prompt isn't possible")
22499 .read(cx)
22500 .as_rope()
22501 .to_string();
22502
22503 editor.update(cx, |editor, cx| {
22504 editor.edit_breakpoint_at_anchor(
22505 self.breakpoint_anchor,
22506 self.breakpoint.clone(),
22507 match self.edit_action {
22508 BreakpointPromptEditAction::Log => {
22509 BreakpointEditAction::EditLogMessage(message.into())
22510 }
22511 BreakpointPromptEditAction::Condition => {
22512 BreakpointEditAction::EditCondition(message.into())
22513 }
22514 BreakpointPromptEditAction::HitCondition => {
22515 BreakpointEditAction::EditHitCondition(message.into())
22516 }
22517 },
22518 cx,
22519 );
22520
22521 editor.remove_blocks(self.block_ids.clone(), None, cx);
22522 cx.focus_self(window);
22523 });
22524 }
22525 }
22526
22527 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
22528 self.editor
22529 .update(cx, |editor, cx| {
22530 editor.remove_blocks(self.block_ids.clone(), None, cx);
22531 window.focus(&editor.focus_handle);
22532 })
22533 .log_err();
22534 }
22535
22536 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
22537 let settings = ThemeSettings::get_global(cx);
22538 let text_style = TextStyle {
22539 color: if self.prompt.read(cx).read_only(cx) {
22540 cx.theme().colors().text_disabled
22541 } else {
22542 cx.theme().colors().text
22543 },
22544 font_family: settings.buffer_font.family.clone(),
22545 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22546 font_size: settings.buffer_font_size(cx).into(),
22547 font_weight: settings.buffer_font.weight,
22548 line_height: relative(settings.buffer_line_height.value()),
22549 ..Default::default()
22550 };
22551 EditorElement::new(
22552 &self.prompt,
22553 EditorStyle {
22554 background: cx.theme().colors().editor_background,
22555 local_player: cx.theme().players().local(),
22556 text: text_style,
22557 ..Default::default()
22558 },
22559 )
22560 }
22561}
22562
22563impl Render for BreakpointPromptEditor {
22564 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22565 let editor_margins = *self.editor_margins.lock();
22566 let gutter_dimensions = editor_margins.gutter;
22567 h_flex()
22568 .key_context("Editor")
22569 .bg(cx.theme().colors().editor_background)
22570 .border_y_1()
22571 .border_color(cx.theme().status().info_border)
22572 .size_full()
22573 .py(window.line_height() / 2.5)
22574 .on_action(cx.listener(Self::confirm))
22575 .on_action(cx.listener(Self::cancel))
22576 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
22577 .child(div().flex_1().child(self.render_prompt_editor(cx)))
22578 }
22579}
22580
22581impl Focusable for BreakpointPromptEditor {
22582 fn focus_handle(&self, cx: &App) -> FocusHandle {
22583 self.prompt.focus_handle(cx)
22584 }
22585}
22586
22587fn all_edits_insertions_or_deletions(
22588 edits: &Vec<(Range<Anchor>, String)>,
22589 snapshot: &MultiBufferSnapshot,
22590) -> bool {
22591 let mut all_insertions = true;
22592 let mut all_deletions = true;
22593
22594 for (range, new_text) in edits.iter() {
22595 let range_is_empty = range.to_offset(&snapshot).is_empty();
22596 let text_is_empty = new_text.is_empty();
22597
22598 if range_is_empty != text_is_empty {
22599 if range_is_empty {
22600 all_deletions = false;
22601 } else {
22602 all_insertions = false;
22603 }
22604 } else {
22605 return false;
22606 }
22607
22608 if !all_insertions && !all_deletions {
22609 return false;
22610 }
22611 }
22612 all_insertions || all_deletions
22613}
22614
22615struct MissingEditPredictionKeybindingTooltip;
22616
22617impl Render for MissingEditPredictionKeybindingTooltip {
22618 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22619 ui::tooltip_container(window, cx, |container, _, cx| {
22620 container
22621 .flex_shrink_0()
22622 .max_w_80()
22623 .min_h(rems_from_px(124.))
22624 .justify_between()
22625 .child(
22626 v_flex()
22627 .flex_1()
22628 .text_ui_sm(cx)
22629 .child(Label::new("Conflict with Accept Keybinding"))
22630 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
22631 )
22632 .child(
22633 h_flex()
22634 .pb_1()
22635 .gap_1()
22636 .items_end()
22637 .w_full()
22638 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
22639 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
22640 }))
22641 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
22642 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
22643 })),
22644 )
22645 })
22646 }
22647}
22648
22649#[derive(Debug, Clone, Copy, PartialEq)]
22650pub struct LineHighlight {
22651 pub background: Background,
22652 pub border: Option<gpui::Hsla>,
22653 pub include_gutter: bool,
22654 pub type_id: Option<TypeId>,
22655}
22656
22657fn render_diff_hunk_controls(
22658 row: u32,
22659 status: &DiffHunkStatus,
22660 hunk_range: Range<Anchor>,
22661 is_created_file: bool,
22662 line_height: Pixels,
22663 editor: &Entity<Editor>,
22664 _window: &mut Window,
22665 cx: &mut App,
22666) -> AnyElement {
22667 h_flex()
22668 .h(line_height)
22669 .mr_1()
22670 .gap_1()
22671 .px_0p5()
22672 .pb_1()
22673 .border_x_1()
22674 .border_b_1()
22675 .border_color(cx.theme().colors().border_variant)
22676 .rounded_b_lg()
22677 .bg(cx.theme().colors().editor_background)
22678 .gap_1()
22679 .block_mouse_except_scroll()
22680 .shadow_md()
22681 .child(if status.has_secondary_hunk() {
22682 Button::new(("stage", row as u64), "Stage")
22683 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22684 .tooltip({
22685 let focus_handle = editor.focus_handle(cx);
22686 move |window, cx| {
22687 Tooltip::for_action_in(
22688 "Stage Hunk",
22689 &::git::ToggleStaged,
22690 &focus_handle,
22691 window,
22692 cx,
22693 )
22694 }
22695 })
22696 .on_click({
22697 let editor = editor.clone();
22698 move |_event, _window, cx| {
22699 editor.update(cx, |editor, cx| {
22700 editor.stage_or_unstage_diff_hunks(
22701 true,
22702 vec![hunk_range.start..hunk_range.start],
22703 cx,
22704 );
22705 });
22706 }
22707 })
22708 } else {
22709 Button::new(("unstage", row as u64), "Unstage")
22710 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22711 .tooltip({
22712 let focus_handle = editor.focus_handle(cx);
22713 move |window, cx| {
22714 Tooltip::for_action_in(
22715 "Unstage Hunk",
22716 &::git::ToggleStaged,
22717 &focus_handle,
22718 window,
22719 cx,
22720 )
22721 }
22722 })
22723 .on_click({
22724 let editor = editor.clone();
22725 move |_event, _window, cx| {
22726 editor.update(cx, |editor, cx| {
22727 editor.stage_or_unstage_diff_hunks(
22728 false,
22729 vec![hunk_range.start..hunk_range.start],
22730 cx,
22731 );
22732 });
22733 }
22734 })
22735 })
22736 .child(
22737 Button::new(("restore", row as u64), "Restore")
22738 .tooltip({
22739 let focus_handle = editor.focus_handle(cx);
22740 move |window, cx| {
22741 Tooltip::for_action_in(
22742 "Restore Hunk",
22743 &::git::Restore,
22744 &focus_handle,
22745 window,
22746 cx,
22747 )
22748 }
22749 })
22750 .on_click({
22751 let editor = editor.clone();
22752 move |_event, window, cx| {
22753 editor.update(cx, |editor, cx| {
22754 let snapshot = editor.snapshot(window, cx);
22755 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
22756 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
22757 });
22758 }
22759 })
22760 .disabled(is_created_file),
22761 )
22762 .when(
22763 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
22764 |el| {
22765 el.child(
22766 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
22767 .shape(IconButtonShape::Square)
22768 .icon_size(IconSize::Small)
22769 // .disabled(!has_multiple_hunks)
22770 .tooltip({
22771 let focus_handle = editor.focus_handle(cx);
22772 move |window, cx| {
22773 Tooltip::for_action_in(
22774 "Next Hunk",
22775 &GoToHunk,
22776 &focus_handle,
22777 window,
22778 cx,
22779 )
22780 }
22781 })
22782 .on_click({
22783 let editor = editor.clone();
22784 move |_event, window, cx| {
22785 editor.update(cx, |editor, cx| {
22786 let snapshot = editor.snapshot(window, cx);
22787 let position =
22788 hunk_range.end.to_point(&snapshot.buffer_snapshot);
22789 editor.go_to_hunk_before_or_after_position(
22790 &snapshot,
22791 position,
22792 Direction::Next,
22793 window,
22794 cx,
22795 );
22796 editor.expand_selected_diff_hunks(cx);
22797 });
22798 }
22799 }),
22800 )
22801 .child(
22802 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
22803 .shape(IconButtonShape::Square)
22804 .icon_size(IconSize::Small)
22805 // .disabled(!has_multiple_hunks)
22806 .tooltip({
22807 let focus_handle = editor.focus_handle(cx);
22808 move |window, cx| {
22809 Tooltip::for_action_in(
22810 "Previous Hunk",
22811 &GoToPreviousHunk,
22812 &focus_handle,
22813 window,
22814 cx,
22815 )
22816 }
22817 })
22818 .on_click({
22819 let editor = editor.clone();
22820 move |_event, window, cx| {
22821 editor.update(cx, |editor, cx| {
22822 let snapshot = editor.snapshot(window, cx);
22823 let point =
22824 hunk_range.start.to_point(&snapshot.buffer_snapshot);
22825 editor.go_to_hunk_before_or_after_position(
22826 &snapshot,
22827 point,
22828 Direction::Prev,
22829 window,
22830 cx,
22831 );
22832 editor.expand_selected_diff_hunks(cx);
22833 });
22834 }
22835 }),
22836 )
22837 },
22838 )
22839 .into_any_element()
22840}